封面图
面试只是起点,能力才是终局
声明变量 let, const ,var
var 声明变量存在变量提升的问题,容易造成全局变量污染。
let 声明变量只在当前作用域内有效,存在暂时性死区机制。
在变量未声明前对变量进行操作会报错,就是因为存在暂时性死区。只有在变量声明后才可对变量进行操作。
const 使用方法和let基本一致。但是const 保存当前变量的引用。意味着const声明一个Object类型的变量,依然可以修改该变量的属性
new 操作符执行的过程
使用new操作符会执行以下过程:
- 在内存中创建一个新对象。
- 这个新对象内部的
[[prototype]]
被赋值为构造函数的prototype属性。 - 构造函数内部的this被赋值给这个新对象。
- 执行构造函数内部的代码,给新对象添加属性。
- 如果构造函数返回非空对象,则返回该对象;否则返回创建的新对象。
Class类构造函数 和 构造函数的 区别
- 调用class类构造函数必须使用new操作符。
- 普通构造函数如果不使用new操作符,就以全局对象作为内部对象。
对象,函数,原型对象 ,prototype ,proto 和 [[Prototype]]
的关系
这几个名词的关系比较容易混淆。
基本关系是:
每个函数本质上是一个对象。每个函数都有一个prototype属性和__proto__,prototype属性的指向是原型对象。
[[Prototype]]
可以理解为浏览器内部实现的一个原型对象的原型。可以使用__proto__属性访问到[[Prototype]]
。
this, call , apply , bind
this 的指向大致分为4种:
作为对象的方法调用
。
当函数作为对象的方法被调用时,this 指向该对象
作为普通函数调用
。
当函数不作为对象的属性被调用时,也就是我们常说的普通函数方式,此时的 this 总是指 向全局对象。
构造器调用
。
当用 new 运算符调用函数时,该函数总会返回一个对象,通常情况下,构造器里的 this 就指向返回的这个对象。
Function.prototype.call 或 Function.prototype.apply 调用
。
跟普通的函数调用相比,用 Function.prototype.call 或 Function.prototype.apply 可以动态地改变传入函数的 this。
call 和 apply其实是Function的原型上的两个方法。也就是Function.prototype.call 或 Function.prototype.apply。
bind被用来修改函数内部的this指向,大部分浏览器已经内置了Function.prototype.bind。当然我们也可以手动实现一个bind
代码语言:javascript复制Fucntion.prototype.bind = function(ctx){
let self = this;
return function(){
return self.apply(ctx,arguments)
}
}
有几个地方经常用到call或者apply:
- 类型判断
const isType(type) => {
return Object.prototype.toString.call(obj) === `[object ${type}]`
}
- 类数组对象转为数组
Array.prototype.slice.call(arrLike)
- 求数组中的最值
function smallest(array){
return Math.min.apply( Math, array );
}
function largest(array){
return Math.max.apply( Math, array );
}
function smallest(){
return Math.min.apply( Math, arguments );
}
function largest(){
return Math.max.apply( Math, arguments );
}
高阶函数
高阶函数是指将函数作为参数或者返回值的函数。将函数作为参数可以将变化的部分封装起来,隔离代码中变化和不变的部分。
判断数据类型:
代码语言:javascript复制const isType = (type) => {
return (obj) => { Object.prototype.toString().call(obj) === `[object ${type} ]`}
}
节流函数
节流函数是限制函数调用频率的技术。比如 resize
,mousemove
,scroll
事件。节流函数的实现原理是:将被执行函数用定时器延时一段时间后执行。如果本次没有执行完,则忽略调用函数的请求。
let throttle = function (fn,interval) {
let timer = null
let self = this
return function (){
let args = arguments;
let firstTime = true;
if(firstTime){
fn.apply(self,args)
return firstTime = false
}
if(timer){
return
}
timmer = setTimeOut(()=>{
clearTimeout(timer)
fn.call(self,args)
},interval || 5000)
}
}
防抖函数
防抖这个技术点允许我们将多个相似的调用分成一组,或者可以理解为多个相同的事件最后只执行一次
。
let debounce = function(fn){
let timer = null
return function(){
timer = setTimeout(()=>{
fn.call(this,argumnets)
},500)
}
}
防抖的应用场景:按钮重复点击
,search输入框
,鼠标事件
。
节流和防抖的区别在于:一个是定时执行,一个是只执行一次
。
class类中的constructor 和 super
- constructor 是类的构造函数,这个是非必需的。不定义构造函数相当于构造函数为空。
- 派生类的方法可以通过super关键字引用他们的原型。
- 在类构造函数中可以使用super调用父类的构造函数。
- 从这些方面来看,我们在定义React组件的时候,props实际上是构造函数的参数。
React组件中的props是什么
我们在定义React组件的时候,props实际上是构造函数的参数。
浅复制 和 深复制
浅复制只复制对象的引用地址,并不复制对象本身,新旧对象共用一个块儿内存。深拷贝会创建一个一模一样的对象,且新旧对象并不共享内存,修改新对象不会修改元对象。
浅拷贝方法:Object.assign()
,Array.prorotype.concat()
,Array.prototype.slice()
深拷贝方法:JSON.parse()
,lodash._cloneDeep()
浏览器的事件循环模型
对象被分配在对内存中;基本类型的数据存放在栈内存;
一个Javascript运行时包含了一个待处理消息的消息队列。每个消息队列都关联着这个消息的回调函数。
在事件循环的某个时刻,运行时会从最先进入队列的消息开始处理队列中的消息。被处理的消息会移除队列,并作为输入参数来调用与之关联的函数。函数的处理会一致进行到执行栈为空为止;然后事件循环就会处理队列中的下一个消息。
异步非阻塞
异步非阻塞,我们一直在说异步非阻塞这个词。到底什么是异步非阻塞?
我觉得可以这样理解:
异步的目的是为了非阻塞。也就是不阻塞。
setTimeout, setInterval为什么是异步的?
这个问题很难从底层原理解释清楚,但是我们可以通过下面的代码了解到这个特点:
代码语言:javascript复制console.log(1)
setTimeout(()=>{console.log('set-time-out')},3000)
console.log(2)
结果如下:
打印出1 之后,并没有执行setTimeout, 而是等到2打印出来之后,才打印set-time-out。
原理就是上面提到的浏览器的事件循环机制,简单点说就是当执行栈里的执行的代码遇到异步的代码时,会将它添加到消息队列中,等到执行栈里的代码执行完成后,会处理消息队列中的代码,依次循环往复。
任务 微任务 宏任务
(macro)task,宏任务可以理解是每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行)。
浏览器为了能够使得JS内部(macro)task与DOM任务能够有序的执行,会在一个(macro)task执行结束后,在下一个(macro)task 执行开始前,对页面进行重新渲染,流程如下:
(macro)task->渲染->(macro)task->...
宏任务包含:
- script(整体代码)
- setTimeout
- setInterval
- I/O
- UI交互事件
- postMessage
- MessageChannel
- setImmediate(Node.js 环境)
microtask,微任务可以理解是在当前 task 执行结束后立即执行的任务。也就是说,在当前task任务后,下一个task之前,在渲染之前。
所以它的响应速度相比setTimeout(setTimeout是task)会更快,因为无需等渲染。也就是说,在某一个macrotask执行完后,就会将在它执行期间产生的所有microtask都执行完毕(在渲染前)。
微任务包含:
- Promise.then
- Object.observe
- MutaionObserver
- process.nextTick(Node.js 环境)
Promise基本原理
Promise的实现本质上是将回调函数封装在内部,其实现方式类似于发布订阅模式,then的时候将回调push到缓存数组中,resolve的时候遍历缓存数组执行回调函数。
依据Promise A 规范。三种状态,thenable, 执行器。逐渐完善
代码语言:javascript复制//极简的实现
class Promise {
callbacks = [];
constructor(fn) {
fn(this._resolve.bind(this));
}
then(onFulfilled) {
this.callbacks.push(onFulfilled);
}
_resolve(value) {
this.callbacks.forEach(fn => fn(value));
}
}