《前端面试总结点线面》系列是为了收拢杂而乱的前端领域知识,由点及线,再涉及面,目的是为帮助广大前端同学复习巩固或查漏补缺或增删改查,为了更好的建立前端领域知识体系,为了更好的为前端面试做好准备,从而做一个合格、进步的前端开发工程师。
EventLoop 是一个程序结构,用于等待和发送消息和事件。 程序中设置两个线程,一个负责程序本身的运行,叫做主线程;另一个负责主线程与其他线程(主要是 IO 操作)的通信,称为 EventLoop 线程(消息线程)。 主线程开始运行,每当遇到 IO 操作,主线程就让 EventLoop 线程通知相应的 IO 程序,主线程接着往后运行,当 IO 完成之后,EventLoop 线程再把结果返回主线程。主线程调用事先设定的回调函数,完成任务。
任务队列
Event Loop
主线程在运行的时候,会产生堆(heap)和栈(stack),栈中的代码调用各种外部 API,它们在任务队列中加入各种事件(click,load,done)。只要栈中的代码执行(同步任务)完毕,主线程就会去读取任务队列(异步任务),依次执行那些事件对应的回调函数。
宏任务 macrotask
setTimeout, setInterval, setImmediate, I/O, callabck, UI 渲染, MessageChannel
优先级: 主代码块 > setImmediate > postMessage > setTimeout/Interval
微任务 microtask
process.nextTick,Promise, MutationObserver, Async
优先级: process.nextTick > Promise > MutationObserver
微任务意义:
减少更新时的渲染次数因为根据 HTML 标准,会在宏任务执行结束之后,在下一个宏任务开始执行之前,UI 都会重新渲染。如果在 microtask 中就完成数据更新,当 macro-task 结束就可以得到最新的 UI 了。如果新建一个 macro-task 来做数据更新的话,那么渲染会执行两次
执行顺序
示例 1:
示例 2:
use strict
严格模式clearTimeout
/ clearInterval
/ removeEventListener
WeakMap
/ WeakSet
弱引用执行上下文
执行栈:用于存储在代码执行期间创建的所有上下文,LIFO(后进先出)
执行上下文创建
GlobalExectionContext = { ThisBinding:, LexicalEnvironment: { EnvironmentRecord: { Type: "Object", // 标识符绑定在这里 a: < uninitialized >, b: < uninitialized >, multiply: < func > } outer: }, VariableEnvironment: { EnvironmentRecord: { Type: "Object", // 标识符绑定在这里 c: undefined, } outer: } } FunctionExectionContext = { ThisBinding: , LexicalEnvironment: { EnvironmentRecord: { Type: "Declarative", // 标识符绑定在这里 Arguments: {0: 20, 1: 30, length: 2}, }, outer: }, VariableEnvironment: { EnvironmentRecord: { Type: "Declarative", // 标识符绑定在这里 g: undefined }, outer: } } 复制代码
闭包
闭包是指有权访问另一个函数作用域中的变量的函数
作用域链
每个函数都有执行上下文环境,当代码在这个环境中执行时,会创建变量对象的作用域链,作用域链是一个对象列表或对象链,它保证了变量对象的有序访问。
this 绑定规则
use strict
[[Prototype]]
call apply bind new
call
Function.prototype.myCall = function(context) { context = context ? Object(context) : window context.fn = this let args = [...arguments].slice(1) let result = context.fn(...args) delete context.fn return result } 复制代码
apply
Function.prototype.myApply = function(context) { context = context ? Object(context) : window context.fn = this let result if(arguments[1]) { result = context.fn(...arguments[1]) }else { result = context.fn() } delete context.fn return result } 复制代码
bind
Function.prototype.myBind = function(context) { const self = this const args = Array.prototype.slice.call(arguments, 1) if(typeof self !== 'function') { throw new TypeError('bind should be function') } return function F() { if(this instanceof F) { return new self(...args, ...arguments) } return self.apply(context, [...args, ...arguments]) } /** return function () { const bindArgs = Array.prototype.slice.call(arguments) return self.apply(context, args.concat(bindArgs)) } */ } 复制代码
new
function myNew() { // 获取构造函数,同时删除arguments第一个参数 const Con = [].shift.call(arguments) // 创建一个空的对象并连接到原型,obj可以访问构造函数原型中的属性 // Object.setPrototypeOf(obj, Con.prototype) // obj.__proto__ = Con.prototype const obj = Object.create(Con.prototype) // 绑定this实现继承,obj可以访问到构造函数中的属性 const ret = Con.apply(obj, arguments) // 优先返回构造函数返回的对象 return ret instanceof Object ? ret : obj } 复制代码
function _new(fn, ...args) { // 创建一个新对象 const obj = Object.create(fn.prototype) // this指向新创建的对象 // 新创建的对象__proto__指向构造函数的原型 // obj.__proto__ = fn.prototype const ret = fn.apply(obj, args) return ret instanceof Object ? ret : obj } 复制代码
curry
function curryAdd(a) { function sum(b) { a = a + b return sum } sum.toString = function() { return a } return sum } // curryAdd(1)(2)(3)(4) // 10 复制代码
const curry = (fn) => (judge = (...args) => args.length >= fn.length ? fn(...args) : (...arg) => fn(...args, ...arg)); const test = curry(function (a, b, c) { console.log(a + b + c); }); test(1, 2, 3); test(1, 2)(3); 复制代码
节流
某个函数在一定时间间隔内只执行一次,在这个时间间隔内无视后来产生的函数调用请求,也不会延长时间间隔。 适用场景: 1.不断点击事件 2.监听滚动事件
function throttle(fn, wait = 50) { let previous = 0 return function(...args) { let now = +new Date() if(now - previous > 50) { previous = now fn.apply(this, args) } } } const betterFn = throttle(() => console.log('fn excuted'), 1000) // 时间间隔大于1000ms才会执行 setInterval(betterFn, 10) // 每隔10ms调用betterFn 复制代码
防抖
debounce 是指某个函数在某段时间内,无论触发多少次回调,都只执行最后一次。debounce 强调函数自上次被调用之后经过一定时间才会再次调用。 适用场景: 1. Search 联想搜索 2. window resize
function debounce(fn, wait = 50, immediate = false) { let timer = null let result const debounced = function(...args) { if(timer) clearTimeout(timer) if(immediate && !timer) { // 首次执行 result = fn.apply(this, args) } timer = setTimeout(() => { result = fn.apply(this, args) }, wait) } debounced.cancel = function(){ clearTimeout(timer) timer = null } return debounced } const betterFn = debounce(() => console.log('fn excuted'), 1000, true) // 第一次触发scroll执行一次,后续只有在停止滚动1秒后才执行函数 window.addEventListener('scroll', betterFn) 复制代码
加强版节流
wait 时间内,函数触发就重新设置定时器,wait 时间到了就立即执行。
function throttlePlus(fn, wait = 50) { let previous = 0 let timer = null return function(...args) { let now = +new Date() if(now - previous < wait) { if(timer) clearTimeout(timer) timer = setTimeout(() => { previous = now fn.apply(this, args) }, wait) } else { previous = now fn.apply(this, args) } } } 复制代码
深浅拷贝
浅拷贝
创建一个新对象,这个新对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型值,拷贝的就是基本类型的值。如果是引用类型,拷贝的就是内存地址。
Object.assign
Object.assign
用于将所有可枚举属性从一个或多个源对象复制到目标对象,并且返回这个目标对象。const target = Object.assign({}, obj1, obj2)
Object.defineProperty(Object, 'myAssign', { value: function(target) { 'use strict' if(target == null) { throw new Error('Cannot convert undefined or null to object') } const to = Object(target) for (let index=1,ilen=arguments.length;i
spread 扩展符
[...[arr]]
{...{obj}}
Array.prototype.slice
concat
深拷贝
深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存,当对象和它所引用的对象一起被拷贝时就是深拷贝。
JSON.parse(JSON.stringify(obj))
undefined
symbol
new Date
function isObject(obj) { return typeof obj === 'object' && obj != null } 复制代码
function cloneDeep(source, hash = new WeakMap()) { if (!isObject(source)) return source if(hash.has(source)) return hash.get(source) let target = Array.isArray(source) ? [] : {} hash.set(source, target) Reflect.ownKeys(source).forEach(key => { if(isObject(key)) { target[key]=cloneDeep(source[key], hash) } else { target[key]=source[key] } }) return target } 复制代码
function Person(){} // contsruct const p = new Person() // instance const child = Object.create(p) // child.__proto__ === p Person.prototype.contructor === Person // 构造函数原型的constructor指向构造函数 p.__proto__ === Person.prototype // 实例的__proto__指向构造函数的原型 p.constructor === Person // 实例的constructor指向构造函数,但实例上并没有构造函数 复制代码
原型链
对象拥有一个原型( prototype
)对象,通过 __proto__
( [[Prototype]]
)指针指向这个原型对象,并从中继承属性和方法,同时这个原型对象也可能 :u6709:️ 它的原型对象,这样一层一层,最终指向 null,这就是原型链。
p.__proto__ === Person.prototype p.__proto__.__proto__ === Object.prototype p.__proto__.__proto__.__proto__ === null 复制代码
// Object instanceof Function 即 Object.__proto__ === Function.prototype // true // Function instanceof Object 即 Function.__proto__.__proto__ === Object.prototype // true // Object instanceof Object 即 Object.__proto__.__proto__ === Object.prototype // true // Function instanceof Function 即 Function.__proto__ === Function.prototype // true 复制代码
__proto__
,一层一层连接到 null。instanceof
的原理是一层一层查找 __proto__
,如果和 constructor.prototype
相等则返回 true。SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
缺陷: 多个实例对引用类型的操作会被篡改。function SubType(){ SuperType.call(this) } 复制代码缺陷:
function SubType(name, age){ SuperType.call(this) } SubType.prototype = new SuperType() SubType.prototype.constructor = SubType 复制代码缺陷:
function object(obj) { function F() {} F.prototype = obj return new F() } 复制代码
const obj2 = Object.create(obj)
缺陷:
function objectPlus(original) { const clone = object(original) clone.someFunc = function() {} return clone } 复制代码缺陷(同原型式继承)
function inherit(subType, superType) { const prototype = Object.create(superType) prototype.constructor = subType subType.prototype = prototype } ... function SubType(name, age) { SuperType.call(this) } inherit(SubType, SuperType) 复制代码
super()
调用了父类的构造函数。 本质上先创建父类的实例对象 this,然后在用子类的构造函数修改 this。并不是说在调用 super 之前子类没有 this,而是 ES6 规定在调用 super 之前在子类构造函数中不能使用 this。模块化主要用来抽离公共代码,隔离作用域,避免变量冲突等。
define(module, [dep1, dep2], callback)
null 和 undefined 区别
Object 对象属性方法
/** * hasOwnProperty --- instance(的可枚举属性) * for ... in --- both instance & prototype(的可枚举属性) * Object.keys --- instance(的可枚举属性,不含symbol类型) * Object.getOwnPropertyNames --- instance(的所有非Symbol属性,但包括不可枚举属性) * Object.getOwnPropertySymbols --- instance(的所有Symbol属性) * Reflect.ownKeys --- instance(的所有属性) */ 复制代码
Set Map WeakSet WeakMap
ES5 继承与 ES6 class 继承区别
[[construct]]
Object.prototype.toString.call
, instanceof
, Array.isArray
Object.prototype.toString.call(type)
精确判断 type 类型 Object.prototype.toString.call(1) Object.prototype.toString.call(null) Object.prototype.toString.call(undefined) Object.prototype.toString.call({})
instanceof
的内部机制是判断对象的原型链上是否能找到类型的 prototype [] instanceof Array [] instanceof Object 'a' instanceof String
Array.isArray
用于判断对象是否为数组,可检测 iframestypeof
只能检测基本数据类型或者只是 object(null、Array、Function、Object)。箭头函数 :vs: 普通函数
for :vs: forEach
如何处理中文输入
模拟 promise.finally
Promise.prototype.finally = function(callback) { const p = this.constructor return this.then( value => p.resolve(callback()).then(() => value), reason => p.resolve(callback()).then(() => throw reason) ) } 复制代码
模拟 promise.race
Promise.prototype._race = promises => new Promise((resolve, reject) => { promises.forEach(item => { Promise.resolve(item).then(resolve, reject) }) }) 复制代码
实现 Promise
const thenPromise = (promise2, x, resolve, reject) => { if (x === promise2) return reject(new TypeError("Chaning cycle for promise")); if (x !== null && (typeof x === "object" || typeof x === "function")) { try { let then = x.then; if (typeof then === "function") { then.call( x, (y) => { thenPromise(promise2, y, resolve, reject); }, (err) => { reject(err); } ); } else { resolve(x); } } catch (e) { reject(e); } } else { resolve(x); } }; class MyPromise { static PENDING = "PENGDING"; static FULFILLED = "FULFILLED"; static REJECTED = "REJECTED"; constructor(fn) { this.status = MyPromise.PENDING; this.value = null; this.reason = null; this.resolvedQueue = []; this.rejectedQueue = []; if (typeof fn !== "function") { throw new Error("Promise resolver should be a function"); } const resolve = (value) => { if (this.status === MyPromise.PENDING) { this.value = value; this.status = MyPromise.FULFILLED; this.resolvedQueue.forEach((cb) => cb(this.value)); } }; const reject = (reason) => { if (this.status === MyPromise.PENDING) { this.reason = reason; this.status = MyPromise.REJECTED; this.rejectedQueue.forEach((cb) => cb(this.reason)); } }; try { fn(resolve, reject); } catch (e) { reject(e); } } // then then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (value) => value; onRejected = typeof onRejected === "function" ? onRejected : (reason) => { throw reason; }; const promise2 = new MyPromise((resolve, reject) => { if (this.status === MyPromise.FULFILLED) { setTimeout(() => { try { let x = onFulfilled && onFulfilled(this.value); thenPromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }); } if (this.status === MyPromise.REJECTED) { setTimeout(() => { try { let x = onRejected(this.reason); thenPromise(promise2, x, resolve, reject); } catch (error) { reject(error); } }); } if (this.status === MyPromise.PENDING) { this.resolvedQueue.push(() => { setTimeout(() => { try { let x = onFulfilled && onFulfilled(this.value); thenPromise(promise2, x, resolve, reject); } catch (error) { reject(error); } }); }); this.rejectedQueue.push(() => { setTimeout(() => { try { let x = onRejected(this.reason); thenPromise(promise2, x, resolve, reject); } catch (error) { reject(error); } }); }); } }); return promise2; } catch(onRejected) { return this.then(null, onRejected); } finally(fn) { return this.then(fn, fn); } resolve = (val) => { return new MyPromise((resolve, reject) => { resolve(val); }); }; reject = (reason) => { return new MyPromise((resolve, reject) => { reject(reason); }); }; static all = (promises) => { return new MyPromise( (resolve, reject) => { const result = []; let count = 0; const len = promises.length; for (let i = 0; i < len; i++) { promises[i].then((data) => { result[i] = data; if (++count === len) { resolve(result); } }); } }, (error) => { reject(error); } ); }; static race = (promises) => { return new MyPromise((resolve, reject) => { const len = promises.length; for (let i = 0; i < len; i++) { promises[i].then(resolve, reject); } }); }; static allSettled = (promises) => { return new MyPromise((resolve, reject) => { const result = []; let count = 0; const len = promises.length; for (let i = 0; i < len; i++) { promises[i].finally((res) => { result[i] = res; if (++count === len) { resolve(result); } }); } }); }; } 复制代码