2022金九银十月复习题js(持续更新)

83 阅读17分钟

一年一度的离职季又开始了,但是疫情原因很多人都不会选择跳槽,除非真的很想跳一下觉得外面的世界那么大,到底有多卷,现实确实很打脸,岗位少了,面试需要造轮子变强了,行业越来越内卷了,所以不想被淘汰只能静下心来,把知识一遍一遍炒。

HTML

web标准三方面

  • 结构标准(HTML:用于对网页元素进行整理和分类。
  • 表现标准(css):用于设置网页元素的版式、颜色、大小等外观样式。
  • 行为标准(js):用于定义网页行为和行为。

web前端分三层

  • HTML:超文本标记语言,从语义的角度描述页面的结构,相当于人的身体组织结构。
  • CSS:层叠样式,从审美的角度美化页面的样式,相当于人的衣服和打扮。
  • Js:从交互角度描述页面的行为,相当于人的动作,让人有生命力。

JavaScript

基本数据类型

  • 基本数据类型:String,Number,Boolean,Null,Undefined,Symbol,bigInt
  • 引用数据类型:Object,Array,Function,Date,Math,Map,Set,RegExp(正则表达式)
    区别:两者的存放地址不同
  • 基本数据类型存放在栈中,占用内存小,空间固定,且经常被使用。
  • 引用数据类型存放在堆中,占用内存大,空间不固定;引用数据类型在栈中存放指针。指针指向堆中该实体的起始地址。

null和undefined的区别?

  • 两个数据类型都是基本数据类型,并且都有一个值,null和undefined。
  • undefined的意思是未定义,比如说只是声明了一个变量但是为定义,那么就是undefined,null是一个空对象。

数据类型的监测方法有哪些?

typeof,instanceof,object.prototype.toString.call()
typeof: 返回一个字符串,表示未经计算的操作数的类型。无法判断数组,只能判断基本类型(上面7种基本数据类型)除了null以外都可以显示正确的类型,对于函数会显示function,对数组,对象,时间戳,null等类型返回的都是object类型,不能准确的定位其类型。对于数组、对象、时间戳、我们可以采用instanceof 来进行判断。

const str = 'abcdefg' 
typeof str // string

const arr = [] 
typeof arr // object

instanceof: 返回一个布尔值,运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。它的原理是根据原型链来查找。

const arr = []
arr instanceof Array // true
arr instanceof Object // true

function Car(make, model, year) {
  this.make = make;
  this.model = model;
  this.year = year;
}
const auto = new Car('Honda', 'Accord', 1998);

console.log(auto instanceof Car);
// expected output: true

console.log(auto instanceof Object);
// expected output: true

上面例子表明 构造函数 Car的 prototype 属性是在实例对象 auto 的原型链上
注意:instanceof可以判断数据类型,但是其类型是基于对象Object的(可以简记为typeof判断为Object)。有没有更好的判断变量类型的方法?有的 可以使用Object.prototype.toString.call(var),可以更加准确的判断某个变量的类型。
Object.prototype.toString.call: 返回 "[object type]",其中type是对象的类型,由名字可以看出此方法是将传入的数据类型转换成字符串输出(null和undefined除外);

var num = 123
num.toString() // '123'

var str = 'hello'
str.toString() // 'hello'

var bool = false
bool.toString() // 'false'

var arr = [1, 2, 3]
arr.toString()  // '1,2,3'

var obj = {lang:'zh'}
obj.toString()  // '[object Object]'

var fn = function(){}
fn.toString()  // 'function(){}'

null.toString()  // Cannot read property 'toString' of null
undefined.toString() // Cannot read property 'toString' of undefined
  • 原理:在JavaScript中,所有类都继承于Object,因此toString()方法应该也被继承了,但由上述可见事实并不像我们想的那样,其实各数据类型使用toString()后的结果表现不一的原因在于:所有类在继承Object的时候,改写了toString()方法。 Object原型上的方法是可以输出数据类型的。因此我们想判断数据类型时,也只能使用原始方法。
  • 当我们把Array自身的toString()方法删除之后,再次使用它时,由原型链它会向上查找这个方法,即Object的toString(),也便将Object上的toString()方法作用在数组上,得出其数据类型[object Array],如下:
// 定义一个数组
var arr = [1, 2, 3]

// 数组原型上是否具有 toString() 方法
console.log(Array.prototype.hasOwnProperty('toString')) //true

// 数组直接使用自身的 toString() 方法
console.log(arr.toString()) // '1,2,3'

// delete操作符删除数组原型上的 toString()
delete Array.prototype.toString

// 删除后,数组原型上是否还具有 toString() 方法
console.log(Array.prototype.hasOwnProperty('toString')) //false

// 删除后的数组再次使用 toString() 时,会向上层访问这个方法,即 Object 的 toString()
console.log(arr.toString()) // '[object Array]'

例子:

const str = 'hello world'
const arr = []
const obj = {}
Object.prototype.toString.call(str) // '[object String]'
Object.prototype.toString.call(arr) // '[object Array]'
Object.prototype.toString.call(obj) // '[object Object]'
Object.prototype.toString.call(null) // '[object Null]'
Object.prototype.toString.call(undefined) // '[object Undefined]'

检测是否为数组?

var arr = [1,2,3]
arr instanceof Array //true
object.prototype.toString.call(arr) //[object Array]
Array.isArray(arr) //true,ES6新增语法
arr.proto == Array.prototype //判断一下,true
Array.prototype.isPrototypeOf(arr)//true,用Array原型上的方法

isNaN和Number.isNaN的区别?

  • isNaN()是ES5的方法,Number.isNaN()是ES6的方法
  • isNaN本意是通过Number方法把参数转换成数字类型,如若转换成功,则返回false,反之返回true,它只是判断参数是否能转成数字,不能用来判断是否严格等于NaN
  • ES6提供了Number.isNaN方法用来判断一个值是否严格等于NaN,它会首先判断传入的值是否为数字类型,如不是,直接返回false
  • isNaN方法首先转换类型,而Number.isNaN方法不用
  • isNaN不能用来判断是否严格等于NaN,Number.isNaN方法可用

object.assign()和(...)扩展运算符的区别

  • 两者都是浅拷贝
  • Object.assign()方法接收的第一个参数作为目标对象,后面的所有参数作为源对象。然后把所有的源对象合并到目标对象中。它会修改了一个对象,因此会触发 ES6 setter。
  • 扩展操作符(…)使用它时,数组或对象中的每一个值都会被拷贝到一个新的数组或对象中。它不复制继承的属性或类的属性,但是它会复制ES6的 symbols 属性。

如何判断一个对象是一个空对象

(1)利用JSON.stringify() 方法:
if(JSON.stringify(obj)=='{}') 如果为真的话就返回空对象
(2)object.keys(obj)方法:
if(object.keys(obj).length<=0) 如果为真就是空对象
(3)for...in...

    for(let key in obj){
        return false;//若不为空,可遍历,返回false
    }
    return true;
}
console.log(result(obj));//返回true

