当前位置:   article > 正文

前端JS高频面试题【更新中】

js高频面试题

数据类型

面试官:JS的数据类型都有哪些⭐⭐⭐⭐⭐

答:
数据类型分为基本数据类型引用数据类型
基本数据类型有:

  • Number
  • String
  • Boolean
  • null
  • Undefined
  • Symbol
  • BigInt

引用数据类型统称为Object类型,细分的话有:

  • Object
  • Array
  • Function
  • Date
  • RegExp

基本数据类型的数据直接存储在栈中;而引用数据类型的数据存储在堆中,在栈中保存数据的引用地址,这个引用地址指向的是对应的数据,以便快速查找到堆内存中的对象。

顺便提一句,栈内存是自动分配内存的。而堆内存是动态分配内存的,不会自动释放。所以每次使用完对象的时候都要把它设置为null,从而减少无用内存的消耗

为什么0.1+0.2>0.3 ⭐⭐⭐⭐

答:

因为在JS底层中,每个变量是以二进制表示,固定长度为64位,其中第1位是符号位,再往后11位是指数为,最后52表示的是尾数位,而0.1和0.2转为二进制的时候是无限循环小数,所以JS就会进行截取,截取以后0.1和0.2就不是他们本身了,要比原来大那么一丢丢,所以0.1+0.2就>0.3了

如何解决这个问题,使0.1+0.2等于0.3? ⭐⭐⭐⭐⭐

答:
先给他们放大倍数,随后在除以相应倍数

const a = 0.1;
const b = 0.2;

console.log(a + b === 0.3)   // false
console.log((a * 1000 + b * 1000) / 1000 === 0.3)  // true

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

数据类型的判断方式⭐⭐⭐⭐⭐

答:
1.typeof

  • 缺点:typeof null的值为Object,无法分辨是null还是Object

2.instanceof

  • 缺点:只能判断某对象是否存在于目标对象的原型链上

3.constructor

  • 使用 constructor 可以查看目标构造函数,也可以进行数据类型判断。
  • 但是不能判断 nullundefined,因为这两个特殊类型没有其对应的包装对象。constructorinstanceof 类似,constructor 返回结果的是自己的构造函数,而 instanceof 则是自己与构造函数比较返回布尔值

4.Object.prototype.toString.call()

  • 一种最好的基本类型检测方式 Object.prototype.toString.call() ;它可以区分 null 、 string 、boolean 、 number 、 undefined 、 array 、 function 、 object 、 date 、 math 数据类型。

  • 缺点:不能细分为谁谁的实例

// -----------------------------------------typeof
typeof undefined // 'undefined' 
typeof '10' // 'String' 
typeof 10 // 'Number' 
typeof false // 'Boolean' 
typeof Symbol() // 'Symbol' 
typeof Function // ‘function' 
typeof null // ‘Object’ 
typeof [] // 'Object' 
typeof {} // 'Object'

// -----------------------------------------instanceof
function Foo() { }
var f1 = new Foo();
var d = new Number(1)
console.log(f1 instanceof Foo);// true
console.log(d instanceof Number); //true
console.log(123 instanceof Number); //false   -->不能判断字面量的基本数据类型

// -----------------------------------------constructor
var d = new Number(1)
var e = 1
function fn() {
  console.log("ming");
}
var date = new Date();
var arr = [1, 2, 3];
var reg = /[hbc]at/gi;

console.log(e.constructor);//ƒ Number() { [native code] }
console.log(e.constructor.name);//Number
console.log(fn.constructor.name) // Function 
console.log(date.constructor.name)// Date 
console.log(arr.constructor.name) // Array 
console.log(reg.constructor.name) // RegExp

(5).constructor === Number     // true
"text".constructor === String  // true
true.constructor === Boolean   // true
({}).constructor === Object    // true
// Uncaught TypeError: Cannot read property 'constructor' of undefined
undefined.constructor === undefined  // 报错
null.constructor === null            // 报错

//-----------------------------------------Object.prototype.toString.call()
console.log(Object.prototype.toString.call(undefined)); // "[object Undefined]" 
console.log(Object.prototype.toString.call(null)); // "[object Null]" 
console.log(Object.prototype.toString.call(123)); // "[object Number]" 
console.log(Object.prototype.toString.call("abc")); // "[object String]" 
console.log(Object.prototype.toString.call(true)); // "[object Boolean]" 


function fn() {
  console.log("ming");
}
var date = new Date();
var arr = [1, 2, 3];
var reg = /[hbc]at/gi;
console.log(Object.prototype.toString.call(fn));// "[object Function]" 
console.log(Object.prototype.toString.call(date));// "[object Date]" 
console.log(Object.prototype.toString.call(arr)); // "[object Array]"
console.log(Object.prototype.toString.call(reg));// "[object RegExp]"
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62

为什么要用Object.prototype.toString.call(),为什么不用 Array.prototype.toString.call()?⭐⭐

答:
因为只有Object.prototype.toString.call()返回的是统一格式,而且 Array.prototype.toString.call()的部分类型无法检验。

function fn() {
 console.log("ming");
}
var date = new Date();
var arr = [1, 2, 3];
var reg = /[hbc]at/gi;

console.log(Array.prototype.toString.call(undefined)); // 报错
console.log(Array.prototype.toString.call(null)); // 报错
console.log(Array.prototype.toString.call(123)); // "[object Number]" 
console.log(Array.prototype.toString.call("abc")); // "[object String]" 
console.log(Array.prototype.toString.call(true)); // "[object Boolean]" 
console.log(Array.prototype.toString.call(fn)); // "[object Function]" 
console.log(Array.prototype.toString.call(date)); // "[object Date]" 
console.log(Array.prototype.toString.call(arr)); // "1,2,3"
console.log(Array.prototype.toString.call(reg));// "[object RegExp]"

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

instanceof原理⭐⭐⭐⭐⭐

  • instanceof原理实际上就是查找目标对象的原型链
function myInstance(L, R) {//L代表instanceof左边,R代表右边
 var RP = R.prototype
 var LP = L.__proto__
 while (true) {
   if(LP == null) {
     return false
   }
   if(LP == RP) {
     return true
   }
   LP = LP.__proto__
 }
}
console.log(myInstance({},Object)); 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

为什么typeof null 是Object?⭐⭐⭐⭐⭐

答:

因为在JavaScript中,不同的对象都是使用二进制存储的,如果二进制前三位都是0的话,系统会判断为是Object类型,而null的二进制全是0,自然也就判断为Object

这个bug是初版本的JavaScript中留下的,扩展一下其他五种标识位:

  • 000 对象
  • 1 整型
  • 010 双精度类型
  • 100字符串
  • 110布尔类型

=====有什么区别 ⭐⭐⭐⭐⭐

答:
===是严格意义上的相等,会比较两边的数据类型和值大小

  • 数据类型不同返回false

  • 数据类型相同,但值大小不同,返回false
    ==是非严格意义上的相等,

  • 两边类型相同,比较大小

  • 两边类型不同,根据下方表格,再进一步进行比较。

    • Null == Undefined ->true
    • String == Number ->先将String转为Number,在比较大小
    • Boolean == Number ->现将Boolean转为Number,在进行比较
    • Object == String,Number,Symbol -> Object 转化为原始类型

NaN === NaN返回什么?⭐⭐⭐⭐⭐

返回 falseNaN永远不等于NaN,判断是否为NaN用一个函数 isNaN来判断;

