1、什么是闭包?闭包的应用
1、闭包:指有权另访问另一个函数作用域中变量的函数;
2、形成原因:内部函数存在外部作用域的引用就会形成闭包;
3、闭包的作用:
- 保护函数的私有变量不被外部干扰,形成不销毁的栈内存;
- 保存一些函数内的值,闭包可以实现方法和属性的私有化;
4、闭包的使用:
函数内部返回一个函数;
自执行函数;
函数作为参数;
循环赋值。
2、原型和原型链
1、原型:
原型是一个可以复制的类,是一个对象模板,原型定义了一些公用的属性和方法,利用原型创建出来的
新对象实例会共享原型的所有属性和方法;
2、原型对象:
我们创建的每一个函数都有一个prototype属性,它指向一个对象。
prototype就是通过该构造函数创建的某个实例的原型对象。
好处:所有的对象实例都可以共享它包含的属性和方法。
3、原型链:
每个对象都有一个__proto__的属性,指向该对象构造函数的原型。
它的作用就是当访问一个对象的属性时,如果该对象内部不存在这个属性,就回去它的
__proto__属性所指向的父对象去查找,直到找到终点null,这条查找的链路就是原型链。
3、继承的方式
1、原型链继承
2、借用构造函数
3、组合继承
4、原型式继承
5、寄生式继承
6、寄生组合式继承
这个继承方式式引用类型的理想继承方式:
1、它使用超类型原型的副本作为子类型的原型;
2、它的优点是只调用一次超类构造函数,避免创造多余的属性。
4、用原型实现一个new
new操作符创建了一个全新的对象:
function objectFactory(){
const obj = new Object();
const Constructor = [].shift.call(arguments);
obj.__proto__ = Constructor.prototype;
const ret = Constructor.apply(obj,arguments);
return typeof ret === 'object' ? ret:obj;
}
5、class是为了解决什么问题出现的?
class作为对象的模板引入,可以通过class关键字定义类。
它的本质是一个函数,可以看作一个语法糖,让对象原型的写法更加清晰、更像面向对象编程的语法。
特点:
1、在class中声明方法,不需要function关键字;
2、class存在暂时性死区,在声明之前不能调用;
3、class默认有constructor、static方法;
4、class构造时必须使用new关键字;
5、class通过extends关键字实现类的继承;
6、通过super关键字进行拓展父类构造器或方法;
子类使用构造器constructor的时候,必须使用super关键字,用来扩展构造器;
子类同名方法会覆盖父类同名方法,使用super关键字后则可以调用到父类的同名构造函数。
7、static关键字是类的方法,只能通过类名来调用,不能被实例对象调用,也可以被继承。
6、var\const\let的区别,还有哪个可以声明提前?
1、var:变量提升机制
在全局或者局部作用域中,使用var关键字声明的变量
,都会被提升到该作用域的最顶部。
2、let声明:
无变量提升,只在当前作用域中有效,是块级作用域;
禁止重复声明,会报错;
3、const声明
声明常量,必须初始化值,一旦定义不能修改值;
只在当前作用域有效,是块级作用域;
不能重复定义,不存在变量提升;
const定义对象,可以修改对象里的属性值,不能重写整个对象。
原因:对象是引用数据类型,它的值是同时保存在栈内存和堆内存的对象,栈区保存了对象在堆区的地址。
const声明的只是栈区内容不变,那么在栈区保存的引用数据类型的地址不可改变,对象就不能
重写,可以修改对象的属性值,不影响栈区地址的变化。
扩展:
基本数据类型:string、number、boolean、null、undefined,基本数据类型的变量是保存在
栈区中的,基本数据类型的值直接在栈内存中存储,值与值之间是独立存在的,修改一个变量不会影响其他的变量。
7、js的事件循环
因为js是单线程的,每个线程都有一个事件循环,主线程上是同步任务,异步任务会被放入异步队列中,
而异步任务又分为宏任务和微任务。
执行顺序优先级是:同步任务>微任务>宏任务
常见的宏任务(macrotasks):
整体代码script、setTimeout、setInterval、setImmediate、I/O、UI renderingnew;
常见的微任务(microtasks):
promise.then(new promise的构造函数是同步任务)、process.nextTick、mutationObserver
8、setTimeout第二个参数是0,他在什么时间执行?
setTimeout是宏任务,它的执行在主线程和微任务之后;
var fuc = [1,2,3];
for(var i in fuc){
setTimeout(function(){console.log(fuc[i])},0);
console.log(fuc[i]);
}
chrome的打印结果:
1
2
3
3个3
9、promise的方法和实现原理?
promise的由来:
在js中要处理异步操作,经历了以下的过程:
多层回调函数——>解决回调地狱的promise——>generator——>async/await;
generator是基于promise来实现的,而async/await又是generator的语法糖;
promise返回resolve、reject回调;
实现方法:then、race、all;
三种状态:pending(未完成)、fulfilled(履行)、rejected(拒绝)
// promise/a+规定的三种状态
const PENDING = 'pending';//未完成
const FULFILLED = 'fulfilled';//履行
const REJECTED = 'rejected';//拒绝
\
class MyPromise {
// 构造方法接收一个回调
constructor(executor) {
this._status = PENDING; //promise的状态
this._value = undefined; //存储then回调return的值
this._resolveQueue = []; //成功队列,resolve时触发
this._rejectQueue = []; //失败队列,reject时触发
\
// 由于resolve/reject是在executor内部被调用,因此需要使用箭头函数固定this指向,否则找不到this._resolveQueue
let _resolve = (val) => {
//把resolve执行回调的操作封装成一个函数,放进setTimeout里,以兼容executor是同步代码的情况
const run = () => {
if (this._status !== PENDING) return; // 对应规范中的"状态只能由pending到fulfilled或rejected"
this._status = FULFILLED; //状态变更
this._value = val; //存储当前value
// 使用队列来储存回调,为了实现then方法可以被同一个promise调用多次
while (this._resolveQueue.length) {
const callback = this._resolveQueue.shift();//删除队列中的第一个元素并返回
callback(val);
}
}
setTimeout(run)
}
// 实现同resolve
let _reject = (val) => {
const run = () => {
if (this._status !== PENDING) return;
this._status = REJECTED;
this._value = val;
while (this._rejectQueue.length) {
const callback = this._rejectQueue.shift();
callback(val);
}
}
setTimeout(run);
}
// new promise()时立即执行executor,并传入resolve和reject
executor(_resolve, _reject)
}
\
// then方法,接收一个成功的回调和一个失败的回调
then(resolveFn, rejectFn) {
// 根据规范,如果then的参数不是function,则我们需要忽略它,让链式调用继续往下执行
typeof resolveFn !== 'function' ? resolveFn = value => value : null;
typeof rejectFn !== 'function' ? rejectFn = reason => {
throw new Error(reason instanceof Error ? reason.message : reason);
} : null;
\
// 返回一个新的promise
return new MyPromise((resolve, reject) => {
// 把resolveFn重新包装一下,再push进resolve执行队列,这是为了获取回调的返回值进行分类讨论
const fulfilledFn = value => {
try {
// 执行第一个promise的成功回调,并获取返回值
let x = resolveFn(value);
// 如果是promise,那么等待状态变更,否则直接resolve
x instanceof MyPromise ? x.then(resolve, reject) : resolve(x);
} catch (error) {
reject(error)
}
}
\
// reject
const rejectedFn = error => {
try {
let x = rejectFn(error);
x instanceof MyPromise ? x.then(resolve, reject) : resolve(x);
} catch{
reject(error);
}
}
\
switch (this._status) {
// 当状态为pending时,把then回调push进resolve/reject执行队列,等待执行
case PENDING:
this._resolveQueue.push(fulfilledFn);
this._rejectQueue.push(rejectedFn);
break;
// 当状态已经变为resolve/reject时,直接执行then回调
case FULFILLED:
fulfilledFn(this._value);
break;
case REJECTED:
rejectedFn(this._value);
break;
}
})
}
\
// catch方法其实就是执行一下then的第二个回调
catch(rejectFn) {
return this.then(undefined, rejectFn)
}
\
// finally方法:
finally(callback) {
return this.then(
value => MyPromise.resolve(callback()).then(() => value),
reason => MyPromise.resolve(callback()).then(() => { throw reason })
)
}
\
// 静态的resolve方法
static resolve(value) {
// 根据规范,如果参数是promise实例,直接return这个实例
if (value instanceof MyPromise) return value;
return new MyPromise(resolve => resolve(value))
}
\
// 静态reject方法
static reject(reason) {
return new MyPromise((resolve, reject) => reject(reason))
}
\
// 静态的all方法
static all(promiseArr) {
let index = 0;
let result = [];
return new MyPromise((resolve, reject) => {
promiseArr.forEach((p, i) => {
// promise.resolve(p)用于处理传入值不为promise的情况
MyPromise.resolve(p).then(
val => {
index++;
result[i] = val;
if (index === promiseArr.length) {
resolve(result)
}
},
err => {
reject(err)
}
)
})
})
}
\
// 静态方法race
static race(promiseArr) {
return new MyPromise((resolve, reject) => {
// 同时执行promise,如果有一个promise的状态发生改变,就更新mypromise的状态
for (let p of promiseArr) {
MyPromise.resolve(p).then(
value => {
resolve(value)
},
err => {
reject(err)
}
)
}
})
}
}
\
10、async await为什么可以同步方式写异步?
async await实际上是对generator的封装,是一个语法糖。它可以通过yield关键字,把函数的执行流挂起,
通过next()方法可以切换到下一个状态。
那么generator函数是如何暂停执行程序的呢?
generator是通过协程来控制程序执行的,generator函数是一个生成器,执行它会返回一个迭代器,
这个迭代器同时也是一个协程。一个线程中可以有多个协程,但是同时只能有一个协程在执行。
协程的执行由程序控制,通过调用生成器的next()方法可以让该协程执行,通过yeild关键字可以
让该协程暂停,交出主线程控制权,通过return关键字可以让该协程结束。
async是通过promise+generator来实现的generator是通过线程来控制程序调度的。
在协程中执行异步任务时,先用promise封装该异步任务,如果异步任务完成,会将其结果放入微任务队列中,
然后通过yeild让出主线程执行权,继续执行主线程js,主线程执行完成之后,会去查找微任务,有任务则执行,
这时通过调用迭代器的next(result)方法,将主线程的执行权转交給该协程继续执行,并且将result赋值給
yield表达式左边的变量,从而以同步的方式实现了异步编程。
11、 $nextTick()是宏任务还是微任务?
首先来说下什么是nextTick?
在下次dom更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的dom。
原理:
使用nextTick接收传入的回调函数,将回调函数暂时存放到一个队列中,开启异步更新。为了考虑浏览器的兼容,
在异步更新时还存在一个降级的过程,会优先使用promise微任务去执行队列中的所有回调,在下一次事件循环时,
更新页面。
promise->mutationObserver->setImmediate->setTimeout
在vue的不同版本中,针对各种渲染和dom事件问题进行了多次调整,在最新的版本中为微任务。