let,const,var的区别

JS中作用域有:全局作用域、函数作用域。没有块作用域的概念。 ECMAScript 6(简称ES6)中新增了块级作用域,使用let声明的变量只能在块级作用域里访问,有“暂时性死区”的特性(也就是说声明前不可用)。

  • 块级作用域:大部分包含{}的都可以构成块级作用域,但是函数和对象不构成块级作用域(函数属于函数作用域而不是块级作用域,对象的话,又不能在里面let)
  • let和const没有变量提升,具有块级作用域
  • var具有变量提升,没有块级作用域
  • let和var声明的变量可以进行修改(const声明常量,允许在不重新赋值的情况下修改它的值(基本数据类型不可,只有引用数据类型可以,引用类型引用的是地址不是值))
  • 作用域链js中, 函数存在一个隐式属性 [[scopes]], 这个属性用来保存当前函数的执行上下文环境, 由于在数据结构上是链式的, 因此也被称作是作用域链
    • 作用域链的作用是保证执行环境里有权访问的变量和函数是有序的,作用域链的变量只能向上访问,变量访问到window对象即被终止,作用域链向下访问变量是不被允许的

函数的this指向

普通函数中的this:

  • this总是代表它的直接调用者(js的this是执行上下文), 例如 obj.func ,那么func中的this就是obj
  • 在默认情况(非严格模式下,未使用 'use strict'),没找到直接调用者,则this指的是 window (约定俗成)
  • 在严格模式下,没有直接调用者的函数中的this是 undefined
  • 使用call,apply,bind(ES5新增)绑定的,this指的是 绑定的对象
    • bind返回一个函数,可以延迟调用
    • call可以接收多个参数,第一个参数是this(你想指定的上下文),在调用的时候需要把其他参数一个一个按顺序传入
    • apply只能接收两个参数,第一个参数是this(你想指定的上下文),第二个参数是一个数组,在调用的时候需要把其他参数都放在这个数组里。

