前端面试资料整理【javascript篇】

块级作用域(执行结果题)
块级作用域
阮一峰 块级作用域

var a = 1999; { console.log(a); // function a(){} a = 2020; function a() {} a = 2021; } console.log(a); // 2020

我的理解:(原理可能错误,但是便于理解,错误点在于块级不能声明函数,浏览器会有自己的支持行为,类似于函数表达式声明——参考阮一峰。声明也不要用函数声明的方式,用函数表达式的方式。)
在块级中,编译过程,函数声明变量提升,执行过程无影响。因此是 function a(){}
在块级外部,编译过程,外部有a,无影响。执行过程时,开始查找,由于 a 的查找顺序是从词法环境({})到变量环境(var),查找到最近的,因此是 2020(注意此处是执行阶段,function a(){} 的变量提升是编译阶段)
ps.函数和变量相比,会被优先提升。(我的理解:这意味着函数会替换掉变量提升)
事件冒泡、事件捕获、事件代理(事件委托)
事件冒泡:略
事件捕获:略
事件代理:利用事件冒泡,将事件绑定在父元素中
target.addEventListener(type, listener, useCapture); ,其中useCapture 为 false 时,为事件冒泡,默认是 false。
Object
常见的方法:
  • Object.defineProperty 定义的 description: { value, writable, configurable, emunable },或者 { set, get, configurable, emunable }
  • Object.createObject.keysObject.valuesObject.entriesObject.toString
  • Object.preventExtensions 组织扩展对象
作用域、作用域链和上下文
作用域是指在函数定义时,声明变量的空间。
作用域链是指在变量查找过程中,从当前上下文查找,逐层往父级,直至全局。
函数声明时,会有个 scope 属性,包含所有父级的变量。此时 VO对象,包含内部函数、变量、形参,存储在上下文中。
函数执行时,AO对象,包含内部函数、变量、形参、内部this,挂载到作用域链上,
作用域、作用域链与执行上下文栈入门了解
原形链
待补充
Promise 与异步
常见的异步请求:
原生
var request = new HttpXMLRequest() request.open('GET', url); request.responseType = 'json' request.onload = function(){} request.send()

实现Promise
待补充
// Promise// Promise.prototype.then// Promise.prototype.all// Promise.prototype.resolve// Promise.prototype.race

事件循环与事件队列
事件循环
组成:
  • 事件队列(单位是消息,消息关联着回调函数,从消息队列中弹出后会调用回调函数,形成执行帧)
  • 执行栈(单位是帧,包含函数中的变量与参数)
  • 堆(保存对象结构)
同步任务与异步任务
window.requestAnimationFrame() 既不是宏任务也不是微任务,而是在浏览器下次重绘的时候执行
闭包
两个主要的特点:
  • 通过函数阻止外部函数对内部变量的引用
  • 函数可以使用外部的变量
参考
闭包中的this
由于闭包是执行在内存中,所以 this 通常指向全局,可以通过 call 改变。
闭包的作用
  • 通过立即执行函数,模拟块级作用域,减少向全局作用域声明变量,另外由于立即执行函数在执行完后外部没有引用,那么内存会立即释放
  • 使用 var 声明时,利用立即执行函数遍历时 i 能准确获取
  • 实现面向对象编程(不是通过 new 构造)
function Person(){ var name = 'default'; return { getName : function(){ return name; }, setName : function(newName){ name = newName; } } }; var p1 = Person(); console.log(p1.getName()); // default p1.setName('wang'); console.log(p1.getName()); // wang

闭包的问题
  • 在闭包中引用 dom 会导致循环引用,无法 GC(引用计数)
// IE9 之前甚至无法销毁 dom function closure(){ var element = document.getElementById(ID); element.onclick = function(){ console.log(element.id); } // 销毁 element // element = null }

  • this 指向
  • 闭包返回局部作用域变量时,内存不会立即释放
宏任务 和 微任务
常见异步考题
es5/es6/es7/es8
待补充
class 中的 super
super 既可以当函数也可以当对象使用。
当函数时,相当于是父类的构造函数,只能用在子类的构造函数中,this 指向子类实例。
class A { constructor() { this.show(); } show(){ console.log('A 实例'); } } class B extends A { constructor() { super(); } show(){ console.log('B 实例'); } } new B() // B 实例

当对象时,在一般函数使用时,super 相当于父类原型对象,this 指向子类实例 。
class A { constructor() { this.x = 'A'; } say() { console.log(this.x) } } class B extends A { constructor() { super(); this.x = 'B' } } let b = new B(); console.log(b.say()) // B

ps. 注意该提醒
class A { constructor() {// 在构造函数上定义的属性和方法相当于定义在父类实例上的,而不是原型对象上 this.p = 2; } } class B extends A { get m() { return super.p; } } let b = new B(); console.log(b.m) // undefined// 引申题 function A(x) { this.p = x } A.prototype.p = 2 // 此时的 p 通过构造函数已经声明 new A().p // undefined

super 在静态方法 this 指向父类
常见的代码 防抖与节流
防抖是指,在一段时间内累计后触发,例如输入框输入文字时,监听 onChange。
function debounce(fn, delay) { let timer return function () { const self = this; const args = arguments; if (timer) { clearTimeout(timer); }timer = setTimeout(function(){ fn.apply(self, args); }, delay) } }let log = debounce(function(){ console.log('!')}, 5000)window.addEventListener('resize',log)

节流是指,在一段时间内多次重复触发仅执行一次,例如重复点击。
function throttle(fn, delay) {let timerreturn function () { const self = this; const args = arguments; if (timer) { return; }timer = setTimeout(function() { self.apply(fn, args) timer = null; }, delay) } }let log = throttle(function(){ console.log('!')}, 3000)window.addEventListener('click',log)

形成这种区别的原因:
节流当第一次执行是 arg 就固定了,也就是说如果用节流放到输入框 onChange 场景时,值将是第一个输入的数字。
防抖,通过不断的 clearTimeout,来更新要执行的函数,直到不触发后,等待 delay 后执行,delay 的作用是在此期间如果再次触发,则会再次 clearTimeout
手写new
// Object.create 会更新 __proto__,也就是 [[Prototype]],维持原形链 function create (proto) { if (typeof proto !== 'object' && typeof proto !== 'function' ) { throw new TypeError("原型只能是对象") } if (proto === null) { throw new TypeError("不能为空") }// function F() {} //F.prototype = proto; // return new F(); proto.constructor.prototype = proto return new proto.constructor() }function newOperator(ctor) { if (typeof ctor !== 'function') { throw '构造函数必须是方法' }newOperator.target = ctor; // es6 可以直接使用 Object.create // const instance = Object.create(ctor.prototype) const instance = create(ctor.prototype) const args = [].slice.call(arguments, 1) // 绑定 this,并执行构造函数 const r = ctor.apply(instance, args); // 如果构造函数有返回,则返回构造函数 if (r) { return r; }// 实例 return instance; }function Person (name) { this.name = name }const w = newOperator(Person, "zs")console.log(w)

手写bind
function bind(fn, obj) { const args = [].slice.call(arguments, 1); const selef = this; return function bound() { return fn.call(obj, [].slice.call(arguments, 1).concat(args)) } }const h = { name: 'zs', }function say() { console.log(this.name) }const bound = bind(say, h) bound()

Object.is Polyfill
if (!Object.is) { Object.defineProperty(Object, "is", { value: function (x, y) { if (x === y) { // 1. 如果 x === y,且均不为0时 // 2. 如果 x,y 均为 0 ,判断符号是否相等 return x !== 0 || 1 / x === 1 / y; } else { // NaN 与自己比较。 包含:Number.NaN, 0/0, NaN return x != x && y != y; } } }) }

如何实现 Array.reduce()
待补充
curry 与 compose
待补充
Object.assign()
待补充
实现字符串 repeat
// 原生repeat 'ni'.repeat(3); // 'ninini' // 实现一 String.prototype.repeatString1 = function (n) { return Array(n + 1).join(this); } console.log('ni'.repeatString1(3)); // 实现二 String.prototype.repeatString2 = function (n) { return Array(n).fill(this).join(''); } console.log('ni'.repeatString2(3));

js 模板引擎
Function('let a = 1');

其他
【前端面试资料整理【javascript篇】】其他

    推荐阅读