Promise 是异步编程的一种解决方案,简单说就是一个保存着某个未来才会结束的事件(通常是一个异步操作)结果的容器。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
它有三种状态,pending(进行中)、fulfilled(已成功)、reject(已失败)。注:resolved是指完成状态,结果可能包含fulfilled和rejected
使用Promise的语法来解决回调地狱的问题,使代码拥有可读性和可维护性。
“回调函数”:把一个函数当作参数传递,传递的是函数的定义并不会立即执行,而是在将来特定的时机再去调用,这个函数就叫做回调函数。
“回调地狱”:把函数作为参数层层嵌套请求,这样层层嵌套,人们称之为回调地狱,代码阅读性非常差。
var sayhello = function (order, callback) {
setTimeout(function () {
console.log(order);
callback();
}, 1000);
}
sayhello("first", function () {
sayhello("second", function () {
sayhello("third", function () {
console.log("end");
});
});
});
使用promise改造
ar sayhello = function (order) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
console.log(order);
//在异步操作执行完后执行 resolve() 函数
resolve();
}, 1000);
});
}
sayhello("first").then(function () {
//仍然返回一个 Promise 对象
return sayhello("second");
}).then(function () {
return sayhello("third");
}).then(function () {
console.log('end');
}).catch(function (err) {
console.log(err);
})
从表面上看,Promise只是能够简化层层回调的写法,而实质上Promise的精髓是“状态”,用维护状态、传递状态的方式来使得回调函数能够及时调用,它比传递callback 函数要简单、灵活的多。
通过Promise这种方式很好的解决了回调地狱问题,使得异步过程同步化,让代码的整体逻辑与大脑的思维逻辑一致,减少出错率。
promise
.finally(() => {
// 语句
});
// 等同于
promise
.then(
result => {
// 语句
return result;
},
error => {
// 语句
throw error;
}
);
实现:
Promise.prototype.finally = function (callback) {
let P = this.constructor;
return this.then(
value => P.resolve(callback()).then(() => value),
reason => P.resolve(callback()).then(() => { throw reason })
);
};
上面代码中,不管前面的 Promise 是fulfilled还是rejected,都会执行回调函数callback。
(详见手写promise)[https://developer.aliyun.com/article/613412]
(结合阮一峰的promise和class介绍一起看理解会更深刻一些)[https://es6.ruanyifeng.com/#docs/promise]
class Promise {
// constructor为构造方法,通过new命令生成对象实例时,自动调用该方法
// this是实例对象
constructor(executor) {
// 初始状态
this.state = 'pending'
// 成功的返回值
this.value = undefined
// 失败原因
this.reason = undefined
// 成功存放的数组
this.onResolvedCallbacks = []
// 失败存放的数组
this.onRejectCallbacks = []
const resolve = value => {
// 调用resolved后,状态需要变更
if (this.state === 'pending') {
this.state = 'fulfilled'
this.value = value
// 一旦执行resolve,调用成功数组的函数
this.onResolvedCallbacks.forEach(fn => fn())
}
}
const reject = reason => {
// 调用resolved后,状态变更为失败
if (this.state === 'pending') {
this.state = 'rejected'
this.reason = reason
this.onRejectCallbacks.forEach(fn => fn())
}
}
try {
executor(resolve, reject)
} catch (err) {
// 如果执行错误,直接把错误返回
reject(err)
}
}
// then方法的第一个参数是resolved状态的回调函数,第二个参数是rejected状态的回调函数,它们都是可选的。
then (onFulfilled, onRejected) {
// 如果onFulfilled不是函数,直接返回value
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
onRejected = typeof onRejected === 'function' ? onRejected : err => {throw err}
// 如果状态为成功,执行onFulfilled,传入成功值
let promise2 = new Promise((resolve, reject)=> {
// onFulfilled和onReject只能异步调用
if (this.state === 'fulfilled') {
setTimeout(()=> {
try{
let x = onFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch(err) {
reject(err)
}
},0)
} else if (this.state === 'rejected') {
setTimeout(()=> {
try{
let x = onFulfilled(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch(err) {
reject(err)
}
},0)
} else if (this.state === 'pending') {
this.onResolvedCallbacks.push(()=> {
setTimeout(()=> {
try{
let x = onFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch(err) {
reject(err)
}
},0)
})
this.onRejectCallbacks.push(()=> {
setTimeout(()=> {
try{
let x = onFulfilled(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch(err) {
reject(err)
}
},0)
})
}
})
return promise2
}
// Promise.prototype.catch()方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。
catch() {
}
}
function resolvePromise(promise2, x, resolve, reject) {
// x不能等于promise2,会循环饮用报错
if (x === promise2) {
return reject(new TypeError('Chaining cycle detected for promise'))
}
// 防止重复调用
let called
// 如果为普通类型直接resolve
if (x != null && (typeof x === 'object' || typeof x === 'function')) {
try {
let then = x.then
// then是函数,默认为promise
if (typeof then === 'function') {
then.call(x,y => {
if (called) return
called = true
// resolve的结果依旧是promise 那就继续解析
resolvePromise(promise2, y, resolve, reject);
}, err => {
if (called) return
called = true
reject(err)
})
} else {
resolve(x)
}
} catch (e) {
if (called) return
called = true
reject(e)
}
} else {
resolve(x)
}
}
// resolve、catch、reject、race、all方法不在promise/A+规范中,均为ES6实现的方法
Promise.resolve = function (val) {
return new Promise((resolve, reject)=> {
resolve(val)
})
}
Promise.reject = function (val) {
return new Promise((resolve, reject)=>{
reject(val)
})
}
// 第一个完成的promise函数状态传递给Promise.race的结果
Promise.race = function (promises = []) {
return new Promise((resolve, reject) => {
promises.forEach(fn => {
// 如果传入的参数为promise
if (fn && typeof fn === 'function') {
// 谁先执行完到then,使用第一个执行完成的结果
fn.then(resolve, reject) // 这里不理解可以看看前面then的实现
} else {
Promise.resolve(fn).then(resolve, reject)
}
})
})
}
Promise.all = function (promises = []) {
const promiseRes = []
// 主要需要实现两个功能,1、所有promise的状态都变成fulfilled才会变成fulfilled,其中要一个被rejected,状态就会变成reject;
// 2、返回的参数是按照传入的顺序而不是完成的时间先后
return new Promise((resolve, reject) => {
promises.forEach((fn, index) => {
// 可以加入传入参数不为promise的判断处理,见race方法的实现
fn.then(res => {
promiseRes.splice(index, 0, res)
if (promiseRes.length = promises.length) {
resolve(promiseRes)
}
}, err => {
reject(err)
})
})
})
}
理解了上面的手写promise,就可以轻易回答这个问题
异步队列:
promise 的本质是回调函数,then 方法的本质是依赖收集,它把 fulfilled 状态要执行的回调函数放在一个队列, rejected 状态要执行的回调函数放在另一个队列。待 promise 从 pending 变为 fulfilled/rejected 状态后,把相应队列的所有函数,执行一遍。
链式调用:
then方法返回的是一个新的Promise实例。所以可以采用链式写法,将返回结果作为参数,传入第二个回调函数
getJSON("/posts.json").then(function(json) {
return json.post;
}).then(function(post) {
// ...
});
async 函数是 Generator 函数的语法糖,是一种异步编程解决方案。
async函数返回一个 Promise 对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。
异步函数的语法结构更像是标准的同步函数,发明了async和await的初衷就是让异步代码的语法结构跟同步代码类似。相对promise,async的实现最简洁,最符合语义
应用场景:await能解决的问题,promise其实也可以,但是在一些简单的异步场景,await会更加简洁,更具语义化;另外promise提供了很多方法,比如all,race这些能满足更多场景的使用
区别:
1、await代码阅读性比较强,像同步代码,promise书写方式是链式的,容易造成代码多层堆叠难以维护。
2、await使用try、catch可以处理同步和异步的错误;promise使用then().catch()去处理数据和捕获异常
有三种状态,pengding、fulfilled、rejected。
优点:
(1)解决回调地狱问题 (2)更好地进行错误捕获 (3)提高代码可读性
缺点:
(1)无法取消Promise,一旦新建它就会立即执行,无法中途取消。
(2)如果不设置回调函数,promise内部抛出的错误,不会反应到外部。
(3)当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
不管promise最后的状态,在执行完then或catch指定的回调函数以后,都会执行finally方法指定的回调函数
Promise.prototype.finally = function (callback) {
let P = this.constructor;
return this.then(
value => P.resolve(callback()).then(() => value),
reason => P.resolve(callback()).then(() => { throw reason })
);
};
generator:
Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同。ES6 诞生以前,异步编程的方法大概有四种,回调函数、事件监听、发布/订阅、Promise 对象。Generator 函数将 JavaScript 异步编程带入了一个全新的阶段。
语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。
执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。
形式上,Generator 函数是一个普通函数,但是有两个特征。一是,function关键字与函数名之间有一个星号;二是,函数体内部使用yield表达式,定义不同的内部状态
遍历器对象的next方法的运行逻辑如下。
(1)遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。
(2)下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式。
(3)如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。
(4)如果该函数没有return语句,则返回的对象的value属性值为undefined。
for…of循环可以自动遍历 Generator 函数运行时生成的Iterator对象,且此时不再需要调用next方法。
async/await:
async 其实就是 Generator 函数的语法糖。将 Generator 函数的星号(*)替换成async,将yield替换成await
async函数对 Generator 函数的改进,体现在以下四点。
1)内置执行器。Generator 函数的执行必须靠执行器,await会自动执行
2) 更好的语义。比起星号和yield,语义更清楚了。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。
3)更广的适用性。await命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)
4)返回值是 Promise。async函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了
详见
事件循环分为浏览器事件循环和node.js事件循环
浏览器的事件循环分为同步任务和异步任务;所有同步任务都在主线程上执行,形成一个函数调用栈(执行栈),而异步则先放到任务队列(task queue)里,任务队列又分为宏任务(macro-task)与微任务(micro-task)。下面的整个执行过程就是事件循环
宏任务大概包括::script(整块代码)、setTimeout、setInterval、I/O、UI交互事件、setImmediate(node环境)
微任务大概包括::new promise().then(回调)、MutationObserver(html5新特新)、Object.observe(已废弃)、process.nextTick(node环境)
若同时存在promise和nextTick,则先执行nextTick
执行过程
JS 引擎去执行 JS 代码的时候会从上至下按顺序执行,先把同步任务放入执行栈中立即执行,微任务放入微任务队列,宏任务放在宏任务队列。当执行栈被清空,然后去执行所有的微任务,当所有微任务执行完毕之后。再次从宏任务开始循环执行,直到执行完毕,然后再执行所有的微任务,就这样一直循环下去。如果在执行微队列任务的过程中,又产生了微任务,那么会加入整个队列的队尾,也会在当前的周期中执行。其实浏览器执行Js代码的完整顺序应该是:同步任务 ——> 异步微任务 ——> DOM渲染页面 ——>异步宏任务
process.nextTick(),效率最高,消费资源小,但会阻塞CPU的后续调用; process.nextTick()方法可以在当前"执行栈"的尾部–>下一次Event Loop(主线程读取"任务队列")之前–>触发process指定的回调函数。也就是说,它指定的任务总是发生在所有异步任务之前,当前主线程的末尾。(nextTick虽然也会异步执行,但是不会给其他io事件执行的任何机会)
setTimeout(),精确度不高,可能有延迟执行的情况发生,且因为动用了红黑树,所以消耗资源大; setTimeout()只是将事件插入了"任务队列",必须等到当前代码(执行栈)执行完,主线程才会去执行它指定的回调函数。要是当前代码耗时很长,有可能要等很久,所以并没有办法保证,回调函数一定会在setTimeout()指定的时间执行。
setImmediate(),消耗的资源小,也不会造成阻塞,但效率也是最低的。setImmediate()是将事件插入到事件队列尾部,主线程和事件队列的函数执行完成之后立即执行setImmediate指定的回调函数,和setTimeout(fn,0)的效果差不多,但是当他们同时在同一个事件循环中时,执行顺序是不定的。
(参考)[https://blog.csdn.net/qq_42033567/article/details/108129645]
(部分题目)[https://blog.csdn.net/IT_studied/article/details/124758936]
1、
console.log('1')
setTimeout(() => {
console.log('4')
setTimeout(() => {
console.log('7')
}, 0)
Promise.resolve()
.then(() => {
console.log('6')
})
console.log('5')
}, 0)
Promise.resolve()
.then(() => {
console.log('3')
})
console.log('2')
输出 1,2,3,4,5,6,7
2、
console.log(1);
setTimeout(()=>console.log(2));
new Promise((resolve, reject)=>{
Promise.resolve(3).then((result)=>{
console.log(result);
});
resolve();
console.log(4);
}).then((result)=>{
console.log(result);
}, (error)=>{
console.log(error);
});
console.log(5);
输出:1 4 5 3 undefined 2
3、
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success')
}, 1000)
})
const promise2 = promise1.then(() => {
throw new Error('error!!!')
})
console.log('promise1', promise1)
console.log('promise2', promise2)
setTimeout(() => {
console.log('promise1', promise1)
console.log('promise2', promise2)
}, 2000)
输出:
promise1 Promise { }
promise2 Promise { }
(node:50928) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: error!!!
(node:50928) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
promise1 Promise { 'success' }
promise2 Promise {
Error: error!!!
at promise.then (...)
at }
4、
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('once')
resolve('success')
}, 1000)
})
const start = Date.now()
promise.then((res) => {
console.log(res, Date.now() - start)
})
promise.then((res) => {
console.log(res, Date.now() - start)
})
输出: (答案不唯一,promise forEach消耗的时间会有差异)
once
success 1002
success 1002
5、
Promise.resolve(1)
.then(2)
.then(Promise.resolve(3))
.then(console.log)
输出:1(.then 或者 .catch 的参数期望是函数,传入非函数则会发生值穿透。)
6、
const promise = new Promise((resolve, reject) => {
console.log(1);
setTimeout(() => {
console.log("timerStart");
resolve("success");
console.log("timerEnd");
}, 0);
console.log(2);
});
promise.then((res) => {
console.log(res);
});
console.log(4);
输出:1、2、4、timerStart、timerEnd、success
7、浏览器事件循环的异步任务为什么要分成微任务和宏任务?
为了可以插队。
在事件循环机制中,异步任务被分为宏任务和微任务两类,分别被放置到宏任务队列和微任务队列中,然后被依次执行。
宏任务通常包括以下几类:
而微任务则通常包括以下几类:
宏任务和微任务被放置在不同的队列中,宏任务的执行时机在当前事件循环的末尾,微任务的执行时机在当前宏任务执行完毕后、下一个宏任务开始执行前。
在实际使用中,由于微任务在宏任务执行完毕后立即执行,所以它们可以在 DOM 更新之前执行。因此,使用微任务可以使得 DOM 更新更加及时,提高用户体验。而宏任务的执行则相对较慢,适合执行一些相对耗时的任务,比如网络请求和计时器任务等。
8、为什么浏览器需要循环事件
1)因为js是单线程,同时只能执行一个任务,event loop可以确保JavaScript代码的执行顺序,使得代码能够按照预期的顺序进行执行,同时不会被阻塞。如果没有事件循环机制,那么当JavaScript执行时间较长时,会导致浏览器的UI线程被阻塞,用户无法操作页面,甚至会出现页面卡顿、崩溃等情况。
2)在浏览器中,渲染和事件处理都是由主线程来执行的,主线程负责执行JavaScript代码,计算布局、样式和渲染页面,并处理事件等操作。由于JavaScript是单线程的,当JavaScript执行较长时间时,页面的渲染和事件处理会被阻塞,用户体验会变得非常差。
为了解决这个问题,浏览器采用了异步的机制。事件循环就是其中一种机制。它可以使得JavaScript执行一段代码后,立即返回到主线程执行其他操作,等到异步任务执行完毕之后,再回到事件循环中执行回调函数。这样就能避免JavaScript长时间阻塞主线程,从而提高页面渲染和事件处理的性能。
1、ES6 语法用过哪些,都有哪些常用的特性
function Parent(name) {
this.name = name || 'parent';
}
function Child(name, age) {
Parent.call(this, name); //继承实例属性,第一次调用Parent()
this.age = age;
}
Child.prototype = new Parent(); //继承原型,第二次调用Parent()
Child.prototype.constructor = Child;//修正构造函数为自己本身
寄生组合式继承:所谓寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。其背后的基本思路是:不必为了指定子类型的原型而调用超类型的构造函数,我们所需要的无非就是超类型原型的一个副本而已。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。
function extend(subClass, superClass) {
var prototype = Object(superClass.prototype); // 创建对象,创建父类原型的一个副本
prototype.constructor = subClass; // 增强对象,弥补因重写原型而失去的默认的constructor 属性
subClass.prototype = prototype; // 指定对象,将新创建的对象赋值给子类的原型
}
function Parent(name) {
this.name = 'allen';
}
function Child(name) {
Parent.call(this, name); //继承第一步,继承实例属性,调用Parent()
}
extend(Child, Parent); //继承第二步,不会调用Parent()
3、箭头函数与普通函数的区别
1、js的数据类型都有哪些 ,有什么区别,数据类型常用的判断方式都有哪些,为什么基本数据类型存到栈但是引用数据类型存到堆
基础数据类型:Number、String、Boolean、Null、Undefined、Symbol
引用数据类型:Object、Array、Function
常用判断方式:
2、闭包
闭包就是能够读取其他函数内部变量的函数。可以理解成“定义在一个函数内部的函数“。当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。
特点:
3、原型链讲一下
当访问一个对象的属性时,如果该对象内部不存在这个属性,那么就会去它的__proto__(隐式原型)属性所指向的那个对象(可以理解为父对象)里找,如果父对象也不存在这个属性,则继续往父对象的__proto__属性所指向的那个对象(可以理解为爷爷对象)里找,这种通过__proto__属性来连接对象直到null的一条链即为我们所谓的原型链。
原型链详解

