ES6 常见面试题总结

145 阅读7分钟

题目一:描述下列代码的执行结果

foo(typeof a);
function foo(p) {
    console.log(this);
    console.log(p);
    console.log(typeof b);
    let b = 0;
}

答案:

window
undefined
Uncaught ReferenceError: Cannot access 'b' before initialization

分析:

  • typeof 运算符被设计成安全的。即使你使用它来检查一个未声明的变量,它也不会抛出错误。JavaScript 允许你使用 typeof 来避免访问未声明的变量时出现的 ReferenceError。使用 typeof 检查一个未声明的变量时,它不会去查找该变量的实际值,而是直接返回字符串 'undefined'
  • 普通函数中的 this 指向 window 对象。(在浏览器中)
  • ES6 新增的声明变量关键字 let 以及暂时性死区的知识。let 和以前的 var 关键字不一样,无法在 let 声明变量之前访问到该变量,所以在 typeof b 的地方就会报错。

题目二:根据下面 ES6 构造函数的书写方式,要求写出 ES5的

class Example { 
  constructor(name) { 
    this.name = name;
  }
  init() { 
    const fun = () => { console.log(this.name) }
    fun(); 
  } 
}
const e = new Example('Hello');
e.init();

答案:

function Example(name) {
    'use strict';
    if (!new.target) {
         throw new TypeError('Class constructor cannot be invoked without new');
    }
    this.name = name;
 }
 
 Object.defineProperty(Example.prototype, 'init', {
    enumerable: false,
    value: function () {
         'use strict';
         if (new.target) {
             throw new TypeError('init is not a constructor');
         }
         var fun = function () {
             console.log(this.name);
         }
         fun.call(this);
    }
 })

分析:考察是否清楚 ES6 的 class 和普通构造函数的区别。它们的区别如下:

  1. ES6中的class必须通过new来调用,不能当做普通函数调用,否则报错,因此,new.target来判断调用方式;
  2. ES6的class中的所有代码均处于严格模式之下,因此,无论是构造函数本身,还是原型方法,都使用了严格模式;
  3. ES6中的原型方法是不可被枚举的,因此,定义原型方法使用了属性描述符,让其不可枚举 ;
  4. 原型上的方法不允许通过new来调用,因此,原型方法中加入了 new.target来判断调用方式;

题目三:以下函数的输出结果

const promise = new Promise((resolve, reject) => {
  console.log(1);
  resolve();
  console.log(2);
});

promise.then(() => {
  console.log(3);
});

console.log(4);

答案:

1
2
4
3

题目四:以下函数的输出结果

setTimeout(() => {
  console.log(1);
});

const promise = new Promise((resolve, reject) => {
  console.log(2);
  resolve();
});

promise.then(() => {
  console.log(3);
});

console.log(4);

输出结果:

2
4
3
1

题目五:以下输出结果

const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject();
  }, 1000);
});
const promise2 = promise1.catch(() => {
  return 2;
});

console.log('promise1', promise1);
console.log('promise2', promise2);

setTimeout(() => {
  console.log('promise1', promise1);
  console.log('promise2', promise2);
}, 2000);

答案:

promise1 Promise {<pending>}
promise2 Promise {<pending>}
promise1 Promise {<rejected>: undefined}
promise2 Promise {<fulfilled>: 2}

题目六:以下输出结果

async function m() {
  console.log(0);
  const n = await 1;
  console.log(n);
}

m();
console.log(2);

答案:

0
2
1

题目七:以下输出结果

async function m() {
  console.log(0);
  const n = await 1;
  console.log(n);
}

(async () => {
  await m();
  console.log(2);
})();

console.log(3);

答案:

0
3
1
2

题目八:以下输出结果

Promise.resolve(1).then(2).then(Promise.resolve(3)).then(console.log);

答案:

1

记住一个点,如果then传入的不是函数,那么值直接透传即可

题目九:以下输出结果

async function m1() {
  return 1;
}

async function m2() {
  const n = await m1();
  console.log(n);
  return 2;
}

async function m3() {
  const n = m2();
  console.log(n); 
  return 3;
}

m3().then((n) => {
  console.log(n);
});

m3();

console.log(4);

答案:

Promise {<pending>}
Promise {<pending>}
4
1
3
1

题目十:以下输出结果


var a;
var b = new Promise((resolve, reject) => {
  console.log('promise1');
  setTimeout(() => {
    resolve();
  }, 1000);
})
  .then(() => {
    console.log('promise2');
  })
  .then(() => {
    console.log('promise3');
  })
  .then(() => {
    console.log('promise4');
  });

a = new Promise(async (resolve, reject) => {
  console.log(a);
  await b;
  console.log(a);
  console.log('after1');
  await a;
  resolve(true);
  console.log('after2');
});