箭头函数中的this

  • 箭头函数没有自己的this,它的this是继承而来;\color{#0000FF}{箭头函数没有自己的this, 它的this是继承而来;} 默认指向在定义它时所处的对象(宿主对象),而不是执行时的对象, 定义它的时候,可能环境是window; 箭头函数可以方便地让我们在 setTimeout ,setInterval中方便的使用this
  • 箭头函数中,this指向的固定化,并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this,导致内部的this就是外层代码块的this

对json的一些理解

  • json是一种轻量级的数据交换格式,在与后端的数据交互中具有较为广泛的应用
  • 在javaScript中,我们可以直接使用json,因为JavaScript中内置了json的解析,把任何的JavaScript对象变成json,就是把这个对象序列化成一个json格式的字符串,这样才能通过网络传递给其他计算机。如果我们收到json格式的字符串,只需要把它反序列化为一个JavaScript对象,就可以在JavaScript中直接使用这个对象了
  • JSON 是适用于 Ajax 应用程序的一种有效格式,原因是它使 JavaScript 对象和字符串值之间得以快速转换 JSON是一种传递对象的语法。JSON是一个提供了stringify和parse方法的内置对象。
    stringify将js对象转化为符合json标准的字符串
    parse将符合json标准的字符串转化为js对象。

JS脚本延时加载的方法

  • defer:给脚本添加defer属性,让脚本加载的同时也进行解析文档,这样不会阻碍到页面的渲染,并且多个添加defer属性的脚本会按照顺序来执行。
  • async:给脚本添加async属性,让脚本进行异步加载,先下载js脚本,下载完脚本之后立即执行js,但是这样也有可能会导致文档没有加载完,会阻碍到页面的渲染。
  • 设置一个定时器,延缓js脚本的加载
  • 将js脚本写在文档的最后面
  • 设置监听事件,监测文档是否解析完毕,解析完毕动态引入脚本

数组原生的方法

  • 数组转换为字符串:toString(),tolocalString(),join()
  • 末尾操作:pop()删除最后一个元素,push()在末尾增加一个元素
  • 首位操作:shift()删除第一个元素,unshift()在守卫增加一个元素
  • 重排序和翻转数组:sort(),resverse()
  • 合并数组:concat()
  • 数组截取(浅拷贝)方法:slice(),不会改变原数组
  • 数组修改:splice(),改变了原数组
  • 数组归并方法:reduce()

set与map的区别

Map

Map对象保存键值对。任何值(对象或者原始值) 都可以作为一个键或一个值。构造函数Map可以接受一个数组作为参数

Map和Object的区别

  • 一个Object 的键只能是字符串或者 Symbols,但一个Map 的键可以是任意值。
  • Map中的键值是有序的(FIFO 原则),而添加到对象中的键则不是。
  • Map的键值对个数可以从 size 属性获取,而 Object 的键值对个数只能手动计算。
  • Object 都有自己的原型,原型链上的键名有可能和你自己在对象上的设置的键名产生冲突。

Set

Set对象允许你存储任何类型的值,无论是原始值或者是对象引用。它类似于数组,但是成员的值都是唯一的,没有重复的值。
Set 本身是一个构造函数,用来生成Set 数据结构。Set函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化。

区别:

  • 1.Map是键值对,Set是值的集合,当然键和值可以是任何的值;
  • 2.Map可以通过get方法获取值,而set不能因为它只有值;
  • 3.都能通过迭代器进行for...of遍历;
  • 4.Set的值是唯一的可以做数组去重,Map由于没有格式限制,可以做数据存储
  • 5.map和set都是stl中的关联容器,map以键值对的形式存储,key=value组成pair,是一组映射关系。set只有值,可以认为只有一个数据,并且set中元素不可以重复且自动排序。

数组去重

  • 数组去重indexof去重
const arr = [1,2,3,4,5,6,6,5,4,3,2,1,0]
const newArr = arr.filter((it, index, list)=>list.indexOf(it) === index)
console.log(newArr)
// 输出:1 2 3 4 5 6 0
  • 数组去重new Set()
const arr = [1,2,3,4,5,6,6,5,4,3,2,1,0]
const newArr = [...new Set(arr)]
console.log(newArr)
// 输出:1 2 3 4 5 6 0

浅拷贝与深拷贝

  • 浅拷贝:只是拷贝一层,更深层次对象级别的只拷贝了地址
    浅拷贝的时候如果数据是基本数据类型,那么就如同直接赋值那种,会拷贝其本身,如果除了基本数据类型之外还有一层对象,那么对于浅拷贝而言就只能拷贝其引用,对象的改变会反应到拷贝对象上
  • 深拷贝:深拷贝就会拷贝多层,即使是嵌套了对象,也会都拷贝出来,内容和原对象一样,更改原对象,拷贝对象不会发生变化

示例:

  • 浅拷贝 浅拷贝如果遇到更深层次的数据(如:对象、数组这类引用类型数据),只是拷贝了地址。
  • 方法二Object.assign()方法:有两个参数,第一个参数是目标对象,第二个是源对象。Object.assign(target,  ...sources)
var obj = {
	id:1,
	name:'Andy',
	msg: {
		age:18
	}
}

var newObj = {}
for(var key in obj) {
	//key是当前属性名, obj[k]是当前属性值
	newObj[key] = obj[key]
}

console.log(newObj)
  • 深拷贝 遍历原对象,检查每个属性值都属于什么数据类型:若是简单数据类型,直接赋值;若是复杂数据类型的(数组、对象),我们先来判断属于哪种复杂数据类型,接着我们进入里面利用递归的方式,把里面的值取出来再进行赋值操作。
  • 注意:判断条件是否为数组、是否为对象这两个不能颠倒,因为若先判断是否为对象,那么数组也会进入该判断,因为数组为“特殊对象”。(使用instanceof判断的原因)
var obj = {
	id:1,
	name:'Andy',
	msg: {
		age:18
	},
	color:['pink','red']
}
 
var newObj = {}
//封装函数
function deepCopy(newObj,obj) {
	for(var key in obj) {
		//判断属性值属于哪种数据类型
		//属性值 obj[key]
		//1.判断这个值是否为数组(数组也属于特殊对象,也是引用类型数据)
		if(obj[key] instanceof Array) {
			newObj[key] = []
			deepCopy(newObj[key],obj[key]) //运用递归,把原对象属性值给新对象
			//判断这个值是否为对象
		} else if(obj[key] instanceof Object) {
			newObj[key] = {}
			deepCopy(newObj[key],obj[key]) //运用递归,把原对象属性值给新对象
		} else {
			//若是普通数据类型
			newObj[key] = obj[key]
		}
		
	}
}
deepCopy(newObj,obj)
  • 方法二利用JSON.stringify():先利用JSON.stringify()将js对象转化为字符串,再利用JSON.parse()方法将字符串还原为js对象赋值给一个新的对象,这样就完成了深拷贝。但是如果拷贝的对象当中有函数,undefined,Symbol的话,数据就会消失。

原型和原型链

  • js中最重要的两条链作用域链和原型链,作用域链是从上往下,原型链是从下往上
  • 原型:
    • 原型就是一个属性,是构造函数属性,是构造函数制造出来的对象的公共祖先,后面所有对象都会继承原型的属性和方法。
    • 原型也是一个对象,所有实例对象需要共享的属性和方法都会存放在这个对象里面,不需要共享的就放在构造函数里面。
    • 在默认情况下,所有原型对象都会自动获得一个constructor属性,这个属性指向prototype属性所在函数。即 Person.prototype.constructor == Person
  • prototype和__proto__
    • __ prototype __ 用来查看原型,是对象的属性,可以查看但是不能修改
    • prototype用来修改原型
    • 每个对象都有__proto__属性,但只有函数对象才有prototype属性
  • 任何对象的最终原型都是Object.prototype
  • 原型对象的作用:原型对象主要作用主要是用于继承
  • __ proto __ JS在创建对象的时候,都有一个叫做__proto__的内置对象属性,用于指向创建它的构造函数的原型对象
  • Object.prototype也有proto属性,但是null。因为处于原型链的顶端
  • 原型链:
    • 在JavaScript中每个对象都有一个指向他的原型内部对象的内部链接,每个原型对象又有自己的原型,直到某个对象的原型为null为止,组成这条链的最后一环。
  • 所有的函数构造器也都是一个普通JS独享,可以给构造器添加或者删除属性等。同时也继承了Object.prototype上的所有方法。

宏任务和微任务

  • macroTask Queue(宏任务队列) : setTimeout setInterval Ajax DOM事件
  • microTask Queue(微任务队列):Promise async/await
  • 微任务比宏任务的执行时间要早 \

同步任务和异步任务
JavaScript是单线程执行的语言,在同一个时间只能做一件事情。这就导致后面的任务需要等到前面的任务完成才能执行,如果前面的任务很耗时就会造成后面的任务一直等待。为了解决这个问题JS中出现了同步任务和异步任务。

  • 同步任务: 在主线程上排队执行的任务只有前一个任务执行完毕,才能执行后一个任务,形成一个执行栈。
  • 异步任务: 不进入主线程,而是进入任务队列,当主线程中的任务执行完毕,就从任务队列中取出任务放进主线程中来进行执行。由于主线程不断重复的获得任务、执行任务、再获取再执行,所以这种机制被叫做事件循环(Event Loop)

我们都知道 Js 是单线程的,但是一些高耗时操作带来了进程阻塞的问题。为了解决这个问题,Js 有两种任务的执行模式:同步模式(Synchronous)和异步模式(Asynchronous)

在异步模式下,创建异步任务主要分为宏任务与微任务两种。ES6 规范中,宏任务(Macrotask) 称为 Task, 微任务(Microtask) 称为 Jobs。宏任务是由宿主(浏览器、Node)发起的,而微任务由 JS 自身发起。

Promise和setTimeout区别

  • promise.then异步会放到microtask queue中。microtask队列中的内容经常是为了需要直接在当前脚本执行完后立即发生的事,所以当同步脚本执行完之后,就调用microtask队列中的内容,然后把异步队列中的setTimeout放入执行栈中执行,所以最终结果是先执行promise.then异步,然后再执行setTimeout异步。
    • Promise 的回调函数属于异步任务,会在同步任务之后执行。
      但是,Promise 的回调函数不是正常的异步任务,而是微任务(microtask)。它们的区别在于,正常任务追加到下一轮事件循环,微任务追加到本轮事件循环。这意味着,微任务的执行时间一定早于正常任务。
setTimeout(() => {
  console.log(7)
}, 0)
new Promise((resolve, reject) => {
  console.log(3);
  resolve();
  console.log(4);
}).then(() => {
  console.log(6)
})
console.log(5)
// 3,4,5,6,7