4、esmodule和commonjs区别是什么,还接触过其他的模块化方案么
6、设计模式
前端比较常见的是单例模式、观察者模式、代理模式。
单例模式:指保证一个类仅有一个实例,并提供一个访问它的全局访问点。常见是用于命名空间;Vuex、Redux也采纳了单例模式,两者都用一个全局的惟一Store来存储所有状态。
7、process.env.NODE_ENV是什么?说一下 Process ,以及 Require 原理?
在node中,有全局变量process表示的是当前的node进程。
process.env包含着关于系统环境的信息,但是process.env中并不存在NODE_ENV。
NODE_ENV是一个用户自定义的变量,在webpack中它的用途是判断生产环境或开发环境。
8、Object.create(null)和直接创建一个{}有什么区别
Object.create() 方法用于创建一个新对象,使用现有的对象来作为新创建对象的原型。
Object.create(null) 创建一个空对象,此对象无原型方法。
{} 其实是new Object(),具有原型方法。
10、异步加载js的方式都有哪些
defer,始终在页面渲染后(dom树生成后)再执行js
async,js加载完后立即执行,可能会阻塞渲染
按需加载
11、判断一个对象是否是循环引用对象
循环引用是指对象的地址和源的地址相同,它只会发生在Object等引用类型的数据中
// 循环引用
const a = {};
a.b = a
实现一个方法判断是否是循环引用对象,具体思路是遍历对象的值是否存在与源的地址相同的情况
function cycle(obj, parent) {
//表示调用的父级数组
var parentArr = parent || [obj];
for (var i in obj) {
if (typeof obj[i] === "object") {
//判断是否有循环引用
parentArr.forEach((pObj) => {
if (pObj === obj[i]) {
obj[i] = "[cycle]"
}
});
cycle(obj[i], [...parentArr, obj[i]])
}
}
return obj;
}
12、跨域,img标签为什么没有跨域问题
浏览器要求,在解析Ajax请求时,要求浏览器的路径与Ajax的请求的路径必须满足三个要求,则满足同源策略,可以访问服务器
协议、域名、端口号都相同才为同源,否则会跨域
如果服务器没有配置CORS,则简单跨域请求可以成功执行,会返回200状态码,但返回的内容会被浏览器拦截!
解决跨域的方法:
this.x = 9; // 在浏览器中,this 指向全局的 "window" 对象
var module = {
x: 81,
getX: function() { return this.x; }
};
module.getX(); // 81
var retrieveX = module.getX;
retrieveX();
// 返回 9 - 因为函数是在全局作用域中调用的
// 创建一个新函数,把 'this' 绑定到 module 对象
var boundGetX = retrieveX.bind(module);
boundGetX(); // 81
1、call、bind、apply使用目的上都是一样的,都是将一个构造方法当作一个普通方法调用;均传递是一个this域对象;均可传递参数。
2、不同点: call、apply直接调用,bind返回一个新函数需手动调用;apply的参数为数组形式,call、bind为单个参数
手写call:(与apply的不同仅为处理参数时, let args = arguments[1] )
Function.prototype.myCall = function(context) {
// 判断是否是undefined和null
if (typeof context === 'undefined' || context === null) {
context = window
}
// call一个参数是其改变指向的对象,后续参数作为实参传递给调用者。所以这里用[...arguments].slice(1)获取到了传递给调用者的函数参数。
let args = [...arguments].slice(1)
context.fn = this // 将调用的函数设置为参数context的方法
let result = context.fn(...args) // 调用函数
delete context.fn // 移除属性
return result // 返回结果
}
手写bind:
Function.prototype.myBind = function(context) {
if (typeof this !== 'function') {
throw new TypeError('Error')
}
let _this = this
let args = [...arguments].slice(1)
return function F() {
// 判断是否被当做构造函数使用
if (this instanceof F) {
return _this.apply(this, args.concat([...arguments]))
}
return _this.apply(context, args.concat([...arguments]))
}
}
15、Map
Map 数据结构类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。
包含以下属性和操作方法:
17、手写instanceof
instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上object instanceof constructor;object为 某个实例对象、constructor 为某个构造函数
instanceof可以用于判断复杂数据类型
function myInstanceOf(object, constructor) {
if (obj === null || type obj !== 'object' || typeof constructor !== 'function') return false
let pointer = object._proto_
while (pointer !== null){
if (pointer === constructor.prototype) return true
else pointer = pointer._proto_
}
}
18、0.1 + 0.2 为什么不等于 0.3
因为在 0.1+0.2 的计算过程中发生了两次精度丢失。第一次是在 0.1 和 0.2 转成双精度二进制浮点数时,由于二进制浮点数的小数位只能存储52位,导致小数点后第53位的数要进行为1则进1为0则舍去的操作,从而造成一次精度丢失。第二次在 0.1 和 0.2 转成二进制浮点数后,二进制浮点数相加的过程中,小数位相加导致小数位多出了一位,又要让第53位的数进行为1则进1为0则舍去的操作,又造成一次精度丢失。最终导致 0.1+0.2 不等于0.3 。
19、js 垃圾回收机制(GC)
采用的两种方式:
1)、标记清除
当变量进入执行环境时,就将这个变量标记为“进入环境”,当变量离开环境时会被标记“离开环境”,离开环境的变量内存被释放
function f1(){
//被标记已进入执行环境
var a=1
var b=2
}
f1() //执行完毕,a,b被标记离开执行环境,内存释放
2)、引用计数
跟踪记录每个值被引用的次数,当某个值的引用次数变为0时,说明没有方法在访问该值了,则可将其占用的内存收回
function f1(){
//跟踪a的引用计数
var a={} //a的引用次数 0
var b=a //a的引用次数 1
var c=a //a的引用次数 2
var b={} //a的引用次数 1
var c=[] //a的引用次数 0
}
3)、手工 --直接置空,GC下次再运行时会删除这些值
a=null
20、和=的区别
1、对于 string、number 等基础类型,== 和 === 是有区别的
a)不同类型间比较,== 之比较 “转化成同一类型后的值” 看 “值” 是否相等,=== 如果类型不同,其结果就是不等。
b)同类型比较,直接进行 “值” 比较,两者结果一样。
2、对于 Array,Object 等高级类型,== 和 === 是没有区别的
进行 “指针地址” 比较
3、基础类型与高级类型,== 和 === 是有区别的
a)对于 ,将高级转化为基础类型,进行 “值” 比较
b)因为类型不同,= 结果为 false
21、XHR 和 Fetch的区别
fetch(url).then(function(response){
return response.json();
}).then(function(jsonData){
console.log(jsonData);
}).catch(function(){
console.log('something wrong~ ╮( ̄▽ ̄)╭');
var xhr = new XHMHttpRequest();
xhr.open('GET', url);
xhr.responseType = 'json';
xhr.onload = function(){
console.log(xhr.response);
};
xhr.onerror = function(){
console.log('something wrong~ ╮( ̄▽ ̄)╭');
};
xhr.send();
22、samesite
Chrome 51 开始,浏览器的 Cookie 新增加了一个SameSite属性,用来防止 CSRF 攻击 和用户追踪(第三方恶意获取cookie),限制第三方 Cookie,从而减少安全风险。
SameSite属性可以设置三个值:Strict、Lax、None。
Set-Cookie: CookieName=CookieValue; SameSite=Strict;
SameSite属性的默认SameSite=Lax 【该操作适用于2019年2月4号谷歌发布Chrome 80稳定版之后的版本】
Set-Cookie: CookieName=CookieValue; SameSite=Lax;
23、判断类型
5 instanceof Number // false
new Number(5) instanceof Number // true
24、如何判断属性是对象实例中的属性还是原型中的属性
1)hasOwnProperty()函数用于判断只在属性存在与对象实例中的时候,返回true。
2)(属性名称 in 对象) 不管属性是原型的还是实例的,只要存在就返回ture否则返回false
!obj.hasOwnProperty(name) && (name in obj)
代码的意思是不在name不在obj的实例中存在并且那么在原型/实例中,所以当属性存在原型上的时候,函数返回true
25、手写一个单例模式
单例模式是一种常见的设计模式,它确保一个类只有一个实例,并提供一个全局访问点。
实现单例模式的关键是使用一个变量来保存实例,并通过一个公共方法来获取这个实例。在获取实例时,首先判断变量是否已经保存了实例,如果已经保存则直接返回该实例,否则创建一个新的实例,并保存到变量中。
以下是一个手写的单例模式的例子:
class Singleton {
constructor() {
// 如果已经有实例了,则返回该实例
if (Singleton.instance) {
return Singleton.instance;
}
// 如果没有实例,则创建一个实例,并保存到 Singleton.instance 变量中
Singleton.instance = this;
}
}
const s1 = new Singleton();
const s2 = new Singleton();
console.log(s1 === s2); // true
应用:
26、手写防抖、节流
// 防抖
function debounce(fn, delay) {
let timer = null;
return function() {
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
fn.call(this, arguments)
}, delay)
}
}
// 节流
function throttle(func, delay) {
let timer = null;
return function() {
if (!timer) {
timer = setTimeout(() => {
func.apply(this, arguments);
timer = null;
}, delay);
}
};
}
防抖和节流区别:
防抖是触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间。适用于可以多次触发但触发只生效最后一次的场景。
节流是高频事件触发,但在n秒内只会执行一次,如果n秒内触发多次函数,只有一次生效,节流会稀释函数的执行频率。
27、继承的几种方式
继承的六种方式
1)、ES6
class Animals {
constructor(name, age) {
this.name = name
this.age = age
}
getAge() {
console.log(this.age)
}
}
class dog extends Animals {
constructor(name, age, color) {
super(name, age)
this.color = color
}
getName() {
console.log('小' + super.name)
}
}
2)、原型链继承
缺点:只能继承父类原型上的方法和属性,不能继承父类的实例属性和方法,多个实例对引用类型的操作会被篡改。
function Animals(name) {
this.name = name
}
Animals.getAge = () => {
console.log(this.age)
}
function Dog(color) {
this.color = color
}
Dog.prototype = new Animals()
Dog.prototype.constructor = Dog;
3)、
28、实现call、apply、bind
let target = {
name: '小周',
age: 19,
say: function(school, grade) {
console.log(`${this.name}今年${this.age},在${school}读${grade}`)
}
}
let myTarget = {
name: '小刘',
age: 17
}
1、call模拟代码;
Function.prototype.myCall = function(target) {
// 如果不穿值,则默认为this指向window
let obj = target || window;
obj.fn = this;
const objArguments = [...arguments].splice(1);
const result = obj.fn(...objArguments);
delete obj.fn;
return result;
}
2、apply模拟代码
Function.prototype.myBind = function(target) {
const _this = this;
let objArguments = [];
objArguments = [...arguments].splice(1);
return function(){
_this.apply(target, objArguments)
}
}
3、bind模拟代码
Function.prototype.myBind = function(target) {
const _this = this;
let objArguments = [];
objArguments = [...arguments].splice(1);
return function(){
_this.apply(target, objArguments)
}
}
target.say.myCall(myTarget, '一中', '高二'); // 小刘今年17,在一中读高二
target.say.myApply(myTarget, ['一中', '高二']); // 小刘今年17,在一中读高二
target.say.myBind(myTarget, '一中', '高二')(); // 小刘今年17,在一中读高二