console.log('end');

答案:

promise1
undefined
end
promise2
promise3
promise4
Promise { <pending> }
after1

题目十一:以下输出结果

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

console.log('script start');

setTimeout(function () {
  console.log('setTimeout');
}, 0);

async1();

new Promise(function (resolve) {
  console.log('promise1');
  resolve();
}).then(function () {
  console.log('promise2');
});
console.log('script end');

答案:

script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout

题目十二:以下输出结果

new Promise((resolve, reject) => {
	resolve(2) 
	new Promise((resolve, reject) => {
		resolve(5)
	}).then((v) => {
		console.log(v)
	})
}).then((v) => {
	console.log(v)
})


new Promise((resolve, reject) => {
	setTimeout(() => {
		resolve(2)
		new Promise((resolve, reject) => {
      resolve(5)
    }).then((v) => {
      console.log(v)
    })
	})
}).then((v) => {
	console.log(v)
})

答案:

5
2
2
5

分析:加入微队列的时机有两个点

  1. 调用 resolve 时,会检查目前注册的thenable(即then中的回调),将它们加入到微队列;
  2. 调用then方法时,会检查当前的 promise 状态,如果为 fulfilled 则将传递的thenable加入到微队列;

在此题的第一部分,当两个resolve被调用时,均还没有运行then方法,即没有注册任何的thenable,因此,第一部分的thenable执行顺序由 then方法的调用顺序决定。

在此题的第二部分,当第一个resolve执行时,由于此时已经注册了 thenable,因此即刻将其加入到微队列,因此会先输出 2.

题目十三:手写用 ES6 proxy 如何实现 arr[-1]的访问

参考:

const proxyArray = (arr) => {
  const length = arr.length;
  return new Proxy(arr, {
    get(target, key) {
      key = +key;
      while (key < 0) {
        key += length;
      }
      return target[key];
    },
  });
};
var a = proxyArray([1, 2, 3, 4, 5, 6, 7, 8, 9]);
console.log(a[-1]); // 9

题目十四:封装一个能够统计重复的字符的函数,例如 aaabbbdddddfff 转化为 3a3b5d3f

参考:

function compression(str) {
  if (str.length == 0) {
    return 0;
  }
  var len = str.length;
  var str2 = '';
  var i = 0;
  var num = 1;
  while (i < len) {
    if (str.charAt(i) == str.charAt(i + 1)) {
      num++;
    } else {
      str2 += num;
      str2 += str.charAt(i);
      num = 1;
    }
    i++;
  }
  return str2;
}
// 测试:
console.log(compression('aaabbbdddddfff')); // 3a3b5d3f

题目十五:手写reduce

Array.prototype.myReduce = function (callback, initialValue) {
    function isArray(obj) {
      return Object.prototype.toString.call(obj) === "[object Array]";
    }
  
    function isObject(obj) {
      return Object.prototype.toString.call(obj) === "[object Object]";
    }
  
    // length为非法字符串
    if (Number.isNaN(Number(this.length))) throw new SyntaxError('Invalid or unexpected token')
  
    // this可能是非数组,因为可以通过call,apply,bind改变函数执行的this,所以我们也需要对this进行安全处理
    // 这里考虑到对象调用的情况,所以不用this.length === 0来判断
    // 如果类数组中初始索引大于索引长度,证明数组中无元素
    if (
      (!this.length && initialValue === undefined) ||
      (!isArray(this) && !isObject(this)) ||
      (isObject(this) && !this.length) ||
      (isObject(this) && Object.keys(this)[0] >= this.length)
    )
      throw new TypeError("Reduce of empty array with no initial value");
  
    if (typeof callback !== "function")
      throw new TypeError(callback + "is not a function");
  
    var res;
  
    // 对于类数组对象,遍历求值
    if (isObject(this)) {
      var keys = Object.keys(this);
      var keyIndex = 0;
      var keyLen = keys.length;
      if (initialValue === undefined) {
        initialValue = this[keys[0]];
        keyIndex = 1;
      }
      res = initialValue;
      for (; keyIndex < keyLen; keyIndex++) {
        if (keys[keyIndex] === "length") continue;
        if (keys[keyIndex] >= this.length) break; // 如果索引值大于类数组长度,直接结束
        res = callback(res, this[keys[keyIndex]], keys[keyIndex], this);
      }
    } else if (isArray(this)) {
      // 数组求值
      var currentIndex = 0;
      var len = this.length;
      if (initialValue === undefined) {
        initialValue = this[0];
        currentIndex = 1;
      }
      res = initialValue;
      for (; currentIndex < len; currentIndex++) {
        res = callback(res, this[currentIndex], currentIndex, this);
      }
    }
  
    return res;
};

