摘要:本文挑選了到大廠面試題,大家在閱讀時(shí),建議不要先看我的答案,而是自己先思考一番。構(gòu)造函數(shù)返回值是或,是返回的是種返回的對(duì)象。
今年來(lái),各大公司都縮減了HC,甚至是采取了“裁員”措施,在這樣的大環(huán)境之下,想要獲得一份更好的工作,必然需要付出更多的努力。
本文挑選了20到大廠面試題,大家在閱讀時(shí),建議不要先看我的答案,而是自己先思考一番。盡管,本文所有的答案,都是我在翻閱各種資料,思考并驗(yàn)證之后,才給出的。但因水平有限,本人的答案未必是最優(yōu)的,如果您有更好的答案,歡迎給我留言。
本文篇幅較長(zhǎng),希望小伙伴們能夠堅(jiān)持讀完,如果想加入交流群,可以通過(guò)文末的公眾號(hào)添加我為好友。
更多優(yōu)質(zhì)文章可戳: https://github.com/YvetteLau/...
1. new的實(shí)現(xiàn)原理是什么?new 的實(shí)現(xiàn)原理:
創(chuàng)建一個(gè)空對(duì)象,構(gòu)造函數(shù)中的this指向這個(gè)空對(duì)象
這個(gè)新對(duì)象被執(zhí)行 [[原型]] 連接
執(zhí)行構(gòu)造函數(shù)方法,屬性和方法被添加到this引用的對(duì)象中
如果構(gòu)造函數(shù)中沒(méi)有返回其它對(duì)象,那么返回this,即創(chuàng)建的這個(gè)的新對(duì)象,否則,返回構(gòu)造函數(shù)中返回的對(duì)象。
function _new() { let target = {}; //創(chuàng)建的新對(duì)象 //第一個(gè)參數(shù)是構(gòu)造函數(shù) let [constructor, ...args] = [...arguments]; //執(zhí)行[[原型]]連接;target 是 constructor 的實(shí)例 target.__proto__ = constructor.prototype; //執(zhí)行構(gòu)造函數(shù),將屬性或方法添加到創(chuàng)建的空對(duì)象上 let result = constructor.apply(target, args); if (result && (typeof (result) == "object" || typeof (result) == "function")) { //如果構(gòu)造函數(shù)執(zhí)行的結(jié)構(gòu)返回的是一個(gè)對(duì)象,那么返回這個(gè)對(duì)象 return result; } //如果構(gòu)造函數(shù)返回的不是一個(gè)對(duì)象,返回創(chuàng)建的新對(duì)象 return target; }2. 如何正確判斷this的指向?
如果用一句話說(shuō)明 this 的指向,那么即是: 誰(shuí)調(diào)用它,this 就指向誰(shuí)。
但是僅通過(guò)這句話,我們很多時(shí)候并不能準(zhǔn)確判斷 this 的指向。因此我們需要借助一些規(guī)則去幫助自己:
this 的指向可以按照以下順序判斷:
全局環(huán)境中的 this瀏覽器環(huán)境:無(wú)論是否在嚴(yán)格模式下,在全局執(zhí)行環(huán)境中(在任何函數(shù)體外部)this 都指向全局對(duì)象 window;
node 環(huán)境:無(wú)論是否在嚴(yán)格模式下,在全局執(zhí)行環(huán)境中(在任何函數(shù)體外部),this 都是空對(duì)象 {};
是否是 new 綁定如果是 new 綁定,并且構(gòu)造函數(shù)中沒(méi)有返回 function 或者是 object,那么 this 指向這個(gè)新對(duì)象。如下:
構(gòu)造函數(shù)返回值不是 function 或 object。new Super() 返回的是 this 對(duì)象。
function Super(age) { this.age = age; } let instance = new Super("26"); console.log(instance.age); //26
構(gòu)造函數(shù)返回值是 function 或 object,new Super()是返回的是Super種返回的對(duì)象。
function Super(age) { this.age = age; let obj = {a: "2"}; return obj; } let instance = new Super("hello"); console.log(instance);//{ a: "2" } console.log(instance.age); //undefined函數(shù)是否通過(guò) call,apply 調(diào)用,或者使用了 bind 綁定,如果是,那么this綁定的就是指定的對(duì)象【歸結(jié)為顯式綁定】。
function info(){ console.log(this.age); } var person = { age: 20, info } var age = 28; var info = person.info; info.call(person); //20 info.apply(person); //20 info.bind(person)(); //20
這里同樣需要注意一種特殊情況,如果 call,apply 或者 bind 傳入的第一個(gè)參數(shù)值是 undefined 或者 null,嚴(yán)格模式下 this 的值為傳入的值 null /undefined。非嚴(yán)格模式下,實(shí)際應(yīng)用的默認(rèn)綁定規(guī)則,this 指向全局對(duì)象(node環(huán)境為global,瀏覽器環(huán)境為window)
function info(){ //node環(huán)境中:非嚴(yán)格模式 global,嚴(yán)格模式為null //瀏覽器環(huán)境中:非嚴(yán)格模式 window,嚴(yán)格模式為null console.log(this); console.log(this.age); } var person = { age: 20, info } var age = 28; var info = person.info; //嚴(yán)格模式拋出錯(cuò)誤; //非嚴(yán)格模式,node下輸出undefined(因?yàn)槿值腶ge不會(huì)掛在 global 上) //非嚴(yán)格模式。瀏覽器環(huán)境下輸出 28(因?yàn)槿值腶ge會(huì)掛在 window 上) info.call(null);隱式綁定,函數(shù)的調(diào)用是在某個(gè)對(duì)象上觸發(fā)的,即調(diào)用位置上存在上下文對(duì)象。典型的隱式調(diào)用為: xxx.fn()
function info(){ console.log(this.age); } var person = { age: 20, info } var age = 28; person.info(); //20;執(zhí)行的是隱式綁定默認(rèn)綁定,在不能應(yīng)用其它綁定規(guī)則時(shí)使用的默認(rèn)規(guī)則,通常是獨(dú)立函數(shù)調(diào)用。
非嚴(yán)格模式: node環(huán)境,執(zhí)行全局對(duì)象 global,瀏覽器環(huán)境,執(zhí)行全局對(duì)象 window。
嚴(yán)格模式:執(zhí)行 undefined
function info(){ console.log(this.age); } var age = 28; //嚴(yán)格模式;拋錯(cuò) //非嚴(yán)格模式,node下輸出 undefined(因?yàn)槿值腶ge不會(huì)掛在 global 上) //非嚴(yán)格模式。瀏覽器環(huán)境下輸出 28(因?yàn)槿值腶ge會(huì)掛在 window 上) //嚴(yán)格模式拋出,因?yàn)?this 此時(shí)是 undefined info();箭頭函數(shù)的情況:
箭頭函數(shù)沒(méi)有自己的this,繼承外層上下文綁定的this。
let obj = { age: 20, info: function() { return () => { console.log(this.age); //this繼承的是外層上下文綁定的this } } } let person = {age: 28}; let info = obj.info(); info(); //20 let info2 = obj.info.call(person); info2(); //283. 深拷貝和淺拷貝的區(qū)別是什么?實(shí)現(xiàn)一個(gè)深拷貝
深拷貝和淺拷貝是針對(duì)復(fù)雜數(shù)據(jù)類(lèi)型來(lái)說(shuō)的,淺拷貝只拷貝一層,而深拷貝是層層拷貝。
深拷貝深拷貝復(fù)制變量值,對(duì)于非基本類(lèi)型的變量,則遞歸至基本類(lèi)型變量后,再?gòu)?fù)制。 深拷貝后的對(duì)象與原來(lái)的對(duì)象是完全隔離的,互不影響,對(duì)一個(gè)對(duì)象的修改并不會(huì)影響另一個(gè)對(duì)象。淺拷貝
淺拷貝是會(huì)將對(duì)象的每個(gè)屬性進(jìn)行依次復(fù)制,但是當(dāng)對(duì)象的屬性值是引用類(lèi)型時(shí),實(shí)質(zhì)復(fù)制的是其引用,當(dāng)引用指向的值改變時(shí)也會(huì)跟著變化。
可以使用 for in、 Object.assign、 擴(kuò)展運(yùn)算符 ... 、Array.prototype.slice()、Array.prototype.concat() 等,例如:
let obj = { name: "Yvette", age: 18, hobbies: ["reading", "photography"] } let obj2 = Object.assign({}, obj); let obj3 = {...obj}; obj.name = "Jack"; obj.hobbies.push("coding"); console.log(obj);//{ name: "Jack", age: 18,hobbies: [ "reading", "photography", "coding" ] } console.log(obj2);//{ name: "Yvette", age: 18,hobbies: [ "reading", "photography", "coding" ] } console.log(obj3);//{ name: "Yvette", age: 18,hobbies: [ "reading", "photography", "coding" ] }
可以看出淺拷貝只最第一層屬性進(jìn)行了拷貝,當(dāng)?shù)谝粚拥膶傩灾凳腔緮?shù)據(jù)類(lèi)型時(shí),新的對(duì)象和原對(duì)象互不影響,但是如果第一層的屬性值是復(fù)雜數(shù)據(jù)類(lèi)型,那么新對(duì)象和原對(duì)象的屬性值其指向的是同一塊內(nèi)存地址。
深拷貝實(shí)現(xiàn)1.深拷貝最簡(jiǎn)單的實(shí)現(xiàn)是: JSON.parse(JSON.stringify(obj))
JSON.parse(JSON.stringify(obj)) 是最簡(jiǎn)單的實(shí)現(xiàn)方式,但是有一些缺陷:
對(duì)象的屬性值是函數(shù)時(shí),無(wú)法拷貝。
原型鏈上的屬性無(wú)法拷貝
不能正確的處理 Date 類(lèi)型的數(shù)據(jù)
不能處理 RegExp
會(huì)忽略 symbol
會(huì)忽略 undefined
2.實(shí)現(xiàn)一個(gè) deepClone 函數(shù)
如果是基本數(shù)據(jù)類(lèi)型,直接返回
如果是 RegExp 或者 Date 類(lèi)型,返回對(duì)應(yīng)類(lèi)型
如果是復(fù)雜數(shù)據(jù)類(lèi)型,遞歸。
考慮循環(huán)引用的問(wèn)題
function deepClone(obj, hash = new WeakMap()) { //遞歸拷貝 if (obj instanceof RegExp) return new RegExp(obj); if (obj instanceof Date) return new Date(obj); if (obj === null || typeof obj !== "object") { //如果不是復(fù)雜數(shù)據(jù)類(lèi)型,直接返回 return obj; } if (hash.has(obj)) { return hash.get(obj); } /** * 如果obj是數(shù)組,那么 obj.constructor 是 [Function: Array] * 如果obj是對(duì)象,那么 obj.constructor 是 [Function: Object] */ let t = new obj.constructor(); hash.set(obj, t); for (let key in obj) { //遞歸 if (obj.hasOwnProperty(key)) {//是否是自身的屬性 t[key] = deepClone(obj[key], hash); } } return t; }4. call/apply 的實(shí)現(xiàn)原理是什么?
call 和 apply 的功能相同,都是改變 this 的執(zhí)行,并立即執(zhí)行函數(shù)。區(qū)別在于傳參方式不同。
func.call(thisArg, arg1, arg2, ...):第一個(gè)參數(shù)是 this 指向的對(duì)象,其它參數(shù)依次傳入。
func.apply(thisArg, [argsArray]):第一個(gè)參數(shù)是 this 指向的對(duì)象,第二個(gè)參數(shù)是數(shù)組或類(lèi)數(shù)組。
一起思考一下,如何模擬實(shí)現(xiàn) call ?
首先,我們知道,函數(shù)都可以調(diào)用 call,說(shuō)明 call 是函數(shù)原型上的方法,所有的實(shí)例都可以調(diào)用。即: Function.prototype.call。
在 call 方法中獲取調(diào)用call()函數(shù)
如果第一個(gè)參數(shù)沒(méi)有傳入,那么默認(rèn)指向 window / global(非嚴(yán)格模式)
傳入 call 的第一個(gè)參數(shù)是 this 指向的對(duì)象,根據(jù)隱式綁定的規(guī)則,我們知道 obj.foo(), foo() 中的 this 指向 obj;因此我們可以這樣調(diào)用函數(shù) thisArgs.func(...args)
返回執(zhí)行結(jié)果
Function.prototype.call = function() { let [thisArg, ...args] = [...arguments]; if (!thisArg) { //context為null或者是undefined thisArg = typeof window === "undefined" ? global : window; } //this的指向的是當(dāng)前函數(shù) func (func.call) thisArg.func = this; //執(zhí)行函數(shù) let result = thisArg.func(...args); delete thisArg.func; //thisArg上并沒(méi)有 func 屬性,因此需要移除 return result; }
bind 的實(shí)現(xiàn)思路和 call 一致,僅參數(shù)處理略有差別。如下:
Function.prototype.apply = function(thisArg, rest) { let result; //函數(shù)返回結(jié)果 if (!thisArg) { //context為null或者是undefined thisArg = typeof window === "undefined" ? global : window; } //this的指向的是當(dāng)前函數(shù) func (func.call) thisArg.func = this; if(!rest) { //第二個(gè)參數(shù)為 null / undefined result = thisArg.func(); }else { result = thisArg.func(...rest); } delete thisArg.func; //thisArg上并沒(méi)有 func 屬性,因此需要移除 return result; }5. 柯里化函數(shù)實(shí)現(xiàn)
在開(kāi)始之前,我們首先需要搞清楚函數(shù)柯里化的概念。
函數(shù)柯里化是把接受多個(gè)參數(shù)的函數(shù)變換成接受一個(gè)單一參數(shù)(最初函數(shù)的第一個(gè)參數(shù))的函數(shù),并且返回接受余下的參數(shù)而且返回結(jié)果的新函數(shù)的技術(shù)。
const curry = (fn, ...args) => args.length < fn.length //參數(shù)長(zhǎng)度不足時(shí),重新柯里化該函數(shù),等待接受新參數(shù) ? (...arguments) => curry(fn, ...args, ...arguments) //參數(shù)長(zhǎng)度滿足時(shí),執(zhí)行函數(shù) : fn(...args);
function sumFn(a, b, c) { return a + b + c; } var sum = curry(sumFn); console.log(sum(2)(3)(5));//10 console.log(sum(2, 3, 5));//10 console.log(sum(2)(3, 5));//10 console.log(sum(2, 3)(5));//10
函數(shù)柯里化的主要作用:
參數(shù)復(fù)用
提前返回 – 返回接受余下的參數(shù)且返回結(jié)果的新函數(shù)
延遲執(zhí)行 – 返回新函數(shù),等待執(zhí)行
6. 如何讓 (a == 1 && a == 2 && a == 3) 的值為true?利用隱式類(lèi)型轉(zhuǎn)換
== 操作符在左右數(shù)據(jù)類(lèi)型不一致時(shí),會(huì)先進(jìn)行隱式轉(zhuǎn)換。
a == 1 && a == 2 && a == 3 的值意味著其不可能是基本數(shù)據(jù)類(lèi)型。因?yàn)槿绻?a 是 null 或者是 undefined bool類(lèi)型,都不可能返回true。
因此可以推測(cè) a 是復(fù)雜數(shù)據(jù)類(lèi)型,JS 中復(fù)雜數(shù)據(jù)類(lèi)型只有 object,回憶一下,Object 轉(zhuǎn)換為原始類(lèi)型會(huì)調(diào)用什么方法?
如果部署了 [Symbol.toPrimitive] 接口,那么調(diào)用此接口,若返回的不是基本數(shù)據(jù)類(lèi)型,拋出錯(cuò)誤。
如果沒(méi)有部署 [Symbol.toPrimitive] 接口,那么根據(jù)要轉(zhuǎn)換的類(lèi)型,先調(diào)用 valueOf / toString
非Date類(lèi)型對(duì)象,hint 是 default 時(shí),調(diào)用順序?yàn)椋?b>valueOf >>> toString,即valueOf 返回的不是基本數(shù)據(jù)類(lèi)型,才會(huì)繼續(xù)調(diào)用 valueOf,如果toString 返回的還不是基本數(shù)據(jù)類(lèi)型,那么拋出錯(cuò)誤。
如果 hint 是 string(Date對(duì)象的hint默認(rèn)是string) ,調(diào)用順序?yàn)椋?b>toString >>> valueOf,即toString 返回的不是基本數(shù)據(jù)類(lèi)型,才會(huì)繼續(xù)調(diào)用 valueOf,如果valueOf 返回的還不是基本數(shù)據(jù)類(lèi)型,那么拋出錯(cuò)誤。
如果 hint 是 number,調(diào)用順序?yàn)椋?valueOf >>> toString
//部署 [Symbol.toPrimitive] / valueOf/ toString 皆可 //一次返回1,2,3 即可。 let a = { [Symbol.toPrimitive]: (function(hint) { let i = 1; //閉包的特性之一:i 不會(huì)被回收 return function() { return i++; } })() }
利用數(shù)據(jù)劫持(Proxy/Object.definedProperty)
let i = 1; let a = new Proxy({}, { i: 1, get: function () { return () => this.i++; } });
數(shù)組的 toString 接口默認(rèn)調(diào)用數(shù)組的 join 方法,重新 join 方法
let a = [1, 2, 3]; a.join = a.shift;7. 什么是BFC?BFC的布局規(guī)則是什么?如何創(chuàng)建BFC?
Box 是 CSS 布局的對(duì)象和基本單位,頁(yè)面是由若干個(gè)Box組成的。
元素的類(lèi)型 和 display 屬性,決定了這個(gè) Box 的類(lèi)型。不同類(lèi)型的 Box 會(huì)參與不同的 Formatting Context。
Formatting Context
Formatting Context 是頁(yè)面的一塊渲染區(qū)域,并且有一套渲染規(guī)則,決定了其子元素將如何定位,以及和其它元素的關(guān)系和相互作用。
Formatting Context 有 BFC (Block formatting context),IFC (Inline formatting context),F(xiàn)FC (Flex formatting context) 和 GFC (Grid formatting context)。FFC 和 GFC 為 CC3 中新增。
BFC布局規(guī)則
BFC內(nèi),盒子依次垂直排列。
BFC內(nèi),兩個(gè)盒子的垂直距離由 margin 屬性決定。屬于同一個(gè)BFC的兩個(gè)相鄰Box的margin會(huì)發(fā)生重疊【符合合并原則的margin合并后是使用大的margin】
BFC內(nèi),每個(gè)盒子的左外邊緣接觸內(nèi)部盒子的左邊緣(對(duì)于從右到左的格式,右邊緣接觸)。即使在存在浮動(dòng)的情況下也是如此。除非創(chuàng)建新的BFC。
BFC的區(qū)域不會(huì)與float box重疊。
BFC就是頁(yè)面上的一個(gè)隔離的獨(dú)立容器,容器里面的子元素不會(huì)影響到外面的元素。反之也如此。
計(jì)算BFC的高度時(shí),浮動(dòng)元素也參與計(jì)算。
如何創(chuàng)建BFC
根元素
浮動(dòng)元素(float 屬性不為 none)
position 為 absolute 或 fixed
overflow 不為 visible 的塊元素
display 為 inline-block, table-cell, table-caption
BFC 的應(yīng)用
防止 margin 重疊 (同一個(gè)BFC內(nèi)的兩個(gè)兩個(gè)相鄰Box的 margin 會(huì)發(fā)生重疊,觸發(fā)生成兩個(gè)BFC,即不會(huì)重疊)
清除內(nèi)部浮動(dòng) (創(chuàng)建一個(gè)新的 BFC,因?yàn)楦鶕?jù) BFC 的規(guī)則,計(jì)算 BFC 的高度時(shí),浮動(dòng)元素也參與計(jì)算)
自適應(yīng)多欄布局 (BFC的區(qū)域不會(huì)與float box重疊。因此,可以觸發(fā)生成一個(gè)新的BFC)
8. 異步加載JS腳本的方式有哪些?標(biāo)簽中增加 async(html5) 或者 defer(html4) 屬性,腳本就會(huì)異步加載。
defer 和 async 的區(qū)別在于:
defer 要等到整個(gè)頁(yè)面在內(nèi)存中正常渲染結(jié)束(DOM 結(jié)構(gòu)完全生成,以及其他腳本執(zhí)行完成),在window.onload 之前執(zhí)行;
async 一旦下載完,渲染引擎就會(huì)中斷渲染,執(zhí)行這個(gè)腳本以后,再繼續(xù)渲染。
如果有多個(gè) defer 腳本,會(huì)按照它們?cè)陧?yè)面出現(xiàn)的順序加載
多個(gè) async 腳本不能保證加載順序
動(dòng)態(tài)創(chuàng)建 script 標(biāo)簽
動(dòng)態(tài)創(chuàng)建的 script ,設(shè)置 src 并不會(huì)開(kāi)始下載,而是要添加到文檔中,JS文件才會(huì)開(kāi)始下載。
let script = document.createElement("script"); script.src = "XXX.js"; // 添加到html文件中才會(huì)開(kāi)始下載 document.body.append(script);
XHR 異步加載JS
let xhr = new XMLHttpRequest(); xhr.open("get", "js/xxx.js",true); xhr.send(); xhr.onreadystatechange = function() { if (xhr.readyState == 4 && xhr.status == 200) { eval(xhr.responseText); } }9. ES5有幾種方式可以實(shí)現(xiàn)繼承?分別有哪些優(yōu)缺點(diǎn)?
ES5 有 6 種方式可以實(shí)現(xiàn)繼承,分別為:
原型鏈繼承的基本思想是利用原型讓一個(gè)引用類(lèi)型繼承另一個(gè)引用類(lèi)型的屬性和方法。
function SuperType() { this.name = "Yvette"; this.colors = ["pink", "blue", "green"]; } SuperType.prototype.getName = function () { return this.name; } function SubType() { this.age = 22; } SubType.prototype = new SuperType(); SubType.prototype.getAge = function() { return this.age; } SubType.prototype.constructor = SubType; let instance1 = new SubType(); instance1.colors.push("yellow"); console.log(instance1.getName()); //"Yvette" console.log(instance1.colors);//[ "pink", "blue", "green", "yellow" ] let instance2 = new SubType(); console.log(instance2.colors);//[ "pink", "blue", "green", "yellow" ]
缺點(diǎn):
通過(guò)原型來(lái)實(shí)現(xiàn)繼承時(shí),原型會(huì)變成另一個(gè)類(lèi)型的實(shí)例,原先的實(shí)例屬性變成了現(xiàn)在的原型屬性,該原型的引用類(lèi)型屬性會(huì)被所有的實(shí)例共享。
在創(chuàng)建子類(lèi)型的實(shí)例時(shí),沒(méi)有辦法在不影響所有對(duì)象實(shí)例的情況下給超類(lèi)型的構(gòu)造函數(shù)中傳遞參數(shù)。
借用構(gòu)造函數(shù)的技術(shù),其基本思想為:
在子類(lèi)型的構(gòu)造函數(shù)中調(diào)用超類(lèi)型構(gòu)造函數(shù)。
function SuperType(name) { this.name = name; this.colors = ["pink", "blue", "green"]; } function SubType(name) { SuperType.call(this, name); } let instance1 = new SubType("Yvette"); instance1.colors.push("yellow"); console.log(instance1.colors);//["pink", "blue", "green", yellow] let instance2 = new SubType("Jack"); console.log(instance2.colors); //["pink", "blue", "green"]
優(yōu)點(diǎn):
可以向超類(lèi)傳遞參數(shù)
解決了原型中包含引用類(lèi)型值被所有實(shí)例共享的問(wèn)題
缺點(diǎn):
方法都在構(gòu)造函數(shù)中定義,函數(shù)復(fù)用無(wú)從談起,另外超類(lèi)型原型中定義的方法對(duì)于子類(lèi)型而言都是不可見(jiàn)的。
組合繼承指的是將原型鏈和借用構(gòu)造函數(shù)技術(shù)組合到一塊,從而發(fā)揮二者之長(zhǎng)的一種繼承模式。基本思路:
使用原型鏈實(shí)現(xiàn)對(duì)原型屬性和方法的繼承,通過(guò)借用構(gòu)造函數(shù)來(lái)實(shí)現(xiàn)對(duì)實(shí)例屬性的繼承,既通過(guò)在原型上定義方法來(lái)實(shí)現(xiàn)了函數(shù)復(fù)用,又保證了每個(gè)實(shí)例都有自己的屬性。
function SuperType(name) { this.name = name; this.colors = ["pink", "blue", "green"]; } SuperType.prototype.sayName = function () { console.log(this.name); } function SuberType(name, age) { SuperType.call(this, name); this.age = age; } SuberType.prototype = new SuperType(); SuberType.prototype.constructor = SuberType; SuberType.prototype.sayAge = function () { console.log(this.age); } let instance1 = new SuberType("Yvette", 20); instance1.colors.push("yellow"); console.log(instance1.colors); //[ "pink", "blue", "green", "yellow" ] instance1.sayName(); //Yvette let instance2 = new SuberType("Jack", 22); console.log(instance2.colors); //[ "pink", "blue", "green" ] instance2.sayName();//Jack
缺點(diǎn):
無(wú)論什么情況下,都會(huì)調(diào)用兩次超類(lèi)型構(gòu)造函數(shù):一次是在創(chuàng)建子類(lèi)型原型的時(shí)候,另一次是在子類(lèi)型構(gòu)造函數(shù)內(nèi)部。
優(yōu)點(diǎn):
可以向超類(lèi)傳遞參數(shù)
每個(gè)實(shí)例都有自己的屬性
實(shí)現(xiàn)了函數(shù)復(fù)用
原型繼承的基本思想:
借助原型可以基于已有的對(duì)象創(chuàng)建新對(duì)象,同時(shí)還不必因此創(chuàng)建自定義類(lèi)型。
function object(o) { function F() { } F.prototype = o; return new F(); }
在 object() 函數(shù)內(nèi)部,先穿甲一個(gè)臨時(shí)性的構(gòu)造函數(shù),然后將傳入的對(duì)象作為這個(gè)構(gòu)造函數(shù)的原型,最后返回了這個(gè)臨時(shí)類(lèi)型的一個(gè)新實(shí)例,從本質(zhì)上講,object() 對(duì)傳入的對(duì)象執(zhí)行了一次淺拷貝。
ECMAScript5通過(guò)新增 Object.create()方法規(guī)范了原型式繼承。這個(gè)方法接收兩個(gè)參數(shù):一個(gè)用作新對(duì)象原型的對(duì)象和(可選的)一個(gè)為新對(duì)象定義額外屬性的對(duì)象(可以覆蓋原型對(duì)象上的同名屬性),在傳入一個(gè)參數(shù)的情況下,Object.create() 和 object() 方法的行為相同。
var person = { name: "Yvette", hobbies: ["reading", "photography"] } var person1 = Object.create(person); person1.name = "Jack"; person1.hobbies.push("coding"); var person2 = Object.create(person); person2.name = "Echo"; person2.hobbies.push("running"); console.log(person.hobbies);//[ "reading", "photography", "coding", "running" ] console.log(person1.hobbies);//[ "reading", "photography", "coding", "running" ]
在沒(méi)有必要?jiǎng)?chuàng)建構(gòu)造函數(shù),僅讓一個(gè)對(duì)象與另一個(gè)對(duì)象保持相似的情況下,原型式繼承是可以勝任的。
缺點(diǎn):
同原型鏈實(shí)現(xiàn)繼承一樣,包含引用類(lèi)型值的屬性會(huì)被所有實(shí)例共享。
寄生式繼承是與原型式繼承緊密相關(guān)的一種思路。寄生式繼承的思路與寄生構(gòu)造函數(shù)和工廠模式類(lèi)似,即創(chuàng)建一個(gè)僅用于封裝繼承過(guò)程的函數(shù),該函數(shù)在內(nèi)部已某種方式來(lái)增強(qiáng)對(duì)象,最后再像真地是它做了所有工作一樣返回對(duì)象。
function createAnother(original) { var clone = object(original);//通過(guò)調(diào)用函數(shù)創(chuàng)建一個(gè)新對(duì)象 clone.sayHi = function () {//以某種方式增強(qiáng)這個(gè)對(duì)象 console.log("hi"); }; return clone;//返回這個(gè)對(duì)象 } var person = { name: "Yvette", hobbies: ["reading", "photography"] }; var person2 = createAnother(person); person2.sayHi(); //hi
基于 person 返回了一個(gè)新對(duì)象 -—— person2,新對(duì)象不僅具有 person 的所有屬性和方法,而且還有自己的 sayHi() 方法。在考慮對(duì)象而不是自定義類(lèi)型和構(gòu)造函數(shù)的情況下,寄生式繼承也是一種有用的模式。
缺點(diǎn):
使用寄生式繼承來(lái)為對(duì)象添加函數(shù),會(huì)由于不能做到函數(shù)復(fù)用而效率低下。
同原型鏈實(shí)現(xiàn)繼承一樣,包含引用類(lèi)型值的屬性會(huì)被所有實(shí)例共享。
所謂寄生組合式繼承,即通過(guò)借用構(gòu)造函數(shù)來(lái)繼承屬性,通過(guò)原型鏈的混成形式來(lái)繼承方法,基本思路:
不必為了指定子類(lèi)型的原型而調(diào)用超類(lèi)型的構(gòu)造函數(shù),我們需要的僅是超類(lèi)型原型的一個(gè)副本,本質(zhì)上就是使用寄生式繼承來(lái)繼承超類(lèi)型的原型,然后再將結(jié)果指定給子類(lèi)型的原型。寄生組合式繼承的基本模式如下所示:
function inheritPrototype(subType, superType) { var prototype = object(superType.prototype); //創(chuàng)建對(duì)象 prototype.constructor = subType;//增強(qiáng)對(duì)象 subType.prototype = prototype;//指定對(duì)象 }
第一步:創(chuàng)建超類(lèi)型原型的一個(gè)副本
第二步:為創(chuàng)建的副本添加 constructor 屬性
第三步:將新創(chuàng)建的對(duì)象賦值給子類(lèi)型的原型
至此,我們就可以通過(guò)調(diào)用 inheritPrototype 來(lái)替換為子類(lèi)型原型賦值的語(yǔ)句:
function SuperType(name) { this.name = name; this.colors = ["pink", "blue", "green"]; } //...code function SuberType(name, age) { SuperType.call(this, name); this.age = age; } SuberType.prototype = new SuperType(); inheritPrototype(SuberType, SuperType); //...code
優(yōu)點(diǎn):
只調(diào)用了一次超類(lèi)構(gòu)造函數(shù),效率更高。避免在SuberType.prototype上面創(chuàng)建不必要的、多余的屬性,與其同時(shí),原型鏈還能保持不變。
因此寄生組合繼承是引用類(lèi)型最理性的繼承范式。
10. 隱藏頁(yè)面中的某個(gè)元素的方法有哪些?隱藏類(lèi)型
屏幕并不是唯一的輸出機(jī)制,比如說(shuō)屏幕上看不見(jiàn)的元素(隱藏的元素),其中一些依然能夠被讀屏軟件閱讀出來(lái)(因?yàn)樽x屏軟件依賴(lài)于可訪問(wèn)性樹(shù)來(lái)闡述)。為了消除它們之間的歧義,我們將其歸為三大類(lèi):
完全隱藏:元素從渲染樹(shù)中消失,不占據(jù)空間。
視覺(jué)上的隱藏:屏幕中不可見(jiàn),占據(jù)空間。
語(yǔ)義上的隱藏:讀屏軟件不可讀,但正常占據(jù)空。
完全隱藏
display: none;
HTML5 新增屬性,相當(dāng)于 display: none
視覺(jué)上的隱藏
設(shè)置 posoition 為 absolute 或 fixed,通過(guò)設(shè)置 top、left 等值,將其移出可視區(qū)域。
position:absolute; left: -99999px;
設(shè)置 position 為 relative,通過(guò)設(shè)置 top、left 等值,將其移出可視區(qū)域。
position: relative; left: -99999px; height: 0
設(shè)置 margin 值,將其移出可視區(qū)域范圍(可視區(qū)域占位)。
margin-left: -99999px; height: 0;
縮放
transform: scale(0); height: 0;
移動(dòng) translateX, translateY
transform: translateX(-99999px); height: 0
旋轉(zhuǎn) rotate
transform: rotateY(90deg);
寬高為0,字體大小為0:
height: 0; width: 0; font-size: 0;
寬高為0,超出隱藏:
height: 0; width: 0; overflow: hidden;
opacity: 0;
visibility: hidden;
position: relative; z-index: -999;
再設(shè)置一個(gè)層級(jí)較高的元素覆蓋在此元素上。
clip-path: polygon(0 0, 0 0, 0 0, 0 0);
語(yǔ)義上的隱藏
讀屏軟件不可讀,占據(jù)空間,可見(jiàn)。
11. let、const、var 的區(qū)別有哪些?
聲明方式 | 變量提升 | 暫時(shí)性死區(qū) | 重復(fù)聲明 | 塊作用域有效 | 初始值 | 重新賦值 |
---|---|---|---|---|---|---|
var | 會(huì) | 不存在 | 允許 | 不是 | 非必須 | 允許 |
let | 不會(huì) | 存在 | 不允許 | 是 | 非必須 | 允許 |
const | 不會(huì) | 存在 | 不允許 | 是 | 必須 | 不允許 |
1.let/const 定義的變量不會(huì)出現(xiàn)變量提升,而 var 定義的變量會(huì)提升。
2.相同作用域中,let 和 const 不允許重復(fù)聲明,var 允許重復(fù)聲明。
3.const 聲明變量時(shí)必須設(shè)置初始值
4.const 聲明一個(gè)只讀的常量,這個(gè)常量不可改變。
這里有一個(gè)非常重要的點(diǎn)即是:在JS中,復(fù)雜數(shù)據(jù)類(lèi)型,存儲(chǔ)在棧中的是堆內(nèi)存的地址,存在棧中的這個(gè)地址是不變的,但是存在堆中的值是可以變得。有沒(méi)有相當(dāng)常量指針/指針常量~
const a = 20; const b = { age: 18, star: 500 }
一圖勝萬(wàn)言,如下圖所示,不變的是棧內(nèi)存中 a 存儲(chǔ)的 20,和 b 中存儲(chǔ)的 0x0012ff21(瞎編的一個(gè)數(shù)字)。而 {age: 18, star: 200} 是可變的。
12. 說(shuō)一說(shuō)你對(duì)JS執(zhí)行上下文棧和作用域鏈的理解?在開(kāi)始說(shuō)明JS上下文棧和作用域之前,我們先說(shuō)明下JS上下文以及作用域的概念。
JS執(zhí)行上下文執(zhí)行上下文就是當(dāng)前 JavaScript 代碼被解析和執(zhí)行時(shí)所在環(huán)境的抽象概念, JavaScript 中運(yùn)行任何的代碼都是在執(zhí)行上下文中運(yùn)行。
執(zhí)行上下文類(lèi)型分為:
全局執(zhí)行上下文
函數(shù)執(zhí)行上下文
執(zhí)行上下文創(chuàng)建過(guò)程中,需要做以下幾件事:
創(chuàng)建變量對(duì)象:首先初始化函數(shù)的參數(shù)arguments,提升函數(shù)聲明和變量聲明。
創(chuàng)建作用域鏈(Scope Chain):在執(zhí)行期上下文的創(chuàng)建階段,作用域鏈?zhǔn)窃谧兞繉?duì)象之后創(chuàng)建的。
確定this的值,即 ResolveThisBinding
作用域作用域負(fù)責(zé)收集和維護(hù)由所有聲明的標(biāo)識(shí)符(變量)組成的一系列查詢(xún),并實(shí)施一套非常嚴(yán)格的規(guī)則,確定當(dāng)前執(zhí)行的代碼對(duì)這些標(biāo)識(shí)符的訪問(wèn)權(quán)限?!?摘錄自《你不知道的JavaScript》(上卷)
作用域有兩種工作模型:詞法作用域和動(dòng)態(tài)作用域,JS采用的是詞法作用域工作模型,詞法作用域意味著作用域是由書(shū)寫(xiě)代碼時(shí)變量和函數(shù)聲明的位置決定的。(with 和 eval 能夠修改詞法作用域,但是不推薦使用,對(duì)此不做特別說(shuō)明)
作用域分為:
全局作用域
函數(shù)作用域
塊級(jí)作用域
JS執(zhí)行上下文棧(后面簡(jiǎn)稱(chēng)執(zhí)行棧)執(zhí)行棧,也叫做調(diào)用棧,具有 LIFO (后進(jìn)先出) 結(jié)構(gòu),用于存儲(chǔ)在代碼執(zhí)行期間創(chuàng)建的所有執(zhí)行上下文。
規(guī)則如下:
首次運(yùn)行JavaScript代碼的時(shí)候,會(huì)創(chuàng)建一個(gè)全局執(zhí)行的上下文并Push到當(dāng)前的執(zhí)行棧中,每當(dāng)發(fā)生函數(shù)調(diào)用,引擎都會(huì)為該函數(shù)創(chuàng)建一個(gè)新的函數(shù)執(zhí)行上下文并Push當(dāng)前執(zhí)行棧的棧頂。
當(dāng)棧頂?shù)暮瘮?shù)運(yùn)行完成后,其對(duì)應(yīng)的函數(shù)執(zhí)行上下文將會(huì)從執(zhí)行棧中Pop出,上下文的控制權(quán)將移動(dòng)到當(dāng)前執(zhí)行棧的下一個(gè)執(zhí)行上下文。
以一段代碼具體說(shuō)明:
function fun3() { console.log("fun3") } function fun2() { fun3(); } function fun1() { fun2(); } fun1();
Global Execution Context (即全局執(zhí)行上下文)首先入棧,過(guò)程如下:
偽代碼:
//全局執(zhí)行上下文首先入棧 ECStack.push(globalContext); //執(zhí)行fun1(); ECStack.push(作用域鏈functionContext); //fun1中又調(diào)用了fun2; ECStack.push( functionContext); //fun2中又調(diào)用了fun3; ECStack.push( functionContext); //fun3執(zhí)行完畢 ECStack.pop(); //fun2執(zhí)行完畢 ECStack.pop(); //fun1執(zhí)行完畢 ECStack.pop(); //javascript繼續(xù)順序執(zhí)行下面的代碼,但ECStack底部始終有一個(gè) 全局上下文(globalContext);
作用域鏈就是從當(dāng)前作用域開(kāi)始一層一層向上尋找某個(gè)變量,直到找到全局作用域還是沒(méi)找到,就宣布放棄。這種一層一層的關(guān)系,就是作用域鏈。
如:
var a = 10; function fn1() { var b = 20; console.log(fn2) function fn2() { a = 20 } return fn2; } fn1()();
fn2作用域鏈 = [fn2作用域, fn1作用域,全局作用域]
13. 防抖函數(shù)的作用是什么?請(qǐng)實(shí)現(xiàn)一個(gè)防抖函數(shù)防抖函數(shù)的作用
防抖函數(shù)的作用就是控制函數(shù)在一定時(shí)間內(nèi)的執(zhí)行次數(shù)。防抖意味著N秒內(nèi)函數(shù)只會(huì)被執(zhí)行一次,如果N秒內(nèi)再次被觸發(fā),則重新計(jì)算延遲時(shí)間。
舉例說(shuō)明: 小思最近在減肥,但是她非常吃吃零食。為此,與其男朋友約定好,如果10天不吃零食,就可以購(gòu)買(mǎi)一個(gè)包(不要問(wèn)為什么是包,因?yàn)?strong>包治百病)。但是如果中間吃了一次零食,那么就要重新計(jì)算時(shí)間,直到小思堅(jiān)持10天沒(méi)有吃零食,才能購(gòu)買(mǎi)一個(gè)包。所以,管不住嘴的小思,沒(méi)有機(jī)會(huì)買(mǎi)包(悲傷的故事)... 這就是 防抖。
防抖函數(shù)實(shí)現(xiàn)
事件第一次觸發(fā)時(shí),timer 是 null,調(diào)用 later(),若 immediate 為true,那么立即調(diào)用 func.apply(this, params);如果 immediate 為 false,那么過(guò) wait 之后,調(diào)用 func.apply(this, params)
事件第二次觸發(fā)時(shí),如果 timer 已經(jīng)重置為 null(即 setTimeout 的倒計(jì)時(shí)結(jié)束),那么流程與第一次觸發(fā)時(shí)一樣,若 timer 不為 null(即 setTimeout 的倒計(jì)時(shí)未結(jié)束),那么清空定時(shí)器,重新開(kāi)始計(jì)時(shí)。
function debounce(func, wait, immediate = true) { let timeout, result; // 延遲執(zhí)行函數(shù) const later = (context, args) => setTimeout(() => { timeout = null;// 倒計(jì)時(shí)結(jié)束 if (!immediate) { //執(zhí)行回調(diào) result = func.apply(context, args); context = args = null; } }, wait); let debounced = function (...params) { if (!timeout) { timeout = later(this, params); if (immediate) { //立即執(zhí)行 result = func.apply(this, params); } } else { clearTimeout(timeout); //函數(shù)在每個(gè)等待時(shí)延的結(jié)束被調(diào)用 timeout = later(this, params); } return result; } //提供在外部清空定時(shí)器的方法 debounced.cancel = function () { clearTimeout(timer); timer = null; }; return debounced; };
immediate 為 true 時(shí),表示函數(shù)在每個(gè)等待時(shí)延的開(kāi)始被調(diào)用。immediate 為 false 時(shí),表示函數(shù)在每個(gè)等待時(shí)延的結(jié)束被調(diào)用。
防抖的應(yīng)用場(chǎng)景
搜索框輸入查詢(xún),如果用戶(hù)一直在輸入中,沒(méi)有必要不停地調(diào)用去請(qǐng)求服務(wù)端接口,等用戶(hù)停止輸入的時(shí)候,再調(diào)用,設(shè)置一個(gè)合適的時(shí)間間隔,有效減輕服務(wù)端壓力。
表單驗(yàn)證
按鈕提交事件。
瀏覽器窗口縮放,resize事件(如窗口停止改變大小之后重新計(jì)算布局)等。
14. 節(jié)流函數(shù)的作用是什么?有哪些應(yīng)用場(chǎng)景,請(qǐng)實(shí)現(xiàn)一個(gè)節(jié)流函數(shù)節(jié)流函數(shù)的作用
節(jié)流函數(shù)的作用是規(guī)定一個(gè)單位時(shí)間,在這個(gè)單位時(shí)間內(nèi)最多只能觸發(fā)一次函數(shù)執(zhí)行,如果這個(gè)單位時(shí)間內(nèi)多次觸發(fā)函數(shù),只能有一次生效。
節(jié)流函數(shù)實(shí)現(xiàn)
function throttle(func, wait, options = {}) { var timeout, context, args, result; var previous = 0; var later = function () { previous = options.leading === false ? 0 : (Date.now() || new Date().getTime()); timeout = null; result = func.apply(context, args); if (!timeout) context = args = null; }; var throttled = function () { var now = Date.now() || new Date().getTime(); if (!previous && options.leading === false) previous = now; //remaining 為距離下次執(zhí)行 func 的時(shí)間 //remaining > wait,表示客戶(hù)端系統(tǒng)時(shí)間被調(diào)整過(guò) var remaining = wait - (now - previous); context = this; args = arguments; //remaining 小于等于0,表示事件觸發(fā)的間隔時(shí)間大于設(shè)置的 wait if (remaining <= 0 || remaining > wait) { if (timeout) { //清空定時(shí)器 clearTimeout(timeout); timeout = null; } //重置 previous previous = now; //執(zhí)行函數(shù) result = func.apply(context, args); if (!timeout) context = args = null; } else if (!timeout && options.trailing !== false) { timeout = setTimeout(later, remaining); } return result; }; throttled.cancel = function () { clearTimeout(timeout); previous = 0; timeout = context = args = null; }; return throttled; }
禁用第一次首先執(zhí)行,傳遞 {leading: false} ;想禁用最后一次執(zhí)行,傳遞 {trailing: false}
節(jié)流的應(yīng)用場(chǎng)景
按鈕點(diǎn)擊事件
拖拽事件
onScoll
計(jì)算鼠標(biāo)移動(dòng)的距離(mousemove)
15. 什么是閉包?閉包的作用是什么?《JavaScript高級(jí)程序設(shè)計(jì)》:
閉包是指有權(quán)訪問(wèn)另一個(gè)函數(shù)作用域中的變量的函數(shù)
《JavaScript權(quán)威指南》:
從技術(shù)的角度講,所有的JavaScript函數(shù)都是閉包:它們都是對(duì)象,它們都關(guān)聯(lián)到作用域鏈。
《你不知道的JavaScript》
當(dāng)函數(shù)可以記住并訪問(wèn)所在的詞法作用域時(shí),就產(chǎn)生了閉包,即使函數(shù)是在當(dāng)前詞法作用域之外執(zhí)行。
function foo() { var a = 2; return function fn() { console.log(a); } } let func = foo(); func(); //輸出2
閉包使得函數(shù)可以繼續(xù)訪問(wèn)定義時(shí)的詞法作用域。拜 fn 所賜,在 foo() 執(zhí)行后,foo 內(nèi)部作用域不會(huì)被銷(xiāo)毀。
能夠訪問(wèn)函數(shù)定義時(shí)所在的詞法作用域(阻止其被回收)。
私有化變量
function base() { let x = 10; //私有變量 return { getX: function() { return x; } } } let obj = base(); console.log(obj.getX()); //10
模擬塊級(jí)作用域
var a = []; for (var i = 0; i < 10; i++) { a[i] = (function(j){ return function () { console.log(j); } })(i); } a[6](); // 6
創(chuàng)建模塊
function coolModule() { let name = "Yvette"; let age = 20; function sayName() { console.log(name); } function sayAge() { console.log(age); } return { sayName, sayAge } } let info = coolModule(); info.sayName(); //"Yvette"
模塊模式具有兩個(gè)必備的條件(來(lái)自《你不知道的JavaScript》)
必須有外部的封閉函數(shù),該函數(shù)必須至少被調(diào)用一次(每次調(diào)用都會(huì)創(chuàng)建一個(gè)新的模塊實(shí)例)
封閉函數(shù)必須返回至少一個(gè)內(nèi)部函數(shù),這樣內(nèi)部函數(shù)才能在私有作用域中形成閉包,并且可以訪問(wèn)或者修改私有的狀態(tài)。
16. 實(shí)現(xiàn) Promise.all 方法在實(shí)現(xiàn) Promise.all 方法之前,我們首先要知道 Promise.all 的功能和特點(diǎn),因?yàn)樵谇宄?Promise.all 功能和特點(diǎn)的情況下,我們才能進(jìn)一步去寫(xiě)實(shí)現(xiàn)。
Promise.all 功能
Promise.all(iterable) 返回一個(gè)新的 Promise 實(shí)例。此實(shí)例在 iterable 參數(shù)內(nèi)所有的 promise 都 fulfilled 或者參數(shù)中不包含 promise 時(shí),狀態(tài)變成 fulfilled;如果參數(shù)中 promise 有一個(gè)失敗rejected,此實(shí)例回調(diào)失敗,失敗原因的是第一個(gè)失敗 promise 的返回結(jié)果。
let p = Promise.all([p1, p2, p3]);
p的狀態(tài)由 p1,p2,p3決定,分成以下;兩種情況:
(1)只有p1、p2、p3的狀態(tài)都變成 fulfilled,p的狀態(tài)才會(huì)變成 fulfilled,此時(shí)p1、p2、p3的返回值組成一個(gè)數(shù)組,傳遞給p的回調(diào)函數(shù)。
(2)只要p1、p2、p3之中有一個(gè)被 rejected,p的狀態(tài)就變成 rejected,此時(shí)第一個(gè)被reject的實(shí)例的返回值,會(huì)傳遞給p的回調(diào)函數(shù)。
Promise.all 的特點(diǎn)
Promise.all 的返回值是一個(gè) promise 實(shí)例
如果傳入的參數(shù)為空的可迭代對(duì)象,Promise.all 會(huì) 同步 返回一個(gè)已完成狀態(tài)的 promise
如果傳入的參數(shù)中不包含任何 promise,Promise.all 會(huì) 異步 返回一個(gè)已完成狀態(tài)的 promise
其它情況下,Promise.all 返回一個(gè) 處理中(pending) 狀態(tài)的 promise.
Promise.all 返回的 promise 的狀態(tài)
如果傳入的參數(shù)中的 promise 都變成完成狀態(tài),Promise.all 返回的 promise 異步地變?yōu)橥瓿伞?/p>
如果傳入的參數(shù)中,有一個(gè) promise 失敗,Promise.all 異步地將失敗的那個(gè)結(jié)果給失敗狀態(tài)的回調(diào)函數(shù),而不管其它 promise 是否完成
在任何情況下,Promise.all 返回的 promise 的完成狀態(tài)的結(jié)果都是一個(gè)數(shù)組
Promise.all 實(shí)現(xiàn)
Promise.all = function (promises) { //promises 是可迭代對(duì)象,省略參數(shù)合法性檢查 return new Promise((resolve, reject) => { //Array.from 將可迭代對(duì)象轉(zhuǎn)換成數(shù)組 promises = Array.from(promises); if (promises.length === 0) { resolve([]); } else { let result = []; let index = 0; for (let i = 0; i < promises.length; i++ ) { //考慮到 i 可能是 thenable 對(duì)象也可能是普通值 Promise.resolve(promises[i]).then(data => { result[i] = data; if (++index === promises.length) { //所有的 promises 狀態(tài)都是 fulfilled,promise.all返回的實(shí)例才變成 fulfilled 態(tài) resolve(result); } }, err => { reject(err); return; }); } } }); }17. 請(qǐng)實(shí)現(xiàn)一個(gè) flattenDeep 函數(shù),把嵌套的數(shù)組扁平化
例如:
flattenDeep([1, [2, [3, [4]], 5]]); //[1, 2, 3, 4, 5]
利用 Array.prototype.flat
ES6 為數(shù)組實(shí)例新增了 flat 方法,用于將嵌套的數(shù)組“拉平”,變成一維的數(shù)組。該方法返回一個(gè)新數(shù)組,對(duì)原數(shù)組沒(méi)有影響。
flat 默認(rèn)只會(huì) “拉平” 一層,如果想要 “拉平” 多層的嵌套數(shù)組,需要給 flat 傳遞一個(gè)整數(shù),表示想要拉平的層數(shù)。
function flattenDeep(arr, deepLength) { return arr.flat(deepLength); } console.log(flattenDeep([1, [2, [3, [4]], 5]], 3));
當(dāng)傳遞的整數(shù)大于數(shù)組嵌套的層數(shù)時(shí),會(huì)將數(shù)組拉平為一維數(shù)組,JS能表示的最大數(shù)字為 Math.pow(2, 53) - 1,因此我們可以這樣定義 flattenDeep 函數(shù)
function flattenDeep(arr) { //當(dāng)然,大多時(shí)候我們并不會(huì)有這么多層級(jí)的嵌套 return arr.flat(Math.pow(2,53) - 1); } console.log(flattenDeep([1, [2, [3, [4]], 5]]));
利用 reduce 和 concat
function flattenDeep(arr){ return arr.reduce((acc, val) => Array.isArray(val) ? acc.concat(flattenDeep(val)) : acc.concat(val), []); } console.log(flattenDeep([1, [2, [3, [4]], 5]]));
使用 stack 無(wú)限反嵌套多層嵌套數(shù)組
function flattenDeep(input) { const stack = [...input]; const res = []; while (stack.length) { // 使用 pop 從 stack 中取出并移除值 const next = stack.pop(); if (Array.isArray(next)) { // 使用 push 送回內(nèi)層數(shù)組中的元素,不會(huì)改動(dòng)原始輸入 original input stack.push(...next); } else { res.push(next); } } // 使用 reverse 恢復(fù)原數(shù)組的順序 return res.reverse(); } console.log(flattenDeep([1, [2, [3, [4]], 5]]));18. 請(qǐng)實(shí)現(xiàn)一個(gè) uniq 函數(shù),實(shí)現(xiàn)數(shù)組去重
例如:
uniq([1, 2, 3, 5, 3, 2]);//[1, 2, 3, 5]
法1: 利用ES6新增數(shù)據(jù)類(lèi)型 Set
Set類(lèi)似于數(shù)組,但是成員的值都是唯一的,沒(méi)有重復(fù)的值。
function uniq(arry) { return [...new Set(arry)]; }
法2: 利用 indexOf
function uniq(arry) { var result = []; for (var i = 0; i < arry.length; i++) { if (result.indexOf(arry[i]) === -1) { //如 result 中沒(méi)有 arry[i],則添加到數(shù)組中 result.push(arry[i]) } } return result; }
法3: 利用 includes
function uniq(arry) { var result = []; for (var i = 0; i < arry.length; i++) { if (!result.includes(arry[i])) { //如 result 中沒(méi)有 arry[i],則添加到數(shù)組中 result.push(arry[i]) } } return result; }
法4:利用 reduce
function uniq(arry) { return arry.reduce((prev, cur) => prev.includes(cur) ? prev : [...prev, cur], []); }
法5:利用 Map
function uniq(arry) { let map = new Map(); let result = new Array(); for (let i = 0; i < arry.length; i++) { if (map.has(arry[i])) { map.set(arry[i], true); } else { map.set(arry[i], false); result.push(arry[i]); } } return result; }19. 可迭代對(duì)象有哪些特點(diǎn)
ES6 規(guī)定,默認(rèn)的 Iterator 接口部署在數(shù)據(jù)結(jié)構(gòu)的 Symbol.iterator 屬性,換個(gè)角度,也可以認(rèn)為,一個(gè)數(shù)據(jù)結(jié)構(gòu)只要具有 Symbol.iterator 屬性(Symbol.iterator 方法對(duì)應(yīng)的是遍歷器生成函數(shù),返回的是一個(gè)遍歷器對(duì)象),那么就可以其認(rèn)為是可迭代的。
可迭代對(duì)象的特點(diǎn)具有 Symbol.iterator 屬性,Symbol.iterator() 返回的是一個(gè)遍歷器對(duì)象
可以使用 for ... of 進(jìn)行循環(huán)
通過(guò)被 Array.from 轉(zhuǎn)換為數(shù)組
let arry = [1, 2, 3, 4]; let iter = arry[Symbol.iterator](); console.log(iter.next()); //{ value: 1, done: false } console.log(iter.next()); //{ value: 2, done: false } console.log(iter.next()); //{ value: 3, done: false }原生具有 Iterator 接口的數(shù)據(jù)結(jié)構(gòu):
Array
Map
Set
String
TypedArray
函數(shù)的 arguments 對(duì)象
NodeList 對(duì)象
20. JSONP 的原理是什么?盡管瀏覽器有同源策略,但是 標(biāo)簽的 src 屬性不會(huì)被同源策略所約束,可以獲取任意服務(wù)器上的腳本并執(zhí)行。jsonp 通過(guò)插入 script 標(biāo)簽的方式來(lái)實(shí)現(xiàn)跨域,參數(shù)只能通過(guò) url 傳入,僅能支持 get 請(qǐng)求。
實(shí)現(xiàn)原理:
Step1: 創(chuàng)建 callback 方法
Step2: 插入 script 標(biāo)簽
Step3: 后臺(tái)接受到請(qǐng)求,解析前端傳過(guò)去的 callback 方法,返回該方法的調(diào)用,并且數(shù)據(jù)作為參數(shù)傳入該方法
Step4: 前端執(zhí)行服務(wù)端返回的方法調(diào)用
jsonp源碼實(shí)現(xiàn)
function jsonp({url, params, callback}) { return new Promise((resolve, reject) => { //創(chuàng)建script標(biāo)簽 let script = document.createElement("script"); //將回調(diào)函數(shù)掛在 window 上 window[callback] = function(data) { resolve(data); //代碼執(zhí)行后,刪除插入的script標(biāo)簽 document.body.removeChild(script); } //回調(diào)函數(shù)加在請(qǐng)求地址上 params = {...params, callback} //wb=b&callback=show let arrs = []; for(let key in params) { arrs.push(`${key}=${params[key]}`); } script.src = `${url}?${arrs.join("&")}`; document.body.appendChild(script); }); }
使用:
function show(data) { console.log(data); } jsonp({ url: "http://localhost:3000/show", params: { //code }, callback: "show" }).then(data => { console.log(data); });
服務(wù)端代碼(node):
//express啟動(dòng)一個(gè)后臺(tái)服務(wù) let express = require("express"); let app = express(); app.get("/show", (req, res) => { let {callback} = req.query; //獲取傳來(lái)的callback函數(shù)名,callback是key res.send(`${callback}("Hello!")`); }); app.listen(3000);參考文章:
[1] 珠峰架構(gòu)課(墻裂推薦)
[2] [JavaScript高級(jí)程序設(shè)計(jì)第六章]
[3] Step-By-Step】高頻面試題深入解析 / 周刊01
[4] Step-By-Step】高頻面試題深入解析 / 周刊02
[5] Step-By-Step】高頻面試題深入解析 / 周刊03
[6] Step-By-Step】高頻面試題深入解析 / 周刊04
謝謝各位小伙伴愿意花費(fèi)寶貴的時(shí)間閱讀本文,如果本文給了您一點(diǎn)幫助或者是啟發(fā),請(qǐng)不要吝嗇你的贊和Star,您的肯定是我前進(jìn)的最大動(dòng)力。 https://github.com/YvetteLau/...
關(guān)注公眾號(hào),加入技術(shù)交流群。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/105008.html
摘要:獲取的對(duì)象范圍方法獲取的是最終應(yīng)用在元素上的所有屬性對(duì)象即使沒(méi)有代碼,也會(huì)把默認(rèn)的祖宗八代都顯示出來(lái)而只能獲取元素屬性中的樣式。因此對(duì)于一個(gè)光禿禿的元素,方法返回對(duì)象中屬性值如果有就是據(jù)我測(cè)試不同環(huán)境結(jié)果可能有差異而就是。 花了很長(zhǎng)時(shí)間整理的前端面試資源,喜歡請(qǐng)大家不要吝嗇star~ 別只收藏,點(diǎn)個(gè)贊,點(diǎn)個(gè)star再走哈~ 持續(xù)更新中……,可以關(guān)注下github 項(xiàng)目地址 https:...
摘要:整理收藏一些優(yōu)秀的文章及大佬博客留著慢慢學(xué)習(xí)原文協(xié)作規(guī)范中文技術(shù)文檔協(xié)作規(guī)范阮一峰編程風(fēng)格凹凸實(shí)驗(yàn)室前端代碼規(guī)范風(fēng)格指南這一次,徹底弄懂執(zhí)行機(jī)制一次弄懂徹底解決此類(lèi)面試問(wèn)題瀏覽器與的事件循環(huán)有何區(qū)別筆試題事件循環(huán)機(jī)制異步編程理解的異步 better-learning 整理收藏一些優(yōu)秀的文章及大佬博客留著慢慢學(xué)習(xí) 原文:https://www.ahwgs.cn/youxiuwenzhan...
摘要:寫(xiě)在前面的話最近互聯(lián)網(wǎng)朋友圈充斥著一股恐慌的氣息。本人作為一名,萬(wàn)不敢稱(chēng)資深,只是呆過(guò)幾年大型央企和大型互聯(lián)網(wǎng)企業(yè),聊有一點(diǎn)自己的看法罷了。如果不放心,以一周為期,對(duì)展示在面前的機(jī)會(huì)進(jìn)行初步分級(jí)。也可以略高于期望,以此探一探對(duì)方的反應(yīng)。 showImg(https://segmentfault.com/img/bVblxeY?w=1008&h=298); 寫(xiě)在前面的話 最近互聯(lián)網(wǎng)朋...
摘要:終于,我在看到美團(tuán)的社招信息后,勇敢地邁出了第一步。當(dāng)時(shí)參加的是美團(tuán)點(diǎn)評(píng)部門(mén)的面試,部門(mén)前端技術(shù)棧是,后端用的。后來(lái)才知道美團(tuán)是一次性全部面完的。所以以后有去參加美團(tuán)面試的童鞋,最好做好面試四個(gè)小時(shí)的打算。 showImg(https://segmentfault.com/img/bV0c3T?w=672&h=361); 前言 我叫王小閏(花名),非科班出身,野生前端從業(yè)者,在小公司打...
閱讀 1203·2021-11-17 09:33
閱讀 3599·2021-09-28 09:42
閱讀 3326·2021-09-13 10:35
閱讀 2478·2021-09-06 15:00
閱讀 2438·2021-08-27 13:12
閱讀 3609·2021-07-26 23:38
閱讀 1826·2019-08-30 15:55
閱讀 539·2019-08-30 15:53