isNaN传入的如果是其他数据类型,那么先将它使用Number()转为数字类型再进行判断

手写call、apply、bind⭐⭐⭐⭐⭐

答:

  • call和apply实现思路主要是:
    • 判断是否是函数调用,若非函数调用抛异常
    • 通过新对象(context)来调用函数
    • 给context创建一个fn设置为需要调用的函数
    • 结束调用完之后删除fn
  • bind实现思路
    • 判断是否是函数调用,若非函数调用抛异常
    • 返回函数
      • 判断函数的调用方式,是否是被new出来的
        • new出来的话返回空对象,但是实例的__proto__指向_this的prototype
    • 完成函数柯里化
      • Array.prototype.slice.call()

call:

Function.prototype.myCall = function (context) {
   // 先判断调用myCall是不是一个函数
   // 这里的this就是调用myCall的
   if (typeof this !== 'function') {
     throw new TypeError("Not a Function")
   }

   // 不传参数默认为window
   context = context || window

   // 保存this
   context.fn = this

   // 保存参数
   let args = Array.from(arguments).slice(1)   //Array.from 把伪数组对象转为数组

   // 调用函数
   let result = context.fn(...args)

   delete context.fn

   return result

 }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

apply

Function.prototype.myApply = function (context) {
  // 判断this是不是函数
  if (typeof this !== "function") {
    throw new TypeError("Not a Function")
  }

  let result

  // 默认是window
  context = context || window

  // 保存this
  context.fn = this

  // 是否传参
  if (arguments[1]) {
    result = context.fn(...arguments[1])
  } else {
    result = context.fn()
  }
  delete context.fn

  return result
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

bind

Function.prototype.myBind = function(context){
  // 判断是否是一个函数
  if(typeof this !== "function") {
    throw new TypeError("Not a Function")
  }
  // 保存调用bind的函数
  const _this = this 
  // 保存参数
  const args = Array.prototype.slice.call(arguments,1)
  // 返回一个函数
  return function F () {
    // 判断是不是new出来的
    if(this instanceof F) {
      // 如果是new出来的
      // 返回一个空对象,且使创建出来的实例的__proto__指向_this的prototype,且完成函数柯里化
      return new _this(...args,...arguments)
    }else{
      // 如果不是new出来的改变this指向,且完成函数柯里化
      return _this.apply(context,args.concat(...arguments))
    }
  } 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

字面量创建对象和new创建对象有什么区别,new内部都实现了什么,手写一个new⭐⭐⭐⭐⭐

答:

字面量:

  • 字面量创建对象更简单,方便阅读
  • 不需要作用域解析,速度更快

new内部:

  • 创建一个新对象
  • 使新对象的__proto__指向原函数的prototype
  • 改变this指向(指向新的obj)并执行该函数,执行结果保存起来作为result
  • 判断执行函数的结果是不是null或Undefined,如果是则返回之前的新对象,如果不是则返回result

手写new

// 手写一个new
function myNew(fn, ...args) {
  // 创建一个空对象
  let obj = {}
  // 使空对象的隐式原型指向原函数的显式原型
  obj.__proto__ = fn.prototype
  // this指向obj
  let result = fn.apply(obj, args)
  // 返回
  return result instanceof Object ? result : obj
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

null和undefined的区别

undefined 代表的含义是未定义,null 代表的含义是空对象。一般变量声明了但还没有定义的时候会返回 undefined,null主要用于赋值给一些可能会返回对象的变量,作为初始化。
null表示"没有对象",即该处不应该有值。典型用法是:
(1)作为函数的参数,表示该函数的参数不是对象。
(2)作为对象原型链的终点。
undefined表示"缺少值",就是此处应该有一个值,但是还没有定义。典型用法是:
(1)变量被声明了,但没有赋值时,就等于undefined。
(2)调用函数时,应该提供的参数没有提供,该参数等于undefined。
(3)对象没有赋值的属性,该属性的值为undefined。
(4)函数没有返回值时,默认返回undefined。
ECMAScript 认为 undefined 是从 null 派生出来的,他们的值是一样的, 但是类型却不一样。
所以

null == undefined //true 
null === undefined //false
  • 1
  • 2

执行栈和执行上下文

什么是作用域,什么是作用域链?⭐⭐⭐⭐

答:

  • 规定变量和函数的可使用范围称作作用域
  • 每个函数都有一个作用域链,查找变量或者函数时,需要从局部作用域到全局作用域依次查找,这些作用域的集合称作作用域链。

什么是执行栈,什么是执行上下文?⭐⭐⭐⭐

答:

执行上下文分为:

  • 全局执行上下文
    • 创建一个全局的window对象,并规定this指向window,执行js的时候就压入栈底,关闭浏览器的时候才弹出
  • 函数执行上下文
    • 每次函数调用时,都会新创建一个函数执行上下文
    • 执行上下文分为创建阶段和执行阶段
      • 创建阶段:函数环境会创建变量对象:arguments对象(并赋值)、函数声明(并赋值)、变量声明(不赋值),函数表达式声明(不赋值);会确定this指向;会确定作用域
      • 执行阶段:变量赋值、函数表达式赋值,使变量对象编程活跃对象
  • eval执行上下文

执行栈:

  • 首先栈特点:先进后出
  • 当进入一个执行环境,就会创建出它的执行上下文,然后进行压栈,当程序执行完成时,它的执行上下文就会被销毁,进行弹栈。
  • 栈底永远是全局环境的执行上下文,栈顶永远是正在执行函数的执行上下文
  • 只有浏览器关闭的时候全局执行上下文才会弹出

闭包

面试官:什么是闭包?闭包的作用?闭包的应用?⭐⭐⭐⭐⭐

彻底理解js中的闭包

闭包,看这一篇就够了——带你看透闭包的本质,百发百中

  • 闭包:
    闭包就是能访问到外部函数作用域中变量的函数
  • 什么时候使用:
    当我们需要隐藏一些不希望被别人访问的内容时就可以使用闭包
  • 构成闭包的要件:
    1. 函数的嵌套
    2. 内部函数要引用外部函数中的变量
    3. 内部函数要作为返回值返回

答:
函数执行,形成私有的执行上下文,使内部私有变量不受外界干扰,起到保护保存的作用

作用:

  • 保护
    • 避免命名冲突
  • 保存
    • 解决循环绑定引发的索引问题
  • 变量不会销毁
    • 可以使用函数内部的变量,使变量不会被垃圾回收机制回收

应用:

  • 设计模式中的单例模式
  • for循环中的保留i的操作
  • 防抖和节流
    函数柯里化

缺点

  • 会出现内存泄漏的问题

原型和原型链

面试官:什么是原型?什么是原型链?如何理解⭐⭐⭐⭐⭐

答:

原型: 原型分为隐式原型和显式原型,每个对象都有一个隐式原型,它指向自己的构造函数的显式原型。每个构造方法都有一个显式原型。

  • __proto__是隐式原型;prototype是显式原型

  • 所有实例的__proto__都指向他们构造函数的prototype

  • 所有的prototype都是对象,自然它的__proto__指向的是Object()prototype

  • 所有的构造函数的隐式原型指向的都是Function()的显示原型

  • Object的隐式原型是null

原型链: 多个__proto__组成的集合成为原型链(概念类似于作用域链)

instanceof就是判断某对象是否位于某构造方法的原型链上。

继承

说一说 JS 中的常用的继承方式有哪些?以及各个继承方式的优缺点。⭐⭐⭐⭐⭐

答:

原型继承、组合继承、寄生组合继承、ES6的extend

原型继承

// ----------------------方法一:原型继承
// 原型继承
// 把父类的实例作为子类的原型
// 缺点:子类的实例共享了父类构造函数的引用属性   不能传参

var person = {
  friends: ["a", "b", "c", "d"]
}

var p1 = Object.create(person)

p1.friends.push("aaa")//缺点:子类的实例共享了父类构造函数的引用属性

console.log(p1);
console.log(person);//缺点:子类的实例共享了父类构造函数的引用属性
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

组合继承

// ----------------------方法二:组合继承
// 在子函数中运行父函数,但是要利用call把this改变一下,
// 再在子函数的prototype里面new Father() ,使Father的原型中的方法也得到继承,最后改变Son的原型中的constructor

// 缺点:调用了两次父类的构造函数,造成了不必要的消耗,父类方法可以复用
// 优点可传参,不共享父类引用属性
function Father(name) {
  this.name = name
  this.hobby = ["篮球", "足球", "乒乓球"]
}

Father.prototype.getName = function () {
  console.log(this.name);
}

function Son(name, age) {
  Father.call(this, name)
  this.age = age
}

Son.prototype = new Father()
Son.prototype.constructor = Son


var s = new Son("ming", 20)

console.log(s);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

寄生组合继承

// ----------------------方法三:寄生组合继承
function Father(name) {
  this.name = name
  this.hobby = ["篮球", "足球", "乒乓球"]
}

Father.prototype.getName = function () {
  console.log(this.name);
}

function Son(name, age) {
  Father.call(this, name)
  this.age = age
}

Son.prototype = Object.create(Father.prototype)
Son.prototype.constructor = Son

var s2 = new Son("ming", 18)
console.log(s2);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

extend

// ----------------------方法四:ES6的extend(寄生组合继承的语法糖)
//     子类只要继承父类,可以不写 constructor ,一旦写了,则在 constructor 中的第一句话
// 必须是 super 。

class Son3 extends Father { // Son.prototype.__proto__ = Father.prototype
  constructor(y) {
    super(200)  // super(200) => Father.call(this,200)
    this.y = y
  }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

内存泄露、垃圾回收机制

什么是内存泄漏⭐⭐⭐⭐⭐

答:
内存泄露是指不再用的内存没有被及时释放出来,导致该段内存无法被使用就是内存泄漏

为什么会导致的内存泄漏⭐⭐⭐⭐⭐

答:
内存泄漏指我们无法在通过js访问某个对象,而垃圾回收机制却认为该对象还在被引用,因此垃圾回收机制不会释放该对象,导致该块内存永远无法释放,积少成多,系统会越来越卡以至于崩溃

垃圾回收机制都有哪些策略?⭐⭐⭐⭐⭐

答:

  • 标记清除法
    • 垃圾回收机制获取根并标记他们,然后访问并标记所有来自它们的引用,然后在访问这些对象并标记它们的引用…如此递进结束后若发现有没有标记的(不可达的)进行删除,进入执行环境的不能进行删除
  • 引用计数法
    • 当声明一个变量并给该变量赋值一个引用类型的值时候,该值的计数+1,当该值赋值给另一个变量的时候,该计数+1,当该值被其他值取代的时候,该计数-1,当计数变为0的时候,说明无法访问该值了,垃圾回收机制清除该对象
    • 缺点: 当两个对象循环引用的时候,引用计数无计可施。如果循环引用多次执行的话,会造成崩溃等问题。所以后来被标记清除法取代。

深拷贝和浅拷贝 ⭐⭐⭐⭐⭐

数组的浅拷贝: 如果是数组,我们可以利用数组的一些方法,比如 slice,concat 方法返回一个新数组的特性来实现拷贝,但假如数组嵌套了对象或者数组的话,使用 concat 方法克隆并不完整,如果数组元素是基本类型,就会拷贝一份,互不影响,
而如果是对象或数组,就会只拷贝对象和数组的引用,这样我们无论在新旧数组进行了修改,两者都会发生变化,我们把这种复制引用的拷贝方法称为浅拷贝。
深拷贝就是指完全的拷贝一个对象,即使嵌套了对象,两者也互相分离,修改一个对象的属性,不会影响另一个。

浅拷贝: 以赋值的形式拷贝引用对象,仍指向同一个地址,修改时原对象也会受到影响

  • Object.assign()
  • ... (展开运算符) — 可以将一个数组中的元素展开到另一个数组中或者作为函数的参数传递
  • Array.concat()
  • Array.slice()

深拷贝: 完全拷贝一个新对象,修改时原对象不再受到任何影响

  • JSON.parse(JSON.stringify(obj)): 性能最快
    【 原理是 JOSN 对象中的 stringify 可以把一个 js 对象序列化为一个 JSON 字符串, parse 可以把 JSON 字符串反序列化为一个 js 对象,通过这两个方法,也可以实现对象的深复制。 】
    • 具有循环引用的对象时,报错
    • 当值为函数、undefined、或 symbol 时,无法拷贝
  • 递归进行逐一赋值
  • structuredClone(value)

手写浅拷贝深拷贝⭐⭐⭐⭐⭐

// ----------------------------------------------浅拷贝
// 只是把对象的属性和属性值拷贝到另一个对象中
var obj1 = {
  a: {
    a1: { a2: 1 },
    a10: { a11: 123, a111: { a1111: 123123 } }
  },
  b: 123,
  c: "123"
}
// 方式1
function shallowClone1(o) {
  let obj = {}

  for (let i in o) {
    obj[i] = o[i]
  }
  return obj
}

// 方式2
var shallowObj2 = { ...obj1 }

// 方式3
var shallowObj3 = Object.assign({}, obj1)

let shallowObj = shallowClone1(obj1);

shallowObj.a.a1 = 999
shallowObj.b = true

console.log(obj1);  //第一层的没有被改变,一层以下就被改变了



// ----------------------------------------------深拷贝

// 简易版  
function deepClone(o) {
  let obj = {}
  for (var i in o) {
    // if(o.hasOwnProperty(i)){
    if (typeof o[i] === "object") {
      obj[i] = deepClone(o[i])
    } else {
      obj[i] = o[i]
    }
    // }
  }
  return obj
}


var myObj = {
  a: {
    a1: { a2: 1 },
    a10: { a11: 123, a111: { a1111: 123123 } }
  },
  b: 123,
  c: "123"
}

var deepObj1 = deepClone(myObj)
deepObj1.a.a1 = 999
deepObj1.b = false
console.log(myObj);



// 简易版存在的问题:参数没有做检验,传入的可能是 Array、null、regExp、Date
function deepClone2(o) {
  if (Object.prototype.toString.call(o) === "[object Object]") {  //检测是否为对象
    let obj = {}
    for (var i in o) {
      if (o.hasOwnProperty(i)) {
        if (typeof o[i] === "object") {
          obj[i] = deepClone(o[i])
        } else {
          obj[i] = o[i]
        }
      }
    }
    return obj
  } else {
    return o
  }
}

function isObject(o) {
  return Object.prototype.toString.call(o) === "[object Object]" || Object.prototype.toString.call(o) === "[object Array]"
}

// 继续升级,没有考虑到数组,以及ES6中的map、set、weakset、weakmap
function deepClone3(o) {
  if (isObject(o)) {//检测是否为对象或者数组
    let obj = Array.isArray(o) ? [] : {}
    for (let i in o) {
      if (isObject(o[i])) {
        obj[i] = deepClone(o[i])
      } else {
        obj[i] = o[i]
      }
    }
    return obj
  } else {
    return o
  }
}


// 有可能碰到循环引用问题  var a = {}; a.a = a; clone(a);//会造成一个死循环
// 循环检测
// 继续升级
function deepClone4(o, hash = new map()) {
  if (!isObject(o)) return o//检测是否为对象或者数组
  if (hash.has(o)) return hash.get(o)
  let obj = Array.isArray(o) ? [] : {}

  hash.set(o, obj)
  for (let i in o) {
    if (isObject(o[i])) {
      obj[i] = deepClone4(o[i], hash)
    } else {
      obj[i] = o[i]
    }
  }
  return obj
}

// 递归易出现爆栈问题
//  将递归改为循环,就不会出现爆栈问题了
var a1 = { a: 1, b: 2, c: { c1: 3, c2: { c21: 4, c22: 5 } }, d: 'asd' };
var b1 = { b: { c: { d: 1 } } }
function cloneLoop(x) {
  const root = {};
  // 栈 
  const loopList = [  //->[]->[{parent:{a:1,b:2},key:c,data:{ c1: 3, c2: { c21: 4, c22: 5 } }}]
    {
      parent: root,
      key: undefined,
      data: x,
    }
  ];
  while (loopList.length) {
    // 深度优先
    const node = loopList.pop();
    const parent = node.parent; //{} //{a:1,b:2}
    const key = node.key; //undefined //c
    const data = node.data; //{ a: 1, b: 2, c: { c1: 3, c2: { c21: 4, c22: 5 } }, d: 'asd' }  //{ c1: 3, c2: { c21: 4, c22: 5 } }}
    // 初始化赋值目标,key 为 undefined 则拷贝到父元素,否则拷贝到子元素
    let res = parent; //{}->{a:1,b:2,d:'asd'} //{a:1,b:2}->{}
    if (typeof key !== 'undefined') {
      res = parent[key] = {};
    }
    for (let k in data) {
      if (data.hasOwnProperty(k)) {
        if (typeof data[k] === 'object') {
          // 下一次循环 
          loopList.push({
            parent: res,
            key: k,
            data: data[k],
          })
        } else {
          res[k] = data[k];
        }
      }
    }
  }
  return root
}


function deepClone5(o) {
  let result = {}
  let loopList = [
    {
      parent: result,
      key: undefined,
      data: o
    }
  ]

  while (loopList.length) {
    let node = loopList.pop()
    let { parent, key, data } = node
    let anoPar = parent
    if (typeof key !== 'undefined') {
      anoPar = parent[key] = {}
    }

    for (let i in data) {
      if (typeof data[i] === 'object') {
        loopList.push({
          parent: anoPar,
          key: i,
          data: data[i]
        })
      } else {
        anoPar[i] = data[i]
      }
    }
  }
  return result
}


let cloneA1 = deepClone5(a1)
cloneA1.c.c2.c22 = 5555555
console.log(a1);
console.log(cloneA1);


// ------------------------------------------JSON.stringify()实现深拷贝

function cloneJson(o) {
  return JSON.parse(JSON.stringify(o))
}

// let obj = { a: { c: 1 }, b: {} };
// obj.b = obj;
// console.log(JSON.parse(JSON.stringify(obj))) // 报错 // Converting circular structure to JSON
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222

深拷贝能使用hash递归的方式写出来就可以了
不过技多不压身,推荐还是看一看使用while实现深拷贝方法

单线程,同步异步

为什么JS是单线程的?⭐⭐⭐⭐⭐

答:因为JS里面有可视的Dom,如果是多线程的话,这个线程正在删除DOM节点,另一个线程正在编辑Dom节点,导致浏览器不知道该听谁的

说说 Promise 的原理?你是如何理解 Promise 的?⭐⭐⭐⭐⭐

  • 做到简易版的promise理解,以及会写race和all函数就可以
class MyPromise2 {
 constructor(executor) {
   // 规定状态
   this.state = "pending"
   // 保存 `resolve(res)` 的res值
   this.value = undefined
   // 保存 `reject(err)` 的err值
   this.reason = undefined
   // 成功存放的数组
   this.successCB = []
   // 失败存放的数组
   this.failCB = []


   let resolve = (value) => {
     if (this.state === "pending") {
       this.state = "fulfilled"
       this.value = value
       this.successCB.forEach(f => f())
     }
   }
   let reject = (reason) => {
     if (this.state === "pending") {
       this.state = "rejected"
       this.value = value
       this.failCB.forEach(f => f())
     }
   }

   try {
     // 执行
     executor(resolve, reject)
   } catch (error) {
     // 若出错,直接调用reject
     reject(error)
   }
 }
 then(onFulfilled, onRejected) {
   if (this.state === "fulfilled") {
     onFulfilled(this.value)
   }
   if (this.state === "rejected") {
     onRejected(this.value)
   }
   if (this.state === "pending") {
     this.successCB.push(() => { onFulfilled(this.value) })
     this.failCB.push(() => { onRejected(this.reason) })
   }
 }
}

Promise.all = function (promises) {
 let list = []
 let count = 0
 function handle(i, data) {
   list[i] = data
   count++
   if (count == promises.length) {
     resolve(list)
   }
 }
 return Promise((resolve, reject) => {
   for (let i = 0; i < promises.length; i++) {
     promises[i].then(res => {
       handle(i, res)
     }, err => reject(err))
   }
 })
}

Promise.race = function (promises) {
   return Promise((resolve, reject) => {
       for (let i = 0; i < promises.length; i++) {
           promises[i].then(res => {
               resolve(res)
           }, err => {
               reject(err)
           })
       }
   })
 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81

以下代码的执行顺序是什么⭐⭐⭐⭐⭐

async function async1() {
 console.log('async1 start')
 await async2()
 console.log('async1 end')
}
async function async2() {
 console.log('async2')
}
async1()
console.log('script start')

//执行到await时,如果返回的不是一个promise对象,await会阻塞下面代码(当前async代码块的代码),会先执行async外的同步代码(在这之前先看看await中函数的同步代码,先把同步代码执行完),等待同步代码执行完之后,再回到async内部继续执行
//执行到await时,如果返回的是一个promise对象,await会阻塞下面代码(当前async代码块的代码),会先执行async外的同步代码(在这之前先看看await中函数的同步代码,先把同步代码执行完),等待同步代码执行完之后,再回到async内部等promise状态达到fulfill的时候再继续执行下面的代码
//所以结果为
//async1 start
//async2
//script start
//async1 end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

宏任务和微任务都有哪些⭐⭐⭐⭐⭐

答:

  • 宏任务:scriptsetTimeOutsetIntervalsetImmediate
  • 微任务:promise.then,process.nextTickObject.observeMutationObserver
  • 注意:Promise是同步任务

宏任务和微任务都是怎样执行的⭐⭐⭐⭐⭐

答:

  • 执行宏任务script,
  • 进入script后,所有的同步任务主线程执行
  • 所有宏任务放入宏任务执行队列
  • 所有微任务放入微任务执行队列
  • 先清空微任务队列,
  • 再取一个宏任务,执行,再清空微任务队列
  • 依次循环

例题1

setTimeout(function(){
    console.log('1')
});
new Promise(function(resolve){
    console.log('2');
    resolve();
}).then(function(){
    console.log('3')
});
console.log('4');
new Promise(function(resolve){
    console.log('5');
    resolve();
}).then(function(){
    console.log('6')
});
setTimeout(function(){
    console.log('7')
});
function bar(){
    console.log('8')
    foo()
}
function foo(){
    console.log('9')
}
console.log('10')
bar()

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

解析

  1. 首先浏览器执行Js代码由上至下顺序,遇到setTimeout,把setTimeout分发到宏任务Event Queue中
  2. new Promise属于主线程任务直接执行打印2
  3. Promis下的then方法属于微任务,把then分到微任务 Event Queue中
  4. console.log(‘4’)属于主线程任务,直接执行打印4
  5. 又遇到new Promise也是直接执行打印5,Promise 下到then分发到微任务Event Queue中
  6. 又遇到setTimouse也是直接分发到宏任务Event Queue中,等待执行
  7. console.log(‘10’)属于主线程任务直接执行
  8. 遇到bar()函数调用,执行构造函数内到代码,打印8,在bar函数中调用foo函数,执行foo函数到中代码,打印9
  9. 主线程中任务执行完后,就要执行分发到微任务Event Queue中代码,实行先进先出,所以依次打印3,6
  10. 微任务Event Queue中代码执行完,就执行宏任务Event Queue中代码,也是先进先出,依次打印1,7。
  • 最终结果:2,4,5,10,8,9,3,6,1,7

例题2

setTimeout(() => {
  console.log('1');
  new Promise(function (resolve, reject) {
    console.log('2');
    setTimeout(() => {
      console.log('3');
    }, 0);
    resolve();
  }).then(function () {
    console.log('4')
  })
}, 0);
console.log('5'); //5 7 10 8 1 2 4 6 3
setTimeout(() => {
  console.log('6');
}, 0);
new Promise(function (resolve, reject) {
  console.log('7');
  // reject();
  resolve();
}).then(function () {
  console.log('8')
}).catch(function () {
  console.log('9')
})
console.log('10');
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

运行结果: 5 7 10 8 1 2 4 6 3

变量提升

变量和函数怎么进行提升的?优先级是怎么样的?⭐⭐⭐

答:

  • 对所有函数声明进行提升(除了函数表达式和箭头函数),引用类型的赋值
    • 开辟堆空间
    • 存储内容
    • 将地址赋给变量
  • 对变量进行提升,只声明,不赋值,值为undefined

var let const 有什么区别⭐⭐⭐⭐⭐

答:

var

  • var声明的变量可进行变量提升,let和const不会
  • var可以重复声明
  • var在非函数作用域中定义是挂在到window上的

let

  • let声明的变量只在局部起作用
  • let防止变量污染
  • 不可在声明

const

  • 具有let的所有特征
  • 不可被改变
  • 不可改变只适用于直接地址。如果使用const声明的是对象的话,是可以修改对象内部的值的。

模块化

为什么要使用模块化?都有哪几种方式可以实现模块化,各有什么特点?⭐⭐⭐

  • 为什么要使用模块化
    • 防止命名冲突
    • 更好的分离,按需加载
    • 更好的复用性
    • 更高的维护性

exportsmodule.exports有什么区别?⭐⭐⭐

  • 导出方式不一样
    • exports.xxx='xxx'
    • module.export = {}
  • exportsmodule.exports的引用,两个指向的是用一个地址,而require能看到的只有module.exports

JS模块包装格式有哪些?⭐⭐⭐

  • commonjs
    • 同步运行,不适合前端
  • AMD
    • 异步运行
    • 异步模块定义,主要采用异步的方式加载模块,模块的加载不影响后面代码的执行。所有依赖这个模块的语句都写在一个回调函数中,模块加载完毕,再执行回调函数
  • CMD
    • 异步运行
    • seajs 规范

ES6和commonjs的区别⭐⭐⭐

Commonjs、AMD、CMD、UMD、ESM 都有什么区别

  • Commonjs

    • 是同步执行的,不适合前端,后端 nodejs 可以使用 commonjs。
    • 使用方式
    module.exports = xxx
    require('xxx')
    
    • 1
    • 2
  • AMD/CMD/UMD 适用前端 异步执行

    • AMD
    define(["a","b","c","d","e"],function(a,b,c,d,e){
      // 相当于在前面声明并初始化了要用到的所有模块
      a.dosomething()
      if(false) {
        // 即使没有用到模块 b,也会提前执行
        b.dosomething()
      }	
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • CMD
    define(function(require, exports, module){
    	var a = require("./a") //需要的时候声明
      a.dosomething()
      if(false) {
        var b = require("./b")
        b.dosomething()
      }
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • AMD 和 CMD 的差别是
      • AMD 是依赖前置(把依赖放在前面)、提前执行(即使没有用到某个模块,也会提前执行)
      • CMD依赖就近、延时执行(用到的时候在声明依赖)
  • ESM

    • 使用 export 、 export default 来导出模块,使用 import 来引入模块
  • ESM 和 commonjs 的区别主要在于

    • commonjs 是运行时加载 ;ESM 是编译时加载
    • commonjs 是同步加载模块;ESM 是异步加载模块
    • commonjs 是对值的浅拷贝;ESM 是对值的引用,而且不可修改(直接地址不可修改,类似于 const)。

require 和 import的区别?⭐⭐⭐

  • 调用时机
    • require 是运行时调用,所以其实是可以放在任何地方的
    • Import 是编译时调用,所以必须放在文件的开头
  • 使用时,
    • require 需要使用 module.exports = fs 或者exports.fs = xxx
    • import 用 export default 或 export const xx
  • 解构赋值
    • require 是赋值的过程
    • import 是解构的过程

JS其他

箭头函数和普通函数的区别?箭头函数可以当做构造函数 new 吗?⭐⭐⭐⭐⭐

  • 箭头函数是普通函数的简写,但是它不具备很多普通函数的特性
  • 第一点,this指向问题,箭头函数的this指向它定义时所在的对象,而不是调用它的对象
  • 不会进行函数提升
  • 没有arguments对象,不能使用arguments,如果要获取参数的话可以使用rest运算符
  • 没有yield属性,不能作为生成器Generator使用
  • 不能new
    • 没有自己的this,不能调用call和apply
    • 没有prototype,new关键字内部需要把新对象的_proto_指向函数的prototype

手写 ajax⭐⭐⭐⭐

var Ajax = {
   get: function (url, callback) {
     let xhr = XMLHttpRequest();
     xhr.open("get", url, false)
     xhr.onreadystatechange = function () {
       if (xhr.readyState == 4) {
         if (xhr.status == 200 || xhr.status == 304) {
           console.log(xhr.responseText);
           callback(xhr.responseText)
         }
       }
     }
     xhr.send()
   },



   post: function (url, data, callback) {
     let xhr = new XMLHttpRequest()
     // 第三个参数为是否异步执行
     xhr.open('post', url, true)
     // 添加http头,设置编码类型
     xhr.setRequestHeader("Content-type","x-www-form-urlencoded")
     xhr.onreadystatechange = function () {
       if(xhr.readyState == 4) {
         if(xhr.status == 200 || xhr.status == 304) {
           console.log(xhr.responseText);
           callback(xhr.responseText)
         }
       }
     }
     xhr.setRequestHeader('Content-type', "application/x-www-urlencoded")
     xhr.send(data)
   }
 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

什么是防抖?什么是节流?手写一个⭐⭐⭐⭐⭐

  • 防抖
    • n秒后在执行该事件,若在n秒内被重复触发,则重新计时
  • 节流
    • n秒内只运行一次,若在n秒内重复触发,只有一次生效
// ---------------------------------------------------------防抖函数
function debounce(func, delay) {
  let timeout
  return function () {
    let arg = arguments
    if (timeout) clearTimeout(timeout)
    timeout = setTimeout(() => {
      func(arg)
    }, delay);
  }
}

// ---------------------------------------------------------立即执行防抖函数
function debounce2(fn, delay) {
  let timer

  return function () {
    let args = arguments
    if (timer) clearTimeout(timer)


    let callNow = !timer
    timer = setTimeout(() => {
      timer = null
    }, delay);
    if (callNow) { fn(args) }
  }
}
// ---------------------------------------------------------立即执行防抖函数+普通防抖
function debounce3(fn, delay, immediate) {
  let timer

  return function () {
    let args = arguments
    let _this = this
    if (timer) clearTimeout(timer)

    if (immediate) {
      let callNow = !timer
      timer = setTimeout(() => {
        timer = null
      }, delay);

      if (callNow) { fn.apply(_this, args) }
    } else {
      timeout = setTimeout(() => {
        func.apply(_this, arguments)
      }, delay);
    }
  }
}

// ---------------------------------------------------------节流 ,时间戳版

function throttle(fn, wait) {

  let previous = 0
  return function () {
    let now = Date.now()
    let _this = this
    let args = arguments
    if (now - previous > wait) {
      fn.apply(_this, arguments)
      previous = now
    }
  }
}

// ---------------------------------------------------------节流 ,定时器版
function throttle2(fn, wait) {
  let timer
  return function () {
    let _this = this
    let args = arguments
    if (!timer) {
      timer = setTimeout(() => {
        timer = null
        fn.apply(_this, arguments)
      }, wait);
    }
  }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83

函数柯里化原理⭐⭐⭐⭐⭐

function add() {
  var args = Array.prototype.slice.call(arguments)

  var adder = function () {
    args.push(...arguments)
    return adder
  }

  adder.toString = function () {
    return args.reduce((prev, curr) => {
      return prev + curr
    }, 0)
  }

  return adder
}

let a = add(1, 2, 3)
let b = add(1)(2)(3)
console.log(a)
console.log(b)
console.log(add(1, 2)(3));
console.log(Function.toString)


// --------普通函数转为柯里化函数------
function createCurry(fn, args = []) {
    return function () {
        let _args = args.concat(...arguments)
        if (_args.length < fn.length) {
            return createCurry.call(this, fn, _args)
        }
        return fn.apply(this, _args)
    }
}

function add(a, b, c) {
    return a + b + c;
}

var _add = createCurry(add);

console.log(_add(1, 2, 3));
console.log(_add(1)(2, 3));
console.log(_add(1)(2)(3));
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

什么是requestAnimationFrame?⭐⭐⭐⭐

  • requestAnimationFrame请求数据帧可以用做动画执行
  • 可以自己决定什么时机调用该回调函数
  • 能保证每次频幕刷新的时候只被执行一次
  • 页面被隐藏或者最小化的时候暂停执行,返回窗口继续执行,有效节省CPU
var s = 0
function f() {
  s++
  console.log(s);
  if (s < 999) {
    window.requestAnimationFrame(f)
  }
}
window.requestAnimationFrame(f)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

js常见的设计模式⭐⭐⭐⭐

  • 单例模式、工厂模式、构造函数模式、发布订阅者模式、迭代器模式、代理模式
  • 单例模式
    • 不管创建多少个对象都只有一个实例
var Single = (function () {
  var instance = null
  function Single(name) {
    this.name = name
  }
  return function (name) {
    if (!instance) {
      instance = new Single(name)
    }
    return instance
  }
})()

var oA = new Single('hi')
var oB = new Single('hello')
console.log(oA);
console.log(oB);
console.log(oB === oA);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 工厂模式
    • 代替new创建一个对象,且这个对象想工厂制作一样,批量制作属性相同的实例对象(指向不同)
function Animal(o) {
  var instance = new Object()
  instance.name = o.name
  instance.age = o.age
  instance.getAnimal = function () {
    return "name:" + instance.name + " age:" + instance.age
  }
  return instance
}

var cat = Animal({name:"cat", age:3})
console.log(cat);

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 构造函数模式
  • 发布订阅者模式
class Watcher {
 // name模拟使用属性的地方
 constructor(name, cb) {
   this.name = name
   this.cb = cb
 }
 update() {//更新
   console.log(this.name + "更新了");
   this.cb() //做出更新回调
 }
}

class Dep {//依赖收集器
 constructor() {
   this.subs = []
 }
 addSubs(watcher) {
   this.subs.push(watcher)
 }
 notify() {//通知每一个观察者做出更新
   this.subs.forEach(w => {
     w.update()
   });
 }
}

// 假如现在用到age的有三个地方
var w1 = new Watcher("我{{age}}了", () => { console.log("更新age"); })
var w2 = new Watcher("v-model:age", () => { console.log("更新age"); })
var w3 = new Watcher("I am {{age}} years old", () => { console.log("更新age"); })

var dep = new Dep()
dep.addSubs(w1)
dep.addSubs(w2)
dep.addSubs(w3)

// 在Object.defineProperty 中的 set中运行
dep.notify()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 代理模式
  • 迭代器模式

点击300ms 延迟问题⭐⭐⭐⭐⭐

  • 设置一个 meta 标签
  • fastClick 插件
  • 自己实现
    • 实现大概思路:封装一个函数,接受两个参数,一个是目标对象,一个是点击后的回调函数; 在 ontouchstart 开始计时,在 ontouchend 中计时结束,如果超出150ms 就
//封装函数,处理延迟300ms问题
function tap(obj, callback) {
   var isMove = false;
   var startTime = 0;
   obj.addEventListener('touchstart', function () {
       startTime += Date.now();
   })
   obj.addEventListener('touchmove', function () {
       isMove = true;
   })
   obj.addEventListener('touchend', function () {
       if (!isMove && (Date.now() - startTime) < 150) {
           callback && callback();
       }
       isMove = false;
       startTime = 0;
   })
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

如何实现上传视频?⭐⭐⭐⭐

  • input type = 'file’去接收

  • 用 window.URL.createObjectURL(file)把 file 文件转换为 URL(现场的/前端转为 URL)

或者用 FormData 去接收, 把 file 文件append 进去,然后传给后端,使用返回的 URL

setTimeOut第三个参数是什么?⭐⭐⭐⭐⭐

可以作为参数传给函数,一般用于 for 循环赋值

什么是暂时性死区?⭐⭐⭐⭐⭐

暂时性死区是指,当进入一个作用域,我去使用一个变量名,而这个变量名已经存在了,但是是不可获取的,就会报错,造成暂时性死区问题;比如一个作用域下面使用了 let 定义了 x,但是在定义之前就使用了 x,就会报错;暂时性死区意味着 typeof 也不是绝对安全的操作

x = '123'; // 报错
let x = 1
---------------------
typeof y; // 报错
let y = 123
  • 1
  • 2
  • 3
  • 4
  • 5

js 遍历数组的方法⭐⭐⭐⭐⭐

reduce、map、filter、every、some、foreach.

数组可以改变原数组的方法⭐⭐⭐⭐⭐

Push、pop、shift、unshift、splice、sort、reverse

不改变的

join 变成字符

Slice,截取

concat 合并数组

foreach 和 map 有什么区别⭐⭐⭐⭐

foreach 没有返回值,一般如果用来遍历修改原数组的话可以用 foreach 方法

如何捕获浏览器关闭事件?⭐⭐

window.onbeforeunload = function (e) {
    e = e || window.event;
    // 兼容IE8和Firefox 4之前的版本
    if (e) {
        e.returnValue = '关闭提示';
    }
    // Chrome, Safari, Firefox 4+, Opera 12+ , IE 9+
    return '关闭提示';
};

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

localstorage 怎么存储图片⭐⭐⭐⭐

创建一个canvas对象,把图片保存在 canvas 中,然后 canvas 对象 toDataUrl,在把 dataurl 数据存储在 localstorage 中。

或者使用 blob 二进制流存储,canvas 对象toBlob

如何实现大文件上传?⭐⭐⭐⭐

使用 input 接受大文件,使用file.slice进行分割分块上传(制定好一个块的大小,然后进行分割),等所有块上传完毕之后,promise.all(),运行成功回调

如何实现 localstorage 定时清除⭐⭐⭐⭐

  • 自己重写一个 set 方法,内部逻辑就是添加一个现在的时间以及有效时长
  • 再重写一个 get 方法,每当 get 的时候先进行判断是否过期,如果过期就删除,并返回 null,没过期的话正常返回

web worker是干什么的?⭐⭐⭐

js是单线程的,而web worker可以多创建一个子线程,多出来的这个子线程执行代码时不会阻塞主线程。它有几个限制,

同源限制,子线程资源必须和主线程资源是同源

dom限制,子线程不能操作dom

文件限制,不能打开本机(file://)文件,只能来源于网络

通信限制,只能使用postmessage来传输信息

脚本限制,不能使用alert、confirm方法

jquery 如何实现链式调用⭐⭐⭐

let fun = {
    fun1: function() {
        console.log("fun1");
        return this;
    },
 
    fun2: function() {
        console.log("fun2");
        return this;
    },
 
    fun3: function() {
        console.log("fun3");
        return this;
    }
}
fun.fun1().fun2().fun3();

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

node 事件循环机制和浏览器事件循环机制有什么区别⭐⭐

浏览器和 Node 环境下,microtask 任务队列的执行时机不同

Node 端,microtask 在事件循环的各个阶段之间执行
浏览器端,microtask 在事件循环的 macrotask 执行完之后执行
https://zhuanlan.zhihu.com/p/54882306

讲一讲Reflect⭐⭐

顾名思义,reflect反射的意思。可以反射对象

Reflect可以提供一些方法去拦截js的操作,Reflect不是一个函数对象,所以它不可构造,Reflect内部的方法和属性都是静态的。

比如创建一个没有原型的对象,也就是说他自己不能调用任何基于Object原型链上的方法

var myObject = Object.create(null) 
// 如果想列举它的key值,只需使用Reflect的静态方法,拦截该对象,然后做出处理
Reflect.ownKeys(myObject)
  • 1
  • 2
  • 3

Object.keys和Object.getOwnPropertyNames有什么区别?⭐⭐⭐

Object.keys只列出非原型上可枚举的key值,而Object.getOwnPropertyNames列出非原型上的所有key值(Symbol除外)

如何配置rem⭐⭐⭐

 //rem适配
 (function () {
    const styleEle = document.createElement('style');
    const docWidth = document.documentElement.clientWidth;
    const rootFontSize = docWidth / 16;

    styleEle.innerHTML = 'html{font-size:' + rootFontSize + 'px!important}';
    document.head.appendChild(styleEle);

})()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

clientHeight、offsetHeight、scrollHeight有什么区别⭐⭐⭐

  • clientHeight
    • 用户可见内部高度+padding
  • offsetHeight
    • 总高度,算上滚动条
  • scrollHeight
    • 可滚动高度的+padding
  • scrollTop
    • 当前元素距离顶部的距

触底加载

  • scrollTop + clientHeight >= scrollHeight - 50px

bom和dom的区别⭐⭐⭐

bom就是window,包含windows(窗口)、navigator(浏览器)、screen(浏览器屏幕)、history(访问历史)、location(地址)等,浏览器相关的东西。bom是包含dom的。

dom是document, html相关的都在里面

倒计时用setimeout来实现还是setInterval⭐⭐⭐⭐

  • setTimeout
    • 因为假如用setInterval的话,该程序执行需要105ms,而设置的间隔为100ms,则还没运行完最后的那5毫秒就会运行下一次的函数

promise相对于async…await的优缺点⭐⭐⭐

  • promise
    • 无法取消
    • 错误无法被try…catch捕获,但是可以被catch方法捕获
  • async传染力比较强

fetch优缺点 ⭐⭐⭐⭐

  • fetch脱离了XHR,基于promise实现
  • 对某些错误不会reject,比如状态码400、500
  • fetch不支持超时timeout处理
  • fetch默认不携带cookie,需要手动配置
  • fetch没有办法监测请求进度,而xhr可以

秒传、分片传输、断点传输⭐⭐⭐⭐

  • 秒传
    • 文件上传前,服务器先对文件做MD5校验,如果服务器上有同样的文件,则返回一个新地址,如果不想秒传也可以,修改文件中的内容就可以了(改名字不行)
  • 分片传输
    • 利用Blob提供的slice方法把大文件分割为一个个小文件分别传输。全部上传完成时候由服务端进行归总整合
  • 断点传输
    • 在分片上传的基础上,分成一个个小文件之后,每个小文件上传完毕之后对其进行状态的存储(localStorage),如果中间发生网络断线或者刷新,下次可以接着上次的进度上传

e.target和e.currentTarget的区别⭐⭐⭐

e.target是点击的那个对象,e.currentTarget是绑定该事件的对象

JS性能优化的方式⭐⭐⭐⭐⭐

  • 垃圾回收
  • 闭包中的对象清楚
  • 防抖节流
  • 分批加载(setInterval,加载10000个节点)
  • 事件委托
  • 少用with
  • requestAnimationFrame的使用
  • script标签中的defer和async
  • CDN

this关键字的理解 ⭐⭐⭐⭐⭐

  • this 总是指向函数的直接调用者(而非间接调用者)
  • 如果有 new 关键字,this 指向 new 出来的那个对象
  • 在事件中,this 指向目标元素(触发这个事件的对象)
  • IE 的 attachEvent 中的 this 总是 指向全局对象 window。
  • 箭头函数中,函数体内的 this 对象,就是定义时所在作用域的对象,而不是使用时所在的作用域的对象。
function foo() { 
  console.log(this.a) 
}
var a = 1 
foo() //1   

const obj = { a: 2, foo: foo }
obj.foo() //2 

const c = new foo() //undefined
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 对于直接调用 foo 来说,不管 foo 函数被放在了什么地方,this 一定 是 window
  • 对于 obj.foo() 来说,我们只需要记住,谁调用了函数,谁就是 this, 所以在这个场景下 foo 函数中的 this 就是 obj 对象
  • 对于 new 的方式来说,this 被永远绑定在了 new 出来的对象上,不会 被任何方式改变 this

说完了以上几种情况,其实很多代码中的 this 应该就没什么问题了,下面看看箭头函数中的 this

function a() { 
	return () => { 
		return () => { 
			console.log(this) 
		}
	} 
}
a()()() //Window
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 首先箭头函数其实是没有 this 的,箭头函数中的 this 只取决包裹箭头 函数的第一个普通函数的 this。在这个例子中,因为包裹箭头函数的第 一个普通函数是 a,所以此时的 this 是 window。另外对箭头函数使用 bind 这类函数是无效的。

this 关键词指的是它所属的对象。
它拥有不同的值,具体取决于它的使用位置:

  • 在方法中,this 指的是所有者对象。
  • 单独的情况下,this 指的是全局对象。
  • 在函数中,this 指的是全局对象。
  • 在函数中,严格模式下,this 是 undefined。
  • 在事件中,this 指的是接收事件的元素。
  • 像 call() 和 apply() 这样的方法可以将 this 引用到任何对象。

ES6新特性

1. let、const

let 特点:

  • 变量不能重复声明
  • 块级作用域
  • 不存在变量提升
  • 不影响作用域链

const 特点:

  • 定义时要赋初始值
  • 一般常量使用大写(潜规则)
  • 变量不能重复声明
  • 常量值不能修改
  • 不存在变量提升
  • 块级作用域
  • 对于数组和对象的元素修改,不算做对常量的修改,不会报错

2. 解构赋值

定义:ES6允许按照一定的模式从数组和对象中提取值,对变量进行赋值,称为解构赋值。

数组解构

const arr = ['张学友', '刘德华', '黎明', '郭富城'];
let [zhang, liu, li, guo] = arr;
  • 1
  • 2

对象解构

const lin = {
	 name: '林志颖',
	 tags: ['车手', '歌手', '小旋风', '演员']
};
let {name, tags} = lin;

// 复杂解构
let wangfei = {
	 name: '王菲',
	 age: 18,
	 songs: ['红豆', '流年', '暧昧', '传奇'],
	 history: [
	 {name: '窦唯'},
	 {name: '李亚鹏'},
	 {name: '谢霆锋'}
	 ]
};
let {songs: [one, two, three], history: [first, second, third]} = wangfei;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

3. 模板字符串

ES6 引入新的声明字符串的方式 『 `` 』 ‘’ “”
语法:${ 内容 }

作用:

  • 内容中可以直接出现换行符
  • 变量拼接

4. 简化对象写法

ES6 允许在大括号里面,直接写入变量和函数,作为对象的属性和方法。

let name = '尚硅谷';
let change = function(){
    console.log('我们可以改变你!!');
}

const school = {
    name,   // name : name, 简化
    change,
    improve(){   // improve: function(){  简化如下
        console.log("我们可以提高你的技能");
    }
}

console.log(school);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

5. 箭头函数

语法: () => { }

  • 特点
    • this 是静态的. this 始终指向函数声明时所在作用域下的 this 的值
      • 注:不能通过call、applay、bind方法直接修改它的 this 指向
    • 不能作为构造实例化对象
    • 不能使用 arguments 变量
    • 没有原型属性
  • 箭头函数的简写
    • 当形参有且只有一个的时,可省略小括号
    • 当代码体只有一条语句的时候,可省略花括号, 此时 return 必须省略,而且语句的执行结果就是函数的返回值

6. 函数参数默认值

  • 形参初始值 具有默认值的参数, 一般位置要靠后(潜规则)
  • 与解构赋值结合
//ES6 允许给函数参数赋值初始值
//1. 形参初始值 具有默认值的参数, 一般位置要靠后(潜规则)
function add(a,b,c=10) {
   return a + b + c;
}
let result = add(1,2);
console.log(result);

//2. 与解构赋值结合
function connect({host="127.0.0.1", username,password, port}){
   console.log(host)
   console.log(username)
   console.log(password)
   console.log(port)
}
connect({
   host: 'atguigu.com', // 若无参数传递,将使用默认值
   username: 'root',
   password: 'root',
   port: 3306
})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

7. rest参数

ES6 引入 rest 参数,用于获取函数的实参,用来代替 arguments

// ES5 获取实参的方式
function date(){
    console.log(arguments);
}
date('白芷','阿娇','思慧');

// rest 参数
function date(...args){
    console.log(args);// 数组 filter some every map 
}
date('阿娇','柏芝','思慧');

// rest 参数必须要放到参数最后
function fn(a,b,...args){
    console.log(a);
    console.log(b);
    console.log(args);
}
fn(1,2,3,4,5,6);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

8. 扩展运算符

『 … 』 扩展运算符能将『数组』转换为逗号分隔的『参数序列』

//声明一个数组 ...
const tfboys = ['易烊千玺','王源','王俊凯'];
// ...tfboys => '易烊千玺','王源','王俊凯'

// 声明一个函数
function chunwan(){
    console.log(arguments);
}

chunwan(...tfboys);// chunwan('易烊千玺','王源','王俊凯')

// 应用
//1. 数组的合并 
const kuaizi = ['王太利','肖央'];
const fenghuang = ['曾毅','玲花'];
// const zuixuanxiaopingguo = kuaizi.concat(fenghuang);
const zuixuanxiaopingguo = [...kuaizi, ...fenghuang];
console.log(zuixuanxiaopingguo);

//2. 数组的克隆
const sanzhihua = ['E','G','M'];
const sanyecao = [...sanzhihua];//  ['E','G','M']
console.log(sanyecao);

//3. 将伪数组转为真正的数组
const divs = document.querySelectorAll('div');
const divArr = [...divs];
console.log(divArr);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

9. Symbol

ES6 引入了一种新的原始数据类型 Symbol,表示独一无二的值。它是JavaScript 语言的第七种数据类型,是一种类似于字符串的数据类型。
其中数据类型:undefined、string、symbol、object、null、number、boolean

  • 特点:
    • Symbol 的值是唯一的,用来解决命名冲突的问题
    • Symbol 值不能与其他数据进行运算
    • Symbol 定义的对象属性不能使用 for…in 循环遍 历 ,但是可以使用Reflect.ownKeys 来获取对象的所有键名
//创建Symbol
let s = Symbol();
console.log(s, typeof s);
let s2 = Symbol('尚硅谷');
let s3 = Symbol('尚硅谷');
console.log(s2 === s3); // false
//Symbol.for 创建
let s4 = Symbol.for('尚硅谷');
let s5 = Symbol.for('尚硅谷');
console.log(s4 === s5); // true

//不能与其他数据进行运算
let result = s + 100;
let result = s > 100;
let result = s + s;
注:和 Symbol()不同的是,用Symbol.for()方法创建的的 symbol 会被放入一个全局 symbol 注册表中。
Symbol.for() 并不是每次都会创建一个新的 symbol,它会首先检查给定的 key 是否已经在注册表中了。假
如是,则会直接返回上次存储的那个。否则,它会再新建一个。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

关于Symbol的更多信息@点击了解更多

10. 迭代器

11. 对ES6新增的Class的了解

事件循环机制 ⭐⭐⭐⭐⭐

知乎

封装axios的思路

map在项目中什么情况下使用

typescript

面向对象

node.js的了解

算法

数据结构

bootstrap

鉴权

怎么做一个○中间有个x关闭按钮

如果一个列表,给你好多条数据,没有分页,怎么优化

如果给你一个草图,怎么做这个页面

css隔行变色怎么实现

本文内容由网友自发贡献,转载请注明出处:https://www.wpsshop.cn/w/2023面试高手/article/detail/294627
推荐阅读
相关标签
  

闽ICP备14008679号