题目十六:手写 flat

参考:

const checkType = (arr) => {
  return Object.prototype.toString.call(arr).slice(8, -1);
};
Array.prototype.myFlat = function (num) {
  var type = checkType(this);
  var result = [];
  if (!Object.is(type, 'Array')) {
    return;
  }
  for (var i = 0; i < this.length; i++) {
    var item = this[i];
    var cellType = checkType(item);
    if (Object.is(cellType, 'Array')) {
      num--;
      if (num < 0) {
        var newArr = result.push(item);
        return newArr;
      }
      result.push.apply(result, item.myFlat(num));
    } else {
      result.push(item);
    }
  }
  return result;
};

题目十七:手写实现浏览器端的发布订阅模式

需要实现效果:

const event = new EventEmitter();
const handle = (...payload) => console.log(pyload);
 
event.on("click", handle);
event.emit("click", 100, 200, 300, 100);
event.remove("click", handle);
event.once("dbclick", function() {
    console.log("click");
});
event.emit("dbclick", 100);

参考:

// 发布订阅模式
class EventEmitter {
  constructor() {
    // 事件对象,存放订阅的名字和事件  如:  { click: [ handle1, handle2 ]  }
    this.events = {};
  }
  // 订阅事件的方法
  on(eventName, callback) {
    if (!this.events[eventName]) {
      // 一个名字可以订阅多个事件函数
      this.events[eventName] = [callback];
    } else {
      // 存在则push到指定数组的尾部保存
      this.events[eventName].push(callback);
    }
  }
  // 触发事件的方法
  emit(eventName, ...rest) {
    // 遍历执行所有订阅的事件
    this.events[eventName] &&
      this.events[eventName].forEach(f => f.apply(this, rest));
  }
  // 移除订阅事件
  remove(eventName, callback) {
    if (this.events[eventName]) {
      this.events[eventName] = this.events[eventName].filter(
        f => f != callback
      );
    }
  }
  // 只执行一次订阅的事件,然后移除
  once(eventName, callback) {
    // 绑定的时fn, 执行的时候会触发fn函数
    const fn = (...rest) => {
      callback.apply(this, rest) // fn函数中调用原有的callback
      this.remove(eventName, fn) // 删除fn, 再次执行的时候之后执行一次
    }
    this.on(eventName, fn)
  }
}

题目十八:如何实现 a == 1 && a == 2 && a == 3 为 true?

方法一 :
const a = {
  value: 1,
  valueOf() {
    return this.value++;
  }
};
方法二:
const a = {
  value: 1,
  toString() {
    return this.value++;
  },
};

方法三:
const a = {
  value: 1,
  [Symbol.toPrimitive]() {
    return this.value++;
  },
};

题目十九:如何让(a===1 && a===2 && a === 3)的值为true?

参考:

var value = 0; //window.value
Object.defineProperty(window, 'a', {
    get: function() {
        return this.value += 1;
    }
});

题目二十:数组去重有哪些方法?

// 方法一
function unique(arr) {
   var result = {}; // 利用对象属性名的唯一性来保证不重复
   for (var i = 0; i < arr.length; i++) {
        if (!result[arr[i]]) {
            result[arr[i]] = true;
        }
   }
   return Object.keys(result); // 获取对象所有属性名的数组
}

// 方法二
function unique(arr) {
   var result = []; // 结果数组
   for (var i = 0; i < arr.length; i++) {
        if (!result.includes(arr[i])) {
            result.push(arr[i]);
        }
   }
   return result;
}

// 方法三:利用ES6的Set去重,适配范围广,效率一般,书写简单
function unique(arr) {
   return [...new Set(arr)]
}

// 方法四
function unique(arr) {
    let newlist = [];
    arr.forEach((item) => {
     // 空数组newList4第一次循环没有找到匹配的item 返回-1  执行数组添加操作
     // 如果数组在第n次循环中找到了newList4数组中item 例如:item等于6 而在newList4数组中已经有9 所以indexOf就不等于-1  不执行数组添加操作
        if (newlist.indexOf(item) === -1) {
            newlist.push(item)
        }
    })
  return newlist
}
// 方法五
function unique(arr) {
  	let list = arr;
    for (let i = 0; i < list.sort().length; i++) {
        if (list[i] == list[i + 1]) {
            list.splice(i, 1)
            i--
        }
    }
  return list
}
// 方法六

let list = [] // 需要去重的数组
function callBack(index) {
  if (index >= 1) {
      if (list[index] === list[index - 1]) {
          list.splice(index, 1)
      }
      callBack(index - 1)
  }
}
//传入排序好的数组的最大索引index
callBack(list.sort().length - 1)

未完待续...

PS:题目来源:网络or面试等。