常見面試題總結(jié)(一)
什么是原型秦踪?
- 原型分為兩種:構(gòu)造函數(shù)原型枪眉,稱為prototype狸驳;實(shí)例(對(duì)象)原型稱為proto
原型之間的關(guān)系?
- 由構(gòu)造函數(shù)創(chuàng)建出來的實(shí)例對(duì)象上役耕,proto屬性指向了構(gòu)造函數(shù)的prototype屬性
構(gòu)造函數(shù)和構(gòu)造器是什么采转?
- js中,默認(rèn)每一個(gè)(正常定義的)函數(shù)瞬痘,都是構(gòu)造器故慈,也稱為類,那么構(gòu)造函數(shù)上的原型鏈上有
? Function() { [native code] }
這個(gè)屬性框全,所以可以通過new 方法創(chuàng)建實(shí)例察绷,構(gòu)造函數(shù)原型prototype上有一個(gè)constructor構(gòu)造器屬性,指向了構(gòu)造函數(shù)自身 - 能夠稱為構(gòu)造器的關(guān)鍵在于擁有--->prototype屬性(實(shí)例沒有)
function Fa() {}
Fa.constructor
? Function() { [native code] }
- 被new出來的實(shí)例津辩,由于不是一個(gè)構(gòu)造器拆撼,所以無法通過new創(chuàng)建新的實(shí)例--->沒有prototype屬性
const son = new Fa()
son.constructor
// ? Fa() {}
const subSon = new son()
// VM5278:1 Uncaught TypeError: son is not a constructor
at <anonymous>:1:16
- 一張圖描述清楚構(gòu)造器想關(guān)
- image.png
- 有些人可能會(huì)把
__proto__
和prototype
搞混淆。從翻譯的角度來說喘沿,它們都可以叫原型闸度,但是其實(shí)是完全不同的兩個(gè)東西。
__proto__
存在于所有的對(duì)象上蚜印,prototype
存在于所有的函數(shù)上筋岛,他倆的關(guān)系就是:函數(shù)的 prototype
是所有使用 new 這個(gè)函數(shù)構(gòu)造的實(shí)例的 __proto__
。函數(shù)也是對(duì)象晒哄,所以函數(shù)同時(shí)有 __proto__
和prototype
。
作者:余騰靖
鏈接:https://juejin.im/post/5e2ff7dce51d4558021a1a4d
來源:掘金
著作權(quán)歸作者所有肪获。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán)寝凌,非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。
原型鏈?zhǔn)鞘裁矗?/h1>
- 實(shí)例擁有的實(shí)例原型對(duì)象proto屬性孝赫,指向了構(gòu)造函數(shù)原型對(duì)象prototype屬性较木,而構(gòu)造函數(shù)原型對(duì)象prototype上的proto屬性也指向了父構(gòu)造函數(shù)的原型prototype,以此類推青柄,通過proto建立起來的指向關(guān)系就被稱為原型鏈
原型鏈的作用伐债?
- 實(shí)例訪問一個(gè)屬性的時(shí)候预侯,先在自身可遍歷屬性上尋找,再到原型鏈上逐層向上尋找峰锁,如果找不到會(huì)返回undefined
- stu.proto.proto.proto萎馅,直到遇到null,則返回undefined
-
image.png
- 作用:將類的方法定義在原型上虹蒋,通過這種方式糜芳,所有的實(shí)例都可以訪問到這個(gè)方法,并且這個(gè)方法只需要占用一份內(nèi)存魄衅,節(jié)省內(nèi)存峭竣,this 的指向還能正確指向類的實(shí)例。
- 注意:這種方法定義的屬性晃虫,由于不是自身屬性皆撩,所以無法通過
Object.keys()
枚舉
function Engineer(workingYears) {
this.workingYears = workingYears;
}
// 不能使用箭頭函數(shù),箭頭函數(shù)的 this 在聲明的時(shí)候就根據(jù)上下文確定了
Engineer.prototype.built = function() {
// this 這里就是執(zhí)行函數(shù)調(diào)用者
console.log(`我已經(jīng)工作 ${this.workingYears} 年了, 我的工作是擰螺絲...`);
};
const engineer = new Engineer(5);
// this 會(huì)正確指向?qū)嵗芤?this.workingYears 是 5
engineer.built(); // => 我已經(jīng)工作 5 年了, 我的工作是擰螺絲...
console.log(Object.keys(engineer)); // => [ 'workingYears' ]
- 優(yōu)點(diǎn):通過這種方式扛吞,所有的實(shí)例都可以訪問到這個(gè)方法,并且這個(gè)方法只需要占用一份內(nèi)存盘榨,節(jié)省內(nèi)存喻粹,this 的指向還能正確指向類的實(shí)例。
- 不可枚舉示例:
function Engineer(workingYears) {
this.workingYears = workingYears;
}
// 不能使用箭頭函數(shù)草巡,箭頭函數(shù)的 this 在聲明的時(shí)候就根據(jù)上下文確定了
Engineer.prototype.built = function() {
// this 這里就是執(zhí)行函數(shù)調(diào)用者
console.log(`我已經(jīng)工作 ${this.workingYears} 年了, 我的工作是擰螺絲...`);
};
const engineer = new Engineer(5);
// this 會(huì)正確指向?qū)嵗匚兀?this.workingYears 是 5
engineer.built(); // => 我已經(jīng)工作 5 年了, 我的工作是擰螺絲...
console.log(Object.keys(engineer)); // => [ 'workingYears' ]
- 可枚舉方法示例:
function Engineer(workingYears) {
this.workingYears = workingYears;
this.built = function() {
console.log(`我已經(jīng)工作 ${this.workingYears} 年了, 我的工作是擰螺絲...`);
};
}
const engineer = new Engineer(5);
console.log(Object.keys(engineer)); // => [ 'workingYears', 'built' ]
- 常見:Array.prototype.slice,Object.prototype.toString就是定義在了原型上的函數(shù)山憨。
![image.png](https://upload-images.jianshu.io/upload_images/2228081-90e672f21aa2457d.png&originHeight=456&originWidth=642&size=48264&status=done&style=none&width=487?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
Object.keys()
枚舉function Engineer(workingYears) {
this.workingYears = workingYears;
}
// 不能使用箭頭函數(shù),箭頭函數(shù)的 this 在聲明的時(shí)候就根據(jù)上下文確定了
Engineer.prototype.built = function() {
// this 這里就是執(zhí)行函數(shù)調(diào)用者
console.log(`我已經(jīng)工作 ${this.workingYears} 年了, 我的工作是擰螺絲...`);
};
const engineer = new Engineer(5);
// this 會(huì)正確指向?qū)嵗芤?this.workingYears 是 5
engineer.built(); // => 我已經(jīng)工作 5 年了, 我的工作是擰螺絲...
console.log(Object.keys(engineer)); // => [ 'workingYears' ]
function Engineer(workingYears) {
this.workingYears = workingYears;
}
// 不能使用箭頭函數(shù)草巡,箭頭函數(shù)的 this 在聲明的時(shí)候就根據(jù)上下文確定了
Engineer.prototype.built = function() {
// this 這里就是執(zhí)行函數(shù)調(diào)用者
console.log(`我已經(jīng)工作 ${this.workingYears} 年了, 我的工作是擰螺絲...`);
};
const engineer = new Engineer(5);
// this 會(huì)正確指向?qū)嵗匚兀?this.workingYears 是 5
engineer.built(); // => 我已經(jīng)工作 5 年了, 我的工作是擰螺絲...
console.log(Object.keys(engineer)); // => [ 'workingYears' ]
function Engineer(workingYears) {
this.workingYears = workingYears;
this.built = function() {
console.log(`我已經(jīng)工作 ${this.workingYears} 年了, 我的工作是擰螺絲...`);
};
}
const engineer = new Engineer(5);
console.log(Object.keys(engineer)); // => [ 'workingYears', 'built' ]
<br />
ES6的class本質(zhì)是什么查乒?
- 其實(shí),ES6 class 就是構(gòu)造器的語法糖郁竟。
- ES6 的 class 就是構(gòu)造器玛迄,class 上的方法定義在構(gòu)造器的 prototype 上,因此你也可以理解為什么 class 的方法是不可枚舉的棚亩。
<br />
class extends實(shí)現(xiàn)繼承的本質(zhì)蓖议?
- 原型繼承+組合繼承
// 原型繼承
function _inheritsLoose(subClass, superClass) {
subClass.prototype = Object.create(superClass.prototype);
subClass.prototype.constructor = subClass;
// 讓子類可以訪問父類上的靜態(tài)屬性,其實(shí)就是定義在構(gòu)造器自身上的屬性
// 例如父類有 Person.say 屬性讥蟆,子類 Student 通過可以通過 Student.say 訪問
subClass.__proto__ = superClass;
}
var Shape = function Shape(x, y) {
this.x = x;
this.y = y;
};
var Circle = (function(_Shape) {
_inheritsLoose(Circle, _Shape);
function Circle(x, y, r) {
var _this;
// 組合繼承
_this = _Shape.call(this, x, y) || this;
_this.r = r;
return _this;
}
var _proto = Circle.prototype;
_proto.draw = function draw() {
console.log(
'\u753B\u4E2A\u5750\u6807\u4E3A (' +
this.x +
', ' +
this.y +
')\uFF0C\u534A\u5F84\u4E3A ' +
this.r +
' \u7684\u5706'
);
};
return Circle;
})(Shape);
繼承有哪幾種方式勒虾?
(1)借助構(gòu)造函數(shù)+call/apply實(shí)現(xiàn)繼承
// 借助構(gòu)造函數(shù)實(shí)現(xiàn)繼承
function Parent(argument) {
this.name='parent';
}
Parent.prototype.say=function(){}
function Child(argument) {
Parent.call(this); // 原理就是call/apply, call和apply改變了父類this中的指向瘸彤,使this指向了子類修然,這樣就可以把父類的屬性掛載到子類里
this.age=11;
}
var child = new Child();
console.log(child); // Child {name: "parent", age: 11}
缺點(diǎn)是只能繼承父類實(shí)例上的屬性,無法繼承原型鏈上的屬性。<br />(2)借助原型鏈實(shí)現(xiàn)繼承
// 借助原型鏈實(shí)現(xiàn)繼承
function Parent1(argument) {
this.name='parent';
this.age=[1,2,3];
}
function Child1(argument) {
this.name='child';
}
Child1.prototype=new Parent1(); // 將父類的實(shí)例賦值給子類的原型,這樣子類就繼承了父類
var child11 = new Child1();
console.log(child11) // Child1 {age: 11 , __proto__: Parent1}
/*******************原型鏈繼承的缺點(diǎn)********************/
var child12=new Child1();
child11.age.push(4); // 往其中一個(gè)實(shí)例的引用屬性添加一個(gè)元素
console.log(child11.age,child12.age) // 會(huì)發(fā)現(xiàn)都是打印出 [1, 2, 3, 4]
缺點(diǎn):當(dāng)父類有引用屬性時(shí)愕宋,由于原型對(duì)象的特點(diǎn)玻靡,多個(gè)實(shí)例對(duì)象的proto都是同一個(gè),而引用屬性在new的時(shí)候不會(huì)開辟新的地址中贝,所以當(dāng)一個(gè)實(shí)例對(duì)象改變了引用屬性的值時(shí)囤捻,另一個(gè)對(duì)象也會(huì)隨之改變。<br />(3)結(jié)合構(gòu)造函數(shù)和原型鏈的方式
// 組合方式雄妥,結(jié)合上面兩種
function Parent3(argument) {
this.name='parent';
this.age=[1,2,3]
}
Parent3.prototype.say=function(){}
function Child3(argument) {
Parent3.call(this); // 結(jié)合構(gòu)造函數(shù)
this.type='test';
}
Child3.prototype=new Parent3(); // 結(jié)合原型鏈
var child31 = new Child3();
var child32 = new Child3();
console.log(child31,child32);
child31.age.push(4);
console.log(child31.age,child32.age);.// [1, 2, 3, 4] [1, 2, 3]
這種方式可以解決前兩種方式的問題最蕾。缺點(diǎn):父類的構(gòu)造函數(shù)會(huì)執(zhí)行兩次。<br />優(yōu)化:把上面的
Child3.prototype=new Parent3();
換成
Child3.prototype=Parent3.prototype;
以上方法還是存在不足老厌,因?yàn)橹灰峭ㄟ^原型鏈繼承來的對(duì)象瘟则,它的constructor打印出來都是父類Parent3,即無法確認(rèn)child31實(shí)例是由父類創(chuàng)造的還是由子類創(chuàng)造的枝秤。 原因:Child3和父類Parent3共用了一個(gè)原型醋拧。Child本身沒有constructor,由于繼承了父類淀弹,就會(huì)把父類的constructor作為自己的丹壕。<br />解決方案:把上面的
Child3.prototype=Parent3.prototype;
換成
Child3.prototype=Object.create(Parent3.prototype); // 讓Child3繼承Parent3,由于Object.create會(huì)返回一個(gè)新對(duì)象薇溃,該對(duì)象繼承了Parent3菌赖,再讓Child3去繼承Parent3,這樣就起到了隔離并且繼承的作用沐序。
Child3.prototype.constructor=Child3;
// 修改Child3的constructor
這樣的話就是組合繼承+原型繼承的完美寫法了琉用。
// 組合方式,完美寫法
function Parent3(argument) {
this.name='parent';
this.age=[1,2,3];
}
Parent3.prototype.say=function(){}
function Child3(argument) {
Parent3.call(this); // 結(jié)合構(gòu)造函數(shù)
this.type='test';
}
Child3.prototype=Object.create(Parent3.prototype);
Child3.prototype.constructor=Child3; // 結(jié)合原型鏈
var child31 = new Child3();
var child32 = new Child3();
console.log(child31,child32);
child31.age.push(4);
console.log(child31.age,child32.age);.// [1, 2, 3, 4] [1, 2, 3]
<br />
Object.create是做什么的策幼?
- Object.create()方法創(chuàng)建一個(gè)新對(duì)象邑时,使用現(xiàn)有的對(duì)象來提供新創(chuàng)建的對(duì)象的proto
const person = {
isHuman: false,
printIntroduction: function () {
console.log(`My name is ${this.name}. Am I human? ${this.isHuman}`);
}
};
const me = Object.create(person);
me.name = "Matthew"; // "name" is a property set on "me", but not on "person"
me.isHuman = true; // inherited properties can be overwritten
me.printIntroduction();
// expected output: "My name is Matthew. Am I human? true"
me.__proto__===person; // true
一道最近校招面試碰到的和原型相關(guān)的面試題
最近面試某大廠碰到下面這道面試題:
function Page() {
return this.hosts;
}
Page.hosts = ['h1'];
Page.prototype.hosts = ['h2'];
const p1 = new Page();
const p2 = Page();
console.log(p1.hosts);
console.log(p2.hosts);
復(fù)制代碼
運(yùn)行結(jié)果是:先輸出 undefiend
,然后報(bào)錯(cuò) TypeError: Cannot read property 'hosts' of undefined
特姐。<br />為什么 console.log(p1.hosts)
是輸出 undefiend
呢晶丘,前面我們提過 new 的時(shí)候如果 return 了對(duì)象,會(huì)直接拿這個(gè)對(duì)象作為 new 的結(jié)果唐含,因此浅浮,p1
應(yīng)該是 this.hosts
的結(jié)果奠货,而在 new Page()
的時(shí)候桩引,this 是一個(gè)以 Page.prototype
為原型的 target
對(duì)象,所以這里 this.hosts
可以訪問到 Page.prototype.hosts
也就是 ['h2']
屏轰。這樣 p1
就是等于 ['h2']
铜靶,['h2']
沒有 hosts
屬性所以返回 undefined
。<br />為什么 console.log(p2.hosts)
會(huì)報(bào)錯(cuò)呢,p2
是直接調(diào)用 Page
構(gòu)造函數(shù)的結(jié)果争剿,直接調(diào)用 page
函數(shù)已艰,這個(gè)時(shí)候 this
指向全局對(duì)象,全局對(duì)象并沒 hosts
屬性蚕苇,因此返回 undefined
哩掺,往 undefined
上訪問 hosts
當(dāng)然報(bào)錯(cuò)。<br />
<br />作者:余騰靖<br />鏈接:https://juejin.im/post/5e2ff7dce51d4558021a1a4d<br />來源:掘金<br />著作權(quán)歸作者所有涩笤。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán)嚼吞,非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。<br />
proxy是什么蹬碧?
proxy表單校驗(yàn)實(shí)例
proxy實(shí)現(xiàn)觀察者模式
const observerQueue = new Set()
const observe = fn => observerQueue.add(fn)
const observable = obj => new Proxy(obj,
set(tgt, key, val, receiver) {
const result = Reflect.set(tgt, key, val, recevier)
observerQueue.forEach(v => v())
return result
}
)
const person = observerable({age: 25, name: 'Mike'})
const print = () => console.log(`${person.name} is ${person.age} years old`)
observe(print)
person.name = 'LiHua'
// Lihua is 25 years old
person.age = 45
// Lihua is 45 years old
Common.js和ES6的module比較
- CommonJS 輸出是值的拷貝舱禽,即原來模塊中的值改變不會(huì)影響已經(jīng)加載的該值,ES6靜態(tài)分析恩沽,動(dòng)態(tài)引用誊稚,輸出的是值的引用,值改變罗心,引用也改變里伯,即原來模塊中的值改變則該加載的值也改變。
- CommonJS 模塊是運(yùn)行時(shí)加載渤闷,ES6 模塊是編譯時(shí)輸出接口疾瓮。
- CommonJS 加載的是整個(gè)模塊,即將所有的接口全部加載進(jìn)來飒箭,ES6 可以單獨(dú)加載其中的某個(gè)接口(方法)狼电,
- CommonJS this 指向當(dāng)前模塊,ES6 this 指向undefined
- CommonJS 模塊輸出的是一個(gè)值的拷貝补憾,ES6 模塊輸出的是值的引用漫萄。
————————————————<br />版權(quán)聲明:本文為CSDN博主「冰雪為融」的原創(chuàng)文章,遵循 CC 4.0 BY-SA 版權(quán)協(xié)議盈匾,轉(zhuǎn)載請(qǐng)附上原文出處鏈接及本聲明腾务。<br />原文鏈接:https://blog.csdn.net/lhjuejiang/article/details/80274212<br />
yield傳值與不傳值的區(qū)別是什么?
- yield每次調(diào)用next的時(shí)候傳值削饵,將作為上一次yield語句后的返回值
- 如果不傳值則值為undefined
// 傳值
function *foo(x) {
let y = 2 * (yield (x + 1))
let z = yield (y / 3)
return (x + y + z)
}
let it = foo(5)
console.log(it.next()) // => {value: 6, done: false}
console.log(it.next(12)) // => {value: 8, done: false}
console.log(it.next(13)) // => {value: 42, done: true}
// 不傳值
function *foo(x) {
let y = 2 * (yield (x + 1))
let z = yield (y / 3)
return (x + y + z)
}
let it = foo(5)
console.log(it.next()) // => {value: 6, done: false}
console.log(it.next()) // => {value: 8, done: false}
console.log(it.next()) // => {value: 42, done: true}
VM5628:7 {value: 6, done: false}
VM5628:8 {value: NaN, done: false}
VM5628:9 {value: NaN, done: true}
yield常見用途
function *fetch() {
yield ajax(url, () => {})
yield ajax(url1, () => {})
yield ajax(url2, () => {})
}
let it = fetch()
let result1 = it.next()
let result2 = it.next()
let result3 = it.next()
async函數(shù)的執(zhí)行順序
- async中await修飾的語句會(huì)同步執(zhí)行
- async函數(shù)執(zhí)行之后返回一個(gè)promise岩瘦,里面執(zhí)行的函數(shù)會(huì)作為回調(diào)函數(shù)在外部執(zhí)行棧結(jié)束之后執(zhí)行
let c = 0
let d = async () => {
c = c + await 10
console.log('函數(shù)內(nèi)部1')
await console.log('函數(shù)內(nèi)部2', c)
console.log('函數(shù)內(nèi)部3')
}
undefined
d()
c++
console.log('函數(shù)外部')
VM2692:3 函數(shù)外部
VM2618:4 函數(shù)內(nèi)部1
VM2618:5 函數(shù)內(nèi)部2 10
VM2618:6 函數(shù)內(nèi)部3
經(jīng)典例題實(shí)現(xiàn)Promise.race和Promise.all
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('p1準(zhǔn)備執(zhí)行')
resolve('p1執(zhí)行了')
}, 1000)
})
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('p2執(zhí)行了')
}, 2000)
})
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('p3執(zhí)行失敗了')
}, 3000)
})
Promise.race([p1, p2, p3]).then(res => console.log(res), err => console.log(err))
p1準(zhǔn)備執(zhí)行
p1執(zhí)行了
Promise.all([p1, p2, p3]).then(res => console.log(res), err => console.log(err))
p1準(zhǔn)備執(zhí)行
p3執(zhí)行失敗了
手寫簡(jiǎn)版的Promise
- 參考地址:Promise/A+規(guī)范
- 簡(jiǎn)版實(shí)現(xiàn)鏈接
// <!-- 簡(jiǎn)易版的promise -->
const PENDING = "pending"
const RESOLVE = "resolve"
const REJECT = "reject"
function MyPromise(fn) {
const that = this
that.status = PENDING // MyPromise 內(nèi)部狀態(tài)
that.value = null // 傳入 resolve 和 reject 的值
that.resolveCallbacks = [] // 保存 then 中resolve的回調(diào)函數(shù)
that.rejectCallbacks = [] // 保存 then 中reject的回調(diào)函數(shù)
// resolve 函數(shù) Promise內(nèi)部調(diào)用 resolve 函數(shù) 例:new MyPromise((resolve,reject)=>{resolve(1)})
function resolve(val) {
if (that.status === PENDING) {
that.status = RESOLVE
that.value = val
that.resolveCallbacks.forEach(cb => cb(that.value))
}
}
// reject 函數(shù) Promise內(nèi)部調(diào)用的 reject 函數(shù) 例:new MyPromise((resolve,reject)=>{reject(1)})
function reject(val) {
if (that.status === PENDING) {
that.status = REJECT
that.value = val
that.rejectCallbacks.forEach(cb => cb(that.value))
}
}
// 調(diào)用傳入 MyPromise 內(nèi)的方法 例:new MyPromise((resolve,reject)=>{}) fn=(resolve,reject)=>{}
try {
fn(resolve, reject)
} catch (error) {
reject(error)
}
}
// 在原型上添加then方法
MyPromise.prototype.then = function (onFulfilled, onRejected) {
const that = this
// 判斷傳入的是否為函數(shù)
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v
onRejected = typeof onRejected === 'function' ? onRejected : r => {
throw r
}
//如果 Promise 內(nèi)部存在異步代碼,調(diào)用then方法時(shí)窿撬,此時(shí) promise 內(nèi)部還是 PENDING 狀態(tài)启昧,
將 then 里面的函數(shù)添加進(jìn)回調(diào)數(shù)組,當(dāng)異步處理完成后調(diào)用 MyPromise 內(nèi)部的 resolve 或者
reject 函數(shù)
if (that.status === PENDING) {
that.resolveCallbacks.push(onFulfilled)
that.rejectCallbacks.push(onRejected)
}
// 當(dāng) Promise 內(nèi)部的狀態(tài)已經(jīng)為 resolve,則調(diào)用 then 里面的函數(shù)并傳遞值
if (that.status === RESOLVE) {
onFulfilled(that.value)
}
// 當(dāng) Promise 內(nèi)部狀態(tài)為 reject,則調(diào)用then里的回調(diào)函數(shù)并傳遞值
if (that.status === REJECT) {
onRejected(that.value)
}
}
// 自己實(shí)現(xiàn)的Promise
new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve(1)
reject(2)
}, 0)
}).then(res => {
console.log(res)
}, err => {
console.log(err)
})
// MyPromise.resolve(4).then().then(res => console.log(4)) // 透?jìng)髋椋形磳?shí)現(xiàn)
async和await遇見EventsLoop的注意事項(xiàng)
一般而言,我們可以把
async function f() {
await p
console.log('ok')
}
簡(jiǎn)化理解為:
function f() {
return RESOLVE(p).then(() => {
console.log('ok')
})
}
『RESOLVE(p)』接近于『Promise.resolve(p)』,不過有微妙而重要的區(qū)別:
p 如果本身已經(jīng)是 Promise 實(shí)例严里,Promise.resolve 會(huì)直接返回 p 而不是產(chǎn)生一個(gè)新 promise新啼。
【個(gè)人認(rèn)為 Promise.resolve 的這一『優(yōu)化』行為可能是弊大于利的,不過因?yàn)橐呀?jīng)寫進(jìn)標(biāo)準(zhǔn)了刹碾,
也不太可能修改了燥撞。】老版本V8的問題是當(dāng) p 是一個(gè)已經(jīng) settled 的 promise迷帜,會(huì)進(jìn)行類似的激進(jìn)優(yōu)化物舒,
導(dǎo)致執(zhí)行時(shí)序與非 settled 的 promise 結(jié)果不同。比如把你的 async2 中加入一個(gè) await 語句戏锹,
老版本行為就和新版本一致了冠胯。
例題:
console.log('script start')
async function async1() {
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2 end')
}
async1()
setTimeout(function() {
console.log('setTimeout')
}, 0)
new Promise(resolve => {
console.log('Promise')
resolve()
})
.then(function() {
console.log('promise1')
})
.then(function() {
console.log('promise2')
})
console.log('script end')
// script start => async2 end => Promise => script end => promise1 => promise2 => async1 end => setTimeout
解析:
當(dāng)await 后面是一個(gè)async函數(shù)并且執(zhí)行之后,也就是:
async function async1() {
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2 end')
}
由于async2執(zhí)行后返回一個(gè)promise景用,則相當(dāng)于
async function async1() {
return new Promise((res, rej) => {
console.log('async2 end')
}).then(() => {
console.log('async1 end')
})
}
結(jié)果一樣
null和undefined有何區(qū)別
- null表示有值涵叮,但是為空,Number(null) === 1 false
- undefined表示沒有值伞插,還沒有定義割粮,Number(undefined) === NaN false
如何正確判斷業(yè)務(wù)中遇到的undefined值
- 注意:避免使用==,因?yàn)?/li>
null == undefined
true
0 == undefined
false
'' == undefined
false
- 正確判斷方法
1媚污、使用====
if(backgroundAudioManger === undefined) ...
2舀瓢、使用typeof
if(typeof backgroundAdudioManager == 'undefined')
手寫實(shí)現(xiàn)call,apply,bind
- call
Function.prototype.myCall = function(context) {
if ( typeof this !== 'function' ) throw new TypeError('Error')
// 完善部分,如果傳入context是個(gè)基礎(chǔ)類型是無法綁定fn函數(shù)的耗美,所以
if (typeof context === 'object') {
context = context || window;
} else {
context = Object.create(null)
}
context = context || window
// 如果context中有fn則會(huì)被覆蓋并清除
// newContext.fn = this
// 使用Symbol()獨(dú)一無二數(shù)據(jù)類型避免fn沖突
let fn = Symbol('fn')
context[fn] = this
let args
let result
if ([...arguments][1]) {
args = [...arguments].slice(1)
result = newContext.fn(args)
} else {
result = newContext.fn()
}
delete context[fn]
return result
}
function fn () {
console.log(this.a, this)
}
const obj = {
a: 21
}
fn.myCall(obj)
- apply
Function.prototype.myApply = function(context) {
if (typeof this !== 'function') {
throw new TypeError('Error')
}
context = context || window
context.fn = this
let result
// 處理參數(shù)和 call 有區(qū)別
if (arguments[1]) {
result = context.fn(...arguments[1])
} else {
result = context.fn()
}
delete context.fn
return result
}
- bind
Function.prototype.myBind = function (context) {
if (typeof this !== 'function') {
throw new TypeError('Error')
}
const _this = this
const args = [...arguments].slice(1)
// 返回一個(gè)函數(shù)
return function F() {
// 因?yàn)榉祷亓艘粋€(gè)函數(shù)京髓,我們可以 new F(),所以需要判斷
if (this instanceof F) {
return new _this(...args, ...arguments)
}
return _this.apply(context, args.concat(...arguments))
}
}
手寫實(shí)現(xiàn)一個(gè)new方法
- new原理那部分商架,鏈接到原型堰怨,obj = Object.create(Con.prototype),這種方式會(huì)比較好
function _new(fn,...arg){
let obj = {}
let con = [].slice.call(arguments)
obj.__proto__ = con.prototype //鏈接原型
const ret = fn.call(obj, ...arg); //改變this的指向
return ret instanceof Object ? ret : obj;
}
- 參考地址:鏈接
Symbol()和Symbol.for()和Symbol.keyFor()區(qū)別
- 參考鏈接:鏈接
- Symbol()返回一個(gè)獨(dú)一無二的標(biāo)識(shí)符類型值
- Symbol.for()蛇摸,將傳入值生成一個(gè)標(biāo)識(shí)符類性值备图,并將其作為key存入到注冊(cè)表中
- Symbol.keyFor(),傳入一個(gè)Symbol.for()生成的標(biāo)識(shí)符類型值赶袄,返回注冊(cè)表中對(duì)應(yīng)該key的value值(Symbol.for(‘value’))
- image.png
巧用parseFloat避免js浮點(diǎn)數(shù)精度轉(zhuǎn)換問題
(0.1 + 0.2).toFixed(10)
"0.3000000000"
parseFloat((0.1 + 0.2).toFixed(10))
0.3
更簡(jiǎn)便的手寫call
Function.prototype.myCall = function(context,...args){
context = context || window
const symbol = Symbol()
context[symbol] = this
const result = context[symbol](...args)
delete context[symbol]
return result
}
[].shift.call(arguments)是什么原理呢揽涮?
改變綁定的 this:
let a = [1,2,3]
const myshift = [].shift.bind(a)
console.log(myshift(a))
- Arguments是一個(gè)類數(shù)組對(duì)線,不能直接進(jìn)行數(shù)組想關(guān)的操作饿肺,使用[].shifit.call(arguments)則可以轉(zhuǎn)化為數(shù)組之后進(jìn)行一些操作
function fn3 (a, b) {
console.log(arguments)
console.log([...arguments])
console.log([].shift.call(arguments))
}
![image.png](https://upload-images.jianshu.io/upload_images/2228081-b2a681a9785b7e0c.png&originHeight=221&originWidth=525&size=14091&status=done&style=none&width=525?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
<br />
![image.png](https://upload-images.jianshu.io/upload_images/2228081-3452e08a7ec6cca6.png&originHeight=259&originWidth=453&size=15817&status=done&style=none&width=453?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
<br />
事件代理應(yīng)用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>手寫函數(shù)測(cè)試</title>
</head>
<body>
<ul id="ul">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
<script>
let ul = document.querySelector('#ul')
ul.addEventListener('click', (event) => {
console.log(event.target);
})
</script>
</body>
</html>
阻止事件冒泡
- 操作:在子DOM節(jié)點(diǎn)的event執(zhí)行事件上綁定stopPropagation
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>手寫函數(shù)測(cè)試</title>
</head>
<body>
<ul id="ul">
<li>1</li>
<li>2</li>
<li>3</li>
<li id="li">4</li>
<li>5</li>
</ul>
<script>
let ul = document.querySelector('#ul')
ul.addEventListener('click', (event) => {
console.log(event);
}) // 如果在后面配置項(xiàng)為true, 則先捕獲(父節(jié)點(diǎn)事件)后冒泡(子節(jié)點(diǎn)事件)
let li = document.querySelector('#li')
li.addEventListener('click', (event) => {
console.log(event, '4點(diǎn)擊了')
event.stopPropagation() // 阻止其余捕獲
})
</script>
</body>
</html>
冒泡事件和捕獲事件順序調(diào)換的配置
- 默認(rèn)false
<div>
外層div
<p>內(nèi)層p</p>
<div>
var div = document.querySelector('div');
var p = document.querySelector('p');
div.addEventListener('click', function() {console.log("冒泡")}, false);
div.addEventListener('click', function() {console.log("捕獲")}, true);
點(diǎn)擊div, 輸出 冒泡敬辣,捕獲
點(diǎn)擊p, 輸出捕獲雪标,冒泡
<br />
瀏覽器同源策略要干啥的零院, 為什么要跨域請(qǐng)求?
- 當(dāng)我們?cè)?a target="_blank">www.a.com這個(gè)域下用ajax提交一個(gè)請(qǐng)求到www.b.com這個(gè)域的時(shí)候村刨,默認(rèn)情況下门粪,瀏覽器是不允許的,因?yàn)檫`反了瀏覽器的同源策略烹困。
- 參考地址:https://www.cnblogs.com/anai/p/4227157.html
跨域解決方案
- Jsonp
- Cors白名單
- document.domain(適用于二級(jí)域名相同--->a.test.com 和 b.test.com)
- postMessage
- 前端proxy正向代理
- 后端proxy反向代理
cookie,localStorage乾吻,sessionStorage區(qū)別
- cookie:多為網(wǎng)絡(luò)請(qǐng)求過程中服務(wù)器通過請(qǐng)求信息在瀏覽器客戶端種下髓梅,可以用來保存用戶登錄狀態(tài),規(guī)避http請(qǐng)求無狀態(tài)缺陷绎签,缺陷是每一個(gè)name對(duì)應(yīng)的value值大小只有4k枯饿,可存儲(chǔ)量太小,超過會(huì)被裁剪诡必,而且每次請(qǐng)求都會(huì)攜帶奢方,造成額外的帶寬占用
- localStorage:除非手動(dòng)清空,否則用于存在于本地存儲(chǔ)中爸舒,可在同源標(biāo)簽頁下共享蟋字,適用于一些長(zhǎng)久不變的數(shù)據(jù),比如一些電商網(wǎng)站的base64數(shù)據(jù)扭勉,大小約為5兆左右
- sessionStorage:只在當(dāng)前窗口下存在鹊奖,同源標(biāo)簽下不共享,瀏覽器窗口關(guān)閉會(huì)被自動(dòng)清空涂炎,適用于需要每次打開都重新登錄的網(wǎng)站存儲(chǔ)JWT的token數(shù)據(jù)忠聚。大小約為5兆左右
- 區(qū)別:sessionStorage保存的數(shù)據(jù)用于瀏覽器的一次會(huì)話,當(dāng)會(huì)話結(jié)束(通常是該窗口關(guān)閉)唱捣,數(shù)據(jù)被清空两蟀;sessionStorage 特別的一點(diǎn)在于,即便是相同域名下的兩個(gè)頁面震缭,只要它們不在同一個(gè)瀏覽器窗口中打開赂毯,那么它們的 sessionStorage 內(nèi)容便無法共享;localStorage 在所有同源窗口中都是共享的蛀序;cookie也是在所有同源窗口中都是共享的欢瞪。除了保存期限的長(zhǎng)短不同,SessionStorage的屬性和方法與LocalStorage完全一樣徐裸。
- 參考:https://github.com/ljianshu/Blog/issues/25
<br />
知道哪些新的緩存方案遣鼓?
- IndexDB:瀏覽器提供的用途類似localStorage的NoSQL型數(shù)據(jù)庫,除非手動(dòng)清除否則一直存在重贺,類似MongoDB的異步存取骑祟,
- 參考:阮一峰IndexDB入門
- 參考:簡(jiǎn)書IndexDB實(shí)例
- ServiceWorker:地址https://jakearchibald.com/2014/offline-cookbook/
- 可以劫持請(qǐng)求和響應(yīng)回懦,做緩存,并可以離線運(yùn)行次企,不可以在ServiceWorker下定義全局變量
- ServiceWorker類似的webWorker:http://www.ruanyifeng.com/blog/2018/07/web-worker.html
代理的工作原理解析
- 正向代理和反向代理優(yōu)缺點(diǎn)
- proxy正向代理原理:
- [圖片上傳失敗...(image-e2b726-1611839965980)]
<br />
- webpack中的proxy本地代理原理
- 使用了express框架搭建本web項(xiàng)目服務(wù)的時(shí)候怯晕,引入了
http-proxy-middleware
的中間件,參考鏈接http://www.reibang.com/p/8fd5d7347c57 - image.png
node服務(wù)后臺(tái)接收預(yù)檢請(qǐng)求防止報(bào)錯(cuò)
if(res.method == 'OPTIONS') {
res.statusCode = 204
res.setHeader('Content-Length', '0')
res.end()
} else {
next()
}
瀏覽器緩存機(jī)制(后端向)
[圖片上傳失敗...(image-f02655-1611839965980)]<br />
瀏覽器渲染原理
參考地址:https://github.com/ljianshu/Blog/issues/51
- 接收信息解析代碼過程:
[圖片上傳失敗...(image-f04011-1611839965980)]
- 接收HTML文件缸棵,構(gòu)建DOM樹
- 接收CSS文件舟茶,構(gòu)建CSSOM樹
- 接收jS文件(加載js腳本),等到Javascript 腳本文件加載后堵第, 通過 DOM API 和 CSSOM API 來操作 DOM Tree 和 CSS Rule Tree吧凉。
<br />
- 注意:在構(gòu)建DOM時(shí),HTML解析器若遇到了JavaScript踏志,那么它會(huì)暫停構(gòu)建DOM阀捅,將控制權(quán)移交給JavaScript引擎,等JavaScript引擎運(yùn)行完畢针余,瀏覽器再從中斷的地方恢復(fù)DOM構(gòu)建饲鄙。
- 瀏覽器會(huì)先下載和構(gòu)建CSSOM,然后再執(zhí)行JavaScript圆雁,最后在繼續(xù)構(gòu)建DOM忍级。
<br />
性能優(yōu)化建議:
- 還有js中操作dom元素的樣式時(shí),如果修改多條style伪朽,不如把多條style寫成一個(gè)class颤练,對(duì)元素進(jìn)行添加class的操作。如果是分步添加元素樣式驱负,可以將元素賦值給一個(gè)變量嗦玖,避免多次獲取一個(gè)元素,因?yàn)楂@取元素也會(huì)造成回流跃脊。
- 不要一條一條的修改 DOM 樣式宇挫,盡量提前設(shè)置好 class,后續(xù)增加 class酪术,進(jìn)行批量修改器瘪。
- 使用 documentFragment 對(duì)象在內(nèi)存里操作 DOM
- 不一定說非要用visibility: hidden替換display:none,完全可以把修改頻繁的元素先 display: none绘雁,修改完之后顯示橡疼。
RAF討論
- 屏幕每16.6ms才刷新一次,比如這一幀有個(gè)人站著庐舟,下一幀他應(yīng)該要邁開左腿欣除,如果都已經(jīng)下一幀了,瀏覽器還沒渲染出邁開腿的畫面挪略,這個(gè)人還站著历帚,看著就卡了滔岳。然后event loop和渲染這邊,我的理解是挽牢,在短短16.6ms內(nèi)谱煤,要保證一個(gè)tick能執(zhí)行完并將這個(gè)tick里對(duì)dom的操作渲染出來(也因此同個(gè)tick里的多個(gè)dom操作會(huì)被合并),這樣下一幀才能有邁開腿的畫面出現(xiàn)在屏幕上禽拔。如果當(dāng)前的tick要花好久才能執(zhí)行完刘离,就說40ms吧,那至少兩幀都只有人站著的畫面睹栖,第三幀還剩短短10ms用來重新渲染寥闪,如果能渲染出來還好,下一幀就能看磨淌,否則下一幀還看不到變化。
- 所以凿渊,假設(shè)一個(gè)tick里js先往某個(gè)<ul>里添加了幾個(gè)<li>子節(jié)點(diǎn)梁只,接著還要做一件非常耗時(shí)的事情,兩件事合在一起要好久才能完成埃脏,不如把兩件事拆開搪锣,放到兩個(gè)requestAnimationFrame的回調(diào)里執(zhí)行,這樣就能先把對(duì)dom的修改重新渲染處理彩掐,下一幀至少能看到變化了构舟。
<br />
前端安全
參考:XSS攻擊和CSRF攻擊
XSS:https://juejin.im/post/5bad9140e51d450e935c6d64<br />CSRF:https://juejin.im/post/5bc009996fb9a05d0a055192#heading-32<br />
了解webpack原理,手寫小型webpack
流程淺析:參考
- createAsset:讀取入口文件堵幽,通過babylon的parse方法將文件內(nèi)容字符串轉(zhuǎn)化為AST語法樹(包含id,filename,dependencies,code信息)的一種數(shù)據(jù)結(jié)構(gòu)
- createGraph:遍歷入門文件AST語法樹上的dependencies數(shù)組狗超,生成一個(gè)新的數(shù)組,數(shù)組每一項(xiàng)和createAsset方法創(chuàng)建出來的AST語法樹非常相像朴下,并且多了一個(gè)mapping屬性指定文件依賴努咐,數(shù)組中將依賴文件根據(jù)依賴順序id遞增排列。
- bundle:接收graph數(shù)組殴胧,生成bundle結(jié)構(gòu)的自執(zhí)行函數(shù)
- 代碼地址:https://github.com/dykily/simple_webpack/blob/327618d4e2/bundler.js