0119面试题——Promise.all、深拷贝、继承、generator、async、new、事件委托

313 阅读5分钟

javascript

模拟实现

  • 实现Promise.all
// 要求:必须等全部都resolved或者reject才返回
// 有一个reject就走catch
// 返回数组:(包括每一个resolved/reject的内容)
// 返回的数组结果对应的顺序与传入的promise顺序一致
function promiseAll(promises) {
    return new Promise((resolve, reject) => {
        let res = []
        let count = 0
        for (let i=0; i<promises.length; i++) {
            promises[i].then(r => {
                res[i] = r
                count++
                if (count === promises.length) {
                    resolve(res)
                }
            }).catch(err => {
            	res[i] = err
                count++
                if (count === promises.length) {
                    reject(res)
                }
            })
                
        }
   })
}
  • 简单实现一个深拷贝
// 对象只考虑普通对象与数组

// 有兴趣自己探究一 如下特殊场景如何满足
// 特殊场景 循环引用/Date/Regex/Symbol/函数

/*
  忽略值为 undefined、symbol、RegExp、函数 的键
  会将Date对象转为时间字符串,即不能正确处理Date
  原型链上的属性无法拷贝
  不能处理循环引用的对象
*/
function deepClone(obj){
	return JSON.parse(JSON.stringify(obj))
}

/*
  不能处理 函数、symbol
  可以处理 undefined、Regex与循环引用
*/
function deepClone(obj) {
    return new Promise(resolve => {
        let { port1, port2 } = new MessageChannel()
        port2.onmessage = e => {
            resolve(e.data)
        }
        port1.postMessage(obj)
    })
}


function deepClone(obj) {
    let objCpy = {}
    for (let k in obj) {
        if (typeof obj[k] === 'function') {
            objCpy[k] = obj[k].bind()
        } else if (typeof obj[k] !== 'object' || obj[k] === null) {
            objCpy[k] = obj[k]
        } else if (obj[k] instanceof Array) {
            let arr = deepClone(obj[k])
            arr.length = obj[k].length
            objCpy[k] = Array.from(obj[k])
        } else {
            objCpy[k] = deepClone(obj[k])
        }
    }
    return objCpy
}
  • ES5实现函数的继承
// 原型继承
// 让父类的实例成为子类的原型
let person = {
	name: 'xm',
    age: 18,
    fav: ['play', 'study', 'movies']
}

// 让person作为原型创建一个新对象
let p = Object.create(person)
// 缺点是共用原型上的引用类型容易造成混乱,尝试修改原型上的值时会在当前对象上创建一个新的值



// =============================================
// 组合继承 = 原型继承 + 构造函数继承
// 父类的属性跟方法
function Animal(name) {
    this.name = name
}
Animal.prototype.eat = function () {
    console.log('Animal eat')
}

// 子类的属性跟方法
function Fish(name, age) {
    // 继承父类的实例属性
    Animal.apply(this, arguments)
    this.age = age
}
// 通过父类实例间接继承父类原型
Fish.prototype = new Animal()	// 缺点是这里创建的对象只用了他原型上的属性而本身的属性被隐藏了
// 设置正确的构造关系
Fish.prototype.constructor = Fish
// 子类的特有方法
Fish.prototype.swim = function () {
	console.log('Fish swim')
}

fishc = new Fish("甲鱼", 18)
fishc.eat()	// Animal eat
fishc.swim()	// Fish swim

// =============================================
// 寄生式组合继承(ES6的CLASS语法糖采用的是这种继承方式)
// 父类的属性跟方法
function Animal(name) {
    this.name = name
}
Animal.prototype.eat = function () {
    console.log('Animal eat')
}

// 子类的属性跟方法
function Fish(name, age) {
    // 继承父类的实例属性
    Animal.apply(this, arguments)
    this.age = age
}
// 以父类原型创建新对象,再作为子类原型,相比组合继承的好处是,不会有多余的父类实例的属性
Fish.prototype = Object.create(Animal.prototype, {
    constructor: {	// 设置正确的构造关系
        value: Fish,
        enumerable: false, // 不可枚举
        writable: true,
        configurable: true
    })


// 子类的特有方法
Fish.prototype.swim = function () {
	console.log('Fish swim')
}

fishc = new Fish("甲鱼", 18)
fishc.eat()	// Animal eat
fishc.swim()	// Fish swim

理论

  • 几种常见for循环的区别与不足之处 (for,for of,for in ,foreach)
    • for: 常规循环,需要使用计次变量
    • for of: 遍历对象的value,需要被遍历对象部署iterator接口
    • for in: 遍历对象的key,包括在原型链上添加的属性
    • forEach: 只能遍历数组的value
  • 什么是深拷贝,什么是浅拷贝?
    • 深拷贝:层层拷贝对象的真实值,而不是拷贝引用,修改拷贝后的对象不影响原对象
    • 浅拷贝:只拷贝了对象的一层属性,如果属性为引用类型,则修改拷贝的对象后会影响原对象
  • generator与async/await有什么关系
    • generator
      • 生成器,声明时使用 function*,执行时返回一个生成器对象
      • 生成器对象每次调用 next 方法会返回 yield 抛出的数据
      • 调用 next 方法传入的参数可以被 yield 返回
    • async/await
      • 是 ES7 引入的,为了更方便的书写异步代码
      • 是通过使用generator 跟 promise实现的
    async function fn(args) {
    // ...
    }
    
    // 等同于
    
    function fn(args) {
      return spawn(function* () {
        // ...
      });
    }
    
    function spawn(genF) {
      return new Promise(function(resolve, reject) { // 返回一个 Promise 对象
        const gen = genF();
        function step(nextF) {
          let next;
          try {
            next = nextF();
          } catch(e) {
            return reject(e); // 执行 fn 中的代码,如果出错直接 reject 返回的 Promise
          }
          if(next.done) { // 如果 fn 执行完成则 resolve fn return 的值
            return resolve(next.value);
          }
          Promise.resolve(next.value).then(function(v) { 
            // 将 yield 返回的值变成 Promise 并执行它的 then 方法
            step(function() { return gen.next(v); });
            // 当 yield 后面的 Promise 执行成功完成时则继续执行 fn 函数
            // 并将它产生的值传入 fn 函数
          }, function(e) {
            step(function() { return gen.throw(e); });
            // 如果出现错误则将错误传入 fn 内部。
            // 如果内部没有捕获则被本函数上面的 try-catch 捕获
          });
        }
        step(function() { return gen.next(); });
      });
    }
    
  • 如何实现generator的自动调用
    function run(gen) {
      var g = gen();
      function next(data) {
          // 需要 yield 返回一个 Promise 对象
          var res = g.next(data);
          if(res.done) return res.value;
          res.value.then(function (data) {
              next(data);
          });
      }
      next();
    }
    
  • new一个对象做了什么(讲述一下new的原理)
    • 创建一个空对象,作为将要返回的对象实例。
    • 将这个空对象的原型,指向构造函数的prototype属性。
    • 将这个空对象赋值给函数内部的this关键字。
    • 开始执行构造函数内部的代码。
    • 如果构造函数内部有return语句,而且return后面跟着一个对象,new命令会返回return语句指定的对象;否则,就会不管return语句,返回this对象。

事件相关

  • 什么是事件委托
    • 利用事件冒泡机制,将对事件的处理函数绑定在事件发生元素的祖先元素上统一进行处理
  • 事件的回调函数 e.target与.currentTarget分别指向谁
    • e.target 是事件发生的实际元素
    • e.currentTarget 是当前在处理事件的函数所绑定的元素