题目一:描述下列代码的执行结果
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 和普通构造函数的区别。它们的区别如下:
- ES6中的class必须通过new来调用,不能当做普通函数调用,否则报错,因此,new.target来判断调用方式;
- ES6的class中的所有代码均处于严格模式之下,因此,无论是构造函数本身,还是原型方法,都使用了严格模式;
- ES6中的原型方法是不可被枚举的,因此,定义原型方法使用了属性描述符,让其不可枚举 ;
- 原型上的方法不允许通过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
分析:加入微队列的时机有两个点
- 调用
resolve时,会检查目前注册的thenable(即then中的回调),将它们加入到微队列; - 调用
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面试等。