1. JS創(chuàng)建變量的5種方式?
- var
- let
- const
- function
- export/import
2. var谎替,let偷溺,const的區(qū)別?
- var聲明的變量會掛載在window上钱贯,而let和const聲明的變量不會挫掏;
- var聲明變量存在變量提升,let和const不存在變量提升秩命;
- let和const聲明形成塊作用域砍濒;
- 同一作用域下let和const不能聲明同名變量,而var可以硫麻;
- const一旦聲明必須賦值,不能使用null占位爸邢;聲明后不能再修改 ;如果聲明的是復(fù)合類型數(shù)據(jù)拿愧,可以修改其屬性杠河;
3. 數(shù)據(jù)類型有哪些?
- 基本數(shù)據(jù)類型:number,string浇辜,boolean券敌,null,undefined柳洋,symbol待诅,bigint;
- 復(fù)雜數(shù)據(jù)類型(引用數(shù)據(jù)類型):object,function熊镣;
4. 如何判斷基本和復(fù)雜類型?
- typeof: 返回結(jié)果是一個字符串;
typeof null => "object"卑雁;
typeof {a: 1} === "object";
typeof [1, 2, 4] === "object";
typeof new Date() === "object";- instanceof:只能檢測引用類型,返回true/false绪囱,例如: arr intanceof Array测蹲;
- constructor:通過原型鏈追溯的數(shù)據(jù)類型,既可以判斷基本數(shù)據(jù)類型又可以判斷引用數(shù)據(jù)類型鬼吵,返回true/false扣甲;
console.log(arr.constructor === Array); //true
console.log(date.constructor === Date); //true
console.log(fn.constructor === Function); //true- Object.prototype.toString.call():由于typeof無法區(qū)分對象、數(shù)組齿椅、函數(shù)的類型;
可以通過Object.prototype.toString.call()方法琉挖,判斷某個對象屬于哪種內(nèi)置類型。
分為null涣脚、string示辈、boolean、number涩澡、undefined顽耳、array坠敷、function、object射富、Date膝迎、math;
返回值為 [object 類型]
?可借鑒來自:https://www.cnblogs.com/onepixel/p/5126046.html
5. 數(shù)據(jù)類型轉(zhuǎn)換有哪些方式胰耗?
分為顯式轉(zhuǎn)換和隱式轉(zhuǎn)化:
①顯式轉(zhuǎn)換
轉(zhuǎn)換為數(shù)值類型:Number(mix)限次、parseInt(string,radix)、parseFloat(string)
轉(zhuǎn)換為字符串類型:toString(radix)柴灯、String(mix)
轉(zhuǎn)換為布爾類型:Boolean(mix)
②隱式轉(zhuǎn)化:
用于檢測是否為非數(shù)值的函數(shù):isNaN(mix)
遞增遞減操作符(包括前置和后置)卖漫、一元正負符號操作符
加法運算操作符
乘除、減號運算符赠群、取模運算符
邏輯操作符(!羊始、&&、||)
關(guān)系操作符(<, >, <=, >=)
相等操作符(==)
?可借鑒來自:https://www.cnblogs.com/Juphy/p/7085197.html
- NaN:not a number:不是一個數(shù)查描,但是屬于number類型突委;
NaN == NaN //false NaN和任何其它值都不相等- isNaN():檢測當前這個值是否是有效數(shù)字,如果不是有效數(shù)字冬三,檢測的結(jié)果是true匀油;反之為false;
isNaN(0)//false isNaN(‘12’) //false isNaN(true) //false
isNaN(NaN)//true isNaN([]) //false- Number():
1??把其它數(shù)據(jù)類型值轉(zhuǎn)化為number類型的值,在使用Number轉(zhuǎn)換的時候只要字符串中出現(xiàn)一個非有效數(shù)字字符勾笆,最后結(jié)果都是NaN敌蚜;
Number('12') //12
Number('12px') //NaN
2??把引用數(shù)據(jù)類型轉(zhuǎn)換為number,首先把引用數(shù)據(jù)類型轉(zhuǎn)換為字符串(toString)窝爪,在把字符串轉(zhuǎn)換為number即可 例如:[]->’’ ‘’->0
Numder([12,23]) => Numder('12,23' ) => NaN
Number({}) //NaN- parseInt():把其它數(shù)據(jù)類型轉(zhuǎn)換為number弛车;
?提取規(guī)則:從左到右依次查找有效數(shù)字字符,直到遇見非有效數(shù)字字符為止(不管后面是否還酸舍,有都不找了)帅韧,把找到的轉(zhuǎn)換為數(shù)字里初;
parseInt('12px') //12- parseFloat():parseInt的基礎(chǔ)上可以識別小數(shù)點;
parseInt('12.53px') //12
parseFlot('12.53px') //12.53- toString():除undefined和null之外的所有類型的值都具有toString()方法啃勉,其作用是返回對象的字符串表示;
6. 基本數(shù)據(jù)類型和復(fù)雜數(shù)據(jù)類型的特點双妨?
- 基本數(shù)據(jù)類型
??變量名在棧上存儲的是具體的數(shù)值
??使用“==”是判斷值是否相等- 復(fù)雜數(shù)據(jù)類型
??變量名棧上存儲的是對象的[內(nèi)存地址]淮阐,內(nèi)容存儲在堆上面
??使用“==”是判斷地址是否相同
7. ES6新增的特性了解和使用有多少?
- let關(guān)鍵字:
相較于var關(guān)鍵字的特點:
①變量不允許被重復(fù)定義
②不會進行變量聲明提升
③保留塊級作用域中- const: 定義常量的特點:①常量值不允許被改變 ②不會進行變量聲明提升
- 解構(gòu)賦值:
1??數(shù)組解構(gòu)
2??對象解構(gòu)- 字符串模板``
- 箭頭函數(shù):
??與普通函數(shù)的區(qū)別:
書寫上用=>代替了function;
普通函數(shù)的this指向window刁品,而ES6箭頭函數(shù)本身沒有this泣特,箭頭函數(shù)的this指向父級作用域的this;
??箭頭函數(shù)的特點:
不需要 function 關(guān)鍵字來創(chuàng)建函數(shù)挑随;
省略 return 關(guān)鍵字状您;
ES6中,箭頭函數(shù)本身沒有this,箭頭函數(shù)繼承父級作用域的 this膏孟;
ES6中眯分,箭頭函數(shù)不存在arguments對象;- 數(shù)組新增的方法:
Array.from():
遍歷數(shù)組元素柒桑,也可遍歷類似數(shù)組的兌現(xiàn)和可遍歷的對象弊决;
Array.of():
將一組值,轉(zhuǎn)換為數(shù)組魁淳;例如:Array.of(3,12,5) ; //[3,12,5]
飘诗;
Array.find():
找出第一個符合條件的數(shù)組成員,他的參數(shù)是一個回調(diào)函數(shù)界逛,所有數(shù)組成員依次執(zhí)行該函數(shù),知道找出第一個返回值為true
的成員昆稿,探后返回該成員,如果沒有符合條件的成員息拜,則返回undefined
貌嫡;例如:[1,3,5,10].find( (n) => n>5)
;
Array.findIndex(當前的值该溯,當前的位置岛抄,原數(shù)組):
用法與find方法非常相似,返回第一歌符合條件的數(shù)組成員的位置,如果所有成員都不符合條件則返回-1;
Array.keys():
對數(shù)組鍵名的遍歷狈茉,返回一個遍歷器對象夫椭,供for ...of 循環(huán)進行遍歷;例如:for (let index of ['a','b'].keys() ) { console.log(index); } //0 //1
氯庆;
Array.values():
對數(shù)組鍵值的遍歷蹭秋,返回一個遍歷器對象,供for ...of 循環(huán)進行遍歷堤撵;例如:for (let elem of ["a","b"].values() ){ console.log(elem); } //'a' //'b'
仁讨;
Array.entries() :
對數(shù)組值對的遍歷,返回一個遍歷器對象实昨,供for ...of 循環(huán)進行遍歷洞豁;例如:for ( let [index,elem] of ['a','b'].entries() ) { console.log(index,elem); } //0 "a" // 1 "b"
;
Array.includes():
在沒有這個方法之前荒给,通常是用indexOf()檢測是否包含某個數(shù)組,indexOf方法的缺點是:不夠語義化丈挟,沒有使用 ===嚴格相等這樣會出現(xiàn)NaN的誤判。[NaN].indexOf(NaN) //-1 [NaN].includes(NaN) //true [1,2,3].includes(2)//true
Array.fill():
使用給定值志电,填充一個數(shù)組窜司,并且fill方法還可以接收第二個和第三個參數(shù)蹋凝,用于指定填充的起始位置和結(jié)束位置;例如:['a','b','c'].fill(6,1,2) //['a',6,'c']
贡歧;
數(shù)組的擴展參考:https://www.imooc.com/article/20567- 對象新增的方法:
Object.is(value1,value2):
判斷兩個參數(shù)是否相等;
Object.assign(target,source):
用于對象的合并,將源對象source
的所有可枚舉屬性,復(fù)制到目標對象target
,也是淺拷貝:Object.assign({}咧叭,obj)
;
擴展運算符:
{a,b,…c}={a:'a',b:'b',c:'c',d:'d'}→c={c:'c',d:'d'}
烁竭;
for…in...:
循環(huán)遍歷出對象返回鍵名key
菲茬;
Object.keys(obj):
返回保存鍵名的字符串數(shù)組['a','b']
,作為遍歷一個對象的補充手段派撕,供for...of...
循環(huán)使用婉弹;
Object.values(obj):
返回保存鍵值的數(shù)組[1,2]
,作為遍歷一個對象的補充手段终吼,供for...of...
循環(huán)使用镀赌;
Object.entries(obj):
返回保存鍵值對的數(shù)組[['a',1],['b',2]]
,作為遍歷一個對象的補充手段际跪,供for...of...
循環(huán)使用商佛;
Object.setPrototypeOf():
該方法的作用與_proto_
相同,用來設(shè)置對象的prototype對象姆打,返回參數(shù)對象本身良姆,他是ES6正式推薦的設(shè)置原型對象的方法Object.setPrototypeOf(obj, proto);
;
Object.getPrototypeOf():
與Object.setPrototypeOf
方法配套幔戏,用于讀取一個對象的原型對象Object.getPrototypeOf(obj);
玛追;
getOwnPropertyDescriptors():
此方法增強了ES5中getOwnPropertyDescriptor()
方法,可以獲取指定對象所有自身屬性的描述對象闲延。結(jié)合defineProperties()
方法痊剖,可以完美復(fù)制對象,包括復(fù)制get和set屬性垒玲;
對象的擴展參考:https://www.imooc.com/article/20569- Symbol用法:這是一種數(shù)據(jù)類型陆馁,表示獨一無二的值;
Symbol參考:https://www.imooc.com/article/20574- ES6提供了Set和Map的數(shù)據(jù)結(jié)構(gòu):
??Set,本質(zhì)與數(shù)組類似合愈。不同在于Set中只能保存不同元素叮贩,如果元素相同會被忽略。且由于 Set 結(jié)構(gòu)沒有鍵名想暗,只有鍵值(或者說鍵名和鍵值是同一個值)妇汗。
Set實例的屬性和方法:
Set結(jié)構(gòu)的實例有屬性:
Set.prototype.constructor:構(gòu)造函數(shù),默認就是Set函數(shù)说莫;
Set.prototype.size:返回Set實例的成員總數(shù);
Set實例的方法分為兩類:操作方法和遍歷方法寞焙;
Set操作方法:
add(value):添加某個值储狭,返回Set結(jié)構(gòu)本身
delete(value):刪除某個值互婿,返回一個布爾值,表示刪除是否成功辽狈;
has(value):返回一個布爾值慈参,表示該值是否為Set的成員;
clear():清除所有成員刮萌,沒有返回值驮配;
Set遍歷操作:
Set結(jié)構(gòu)的實例有四個遍歷方法,可以用于遍歷成員
keys():返回鍵名的遍歷器
values():返回鍵值的遍歷器
entries():返回鍵值對的遍歷器
forEach() :使用回調(diào)函數(shù)遍歷每個成員
∽湃住(1)keys()壮锻,values(),entries()// 接收數(shù)組 let set2 = new Set([12,13,14,15,15]);// 得到[12,13,14,15] set.add(1) //[1,12,13,14,15] set.size //5 set.has(14) //true set.delete(1) //[12,13,14,15] set.clear() //undefined
??Map涮阔,本質(zhì)是與Object類似的結(jié)構(gòu)猜绣。不同在于,Object強制規(guī)定key只能是字符串敬特。而Map結(jié)構(gòu)的key可以是任意對象掰邢。即:
object是 <string,object>集合
map是<object,object>集合
Map操作方法:
size屬性:返回 Map 結(jié)構(gòu)的成員總數(shù)
set():設(shè)置鍵名key對應(yīng)的鍵值為value,然后返回整個 Map 結(jié)構(gòu)伟阔。如果key已經(jīng)有值辣之,則鍵值會被更新,否則就新生成該鍵皱炉。
get():讀取key對應(yīng)的鍵值召烂,如果找不到key,返回undefined娃承。
delete():刪除鍵名奏夫,刪除成功返回true
has():返回一個布爾值,表示某個鍵是否在當前 Map 對象之中历筝。
clear():清除所有
Map遍歷操作:
Map結(jié)構(gòu)原生提供三個遍歷器生成函數(shù)和一個遍歷方法;
keys():返回鍵名的遍歷器
values():返回鍵值的遍歷器
entries():返回所有成員的遍歷器
forEach():遍歷Map的所有成員
Map遍歷的順序就是插入的順序let maap = new Map([['key1','value1'],['key2','value2']]) maap.keys() // MapIterator {"key1", "key2"} maap.values() // MapIterator {"value1", "value2"} maap.size // 2 maap.entries() // MapIterator {"key1" => "value1", "key2" => "value2"} maap.set('key3','value3') // Map(3) {"key1" => "value1", "key2" => "value2", "key3" => "value3"} maap.get('key3') // "value3" maap.delete('key2') // true maap.entries() // MapIterator {"key1" => "value1", "key3" => "value3"}
Set和Map數(shù)據(jù)結(jié)構(gòu)參考:https://www.imooc.com/article/20578
- Promise
1??什么是promise?
將異步任務(wù)同步化的執(zhí)行方式酗昼;
2??promise的基本使用?
通過new promise創(chuàng)建一個promise對象梳猪,里面是一個回調(diào)函數(shù)麻削,回調(diào)函數(shù)中有2個參數(shù)resolve和reject,resolve()當異步執(zhí)行成功的時候調(diào)用的方法春弥,reject()當異步失敗的時候調(diào)用的方法呛哟。
除此之外promise有then方法和catch方法,當成功的時候執(zhí)行.then()匿沛,當失敗的時候執(zhí)行.cath()
.then 函數(shù)會返回一個 Promise 實例扫责,并且該返回值是一個新的實例而不是之前的實例。因為 Promise 規(guī)范規(guī)定除了 pending 狀態(tài)逃呼,其他狀態(tài)是不可以改變的鳖孤,如果返回的是一個相同實例的話者娱,多個 then 調(diào)用就失去意義了。
3??Promise 的優(yōu)勢(特點)苏揣?
對象的狀態(tài)不受外界影響黄鳍;
就在于這個鏈式調(diào)用。我們可以在 then 方法中繼續(xù)寫 Promise 對象并返回平匈,然后繼續(xù)調(diào)用 then 來進行回調(diào)操作框沟,成功用resolve 將異步操作的結(jié)果,作為參數(shù)傳遞出去增炭,失敗用reject返回忍燥;
promise狀態(tài)一經(jīng)改變不會再變;
4??方法
.then() 異步操作成功時執(zhí)行
.cath() 異步操作失敗時執(zhí)行
.all() 當所有的異步代碼都執(zhí)行完畢以后才會執(zhí)行.then中的操作
.race() 只要有一個promise執(zhí)行完畢后就會執(zhí)行.then操作
5??使用場景
主要用于異步計算
可以將異步操作隊列化弟跑,按照期望的順序執(zhí)行灾前,返回符合預(yù)期的結(jié)果
可以在對象之間傳遞和操作promise,幫助我們處理隊列
封裝API接口和異步操作var p1 = new Promise(function(resolve,reject){ setTimeout (function () { console.log('1') resolve() },3000) }) function p2 () { return new Promise(function (resolve,reject){ setTimeout(function(){ console.log('2') resolve() },2000) }) } function p3 () { return new Promise(function(resolve,reject){ setTimeout(function(){ console.log('3') resolve() },1000) }) } function p4() { return new Promise(function (resolve,reject){ setTimeout(function(){ console.log('4') resolve() },500) }) } p1.then(function(){ return p2() }).then(function(){ return p3() }).then(function(){ return p4() }) //1 //2 //3 //4
Promise語法參考:https://www.imooc.com/article/20580
- 類(class)
ES6中提供的類實際上只是JS原型模式包裝孟辑,有了class,對象的創(chuàng)建哎甲,繼承更直觀,父類方法的調(diào)用饲嗽,實例化炭玫,靜態(tài)方法和構(gòu)造函數(shù)更加形象化。//基本定義和生成實例 class Parent{ constructor(name){ this.name = name; } } let parent = new Parent(‘xiaomao’) //繼承 class Child extends Parent{ //子類怎么去覆蓋父類,this一定要放在super后面 constructor(name = ‘child’){ super(name); //若super()貌虾,則所有參數(shù)都是父類的 this.type = ‘child’; //子類增加的屬性 } }
Class的基本語法參考:https://www.imooc.com/article/20596
- Generator函數(shù)
Generator 函數(shù)是 ES6 提供的一種異步編程解決方案吞加,語法行為與傳統(tǒng)函數(shù)完全不同。
形式上尽狠,Generator 函數(shù)是一個普通函數(shù)衔憨,但是有兩個特征。
?function關(guān)鍵字與函數(shù)名之間有一個星號袄膏;
?函數(shù)體內(nèi)部使用yield表達式践图,定義不同的內(nèi)部狀態(tài)(yield在英語里的意思就是“產(chǎn)出”)
?執(zhí)行yield的時候要配合next()使用// 使用 * 表示這是一個 Generator 函數(shù) // 內(nèi)部可以通過 yield 暫停代碼 // 通過調(diào)用 next 恢復(fù)執(zhí)行 function* helloWorldGenerator() { let a = 1 + 2; yield 2; yield 3; } var hw = helloWorldGenerator(); console.log(b.next()); // > { value: 2, done: false } console.log(b.next()); // > { value: 3, done: false } console.log(b.next()); // > { value: undefined, done: true } //上面代碼定義了一個 Generator 函數(shù)helloWorldGenerator,它內(nèi)部有兩個yield表達式(2和3)沉馆,即該函數(shù)有兩個狀態(tài):2码党,3(結(jié)束執(zhí)行)。
Generator函數(shù)的語法參考:https://www.imooc.com/article/20585
Generator函數(shù)的異步應(yīng)用參考:https://www.imooc.com/article/20594
參考原文鏈接:
https://www.cnblogs.com/houjl/p/10087687.html
https://blog.csdn.net/ChinaW_804/article/details/109601546
https://www.cnblogs.com/wasbg/p/11160415.html
8. == 和 === 的區(qū)別?
== 是相對比較斥黑; === 是絕對比較
== 表示相等 (值相等)
===表示恒等(類型和值都要相等)
js在比較的時候如果是 == 會先做類型轉(zhuǎn)換揖盘,再判斷值得大小,如果是===類型和值必須都相等
9. 什么是包裝類锌奴?
js中提供了三種特殊的引用類型(String Number Boolean)每當我們給原始值賦屬性值時 后臺都會給我們偷偷轉(zhuǎn)換 調(diào)用包裝類
??怎么進行“包裝”的兽狭?var str="hello word"; //var str = new String("hello world"); // 1.創(chuàng)建出一個和基本類型值相同的對象 //var long = str.length; // 2.這個對象就可以調(diào)用包裝對象下的方法,并且返回結(jié)給long變量 //str = null; // 3.之后這個臨時創(chuàng)建的對象就被銷毀了 var long=str.length; //因為str沒有l(wèi)ength屬性 所以執(zhí)行這步之前后臺會自動執(zhí)行以上三步操作 console.log(long); // (結(jié)果為:10) //var str = new String("hello word"); // 1.因為下面有輸出創(chuàng)建出str.length 而str不應(yīng)該具有l(wèi)ength這個屬性 所以再次開辟空間創(chuàng)建出一個和基本類型值相同的對象 //str.length=nudefined; // 2.因為包裝對象下面沒有l(wèi)ength這個屬性沒有值,所以值是未定 //str = null; // 3.這個對象又被銷毀了 console.log(str.length) // (結(jié)果為:undefined)
參考原文鏈接:https://blog.csdn.net/qq_41853863/article/details/81227734
10.防抖和節(jié)流的原理椭符,手寫一下?
使用場景和優(yōu)點:
在進行窗口的onscroll,oninput,resize,onkeyup等操作時荔燎,如果事件處理函數(shù)調(diào)用的頻率無限制耻姥,瀏覽器的負擔(dān)會很大销钝,形成卡頓等問題,用戶體驗會非常糟糕琐簇。防抖動和節(jié)流本質(zhì)是不一樣的蒸健。防抖動是將多次執(zhí)行變?yōu)樽詈笠淮螆?zhí)行,節(jié)流是將多次執(zhí)行變成每隔一段時間執(zhí)行
?防抖(debounce):就是指觸發(fā)事件后在 n 秒內(nèi)函數(shù)只能執(zhí)行一次婉商,如果在 n 秒內(nèi)又觸發(fā)了事件似忧,則會重新計算函數(shù)執(zhí)行時間。
防抖函數(shù)原理:在事件被觸發(fā)n秒后再執(zhí)行回調(diào)丈秩,如果在這n秒內(nèi)又被觸發(fā)盯捌,則重新計時。
例如:eg. 像仿百度搜索蘑秽,就應(yīng)該用防抖饺著,當我連續(xù)不斷輸入時,不會發(fā)送請求肠牲;當我一段時間內(nèi)不輸入了幼衰,才會發(fā)送一次請求;如果小于這段時間繼續(xù)輸入的話缀雳,時間會重新計算渡嚣,也不會發(fā)送請求。
適用場景:按鈕提交場景:防止多次提交按鈕肥印,只執(zhí)行最后提交的一次 服務(wù)端驗證場景:表單驗證需要服務(wù)端配合识椰,只執(zhí)行一段連續(xù)的輸入事件的最后一次,還有搜索聯(lián)想詞功能類似深碱;
防抖(debounce)// 防抖:(每次觸發(fā)的事件都會在x秒后被執(zhí)行) function debounce(fn,wait){ let timer =null; return function(){ let context = this; let args = arguments; clearTimeout(timer) timer = setTimeout(function(){ fn.apply(context,args); },wait) } } // 改良防抖:(第一次會立即觸發(fā)腹鹉,之后觸發(fā)的事件會在延遲x秒被執(zhí)行) function debounce(fn,wait){ let timer = null; //null轉(zhuǎn)化成布爾值為false return function(){ let context = this; let args = arguments; //判斷第一次觸發(fā):當timer為null時,是第一次觸發(fā)莹痢; let firstTime = !timer; //!timer -> !null -> !false ->true //timer存在且為true种蘸,就會清楚定時器; if(timer) { clearTimeout(timer) } //第一次觸發(fā):firstTime為true竞膳,就會觸發(fā)執(zhí)行fn(); if (firstTime) { fn.apply(context,args); } //如果timer不存就開啟一個定時器航瞭,在第一次觸發(fā)后延遲wait秒,將timer置空坦辟,相當于延遲wait秒后重新執(zhí)行第一次觸發(fā)的邏輯語句 timer = setTimeout(function(){ timer = null; },wait) } } // 處理函數(shù) function handle() { console.log(Math.random()); } // 滾動事件 window.addEventListener('scroll', debounce(handle, 1000)); //input事件 //function inputTap(e) { // console.log(e.target.value) // } // document.addEventListener('input', inputTap)
當持續(xù)觸發(fā)scroll事件時刊侯,事件處理函數(shù)handle只在停止?jié)L動1000毫秒之后才會調(diào)用一次,也就是說在持續(xù)觸發(fā)scroll事件的過程中锉走,事件處理函數(shù)handle一直沒有執(zhí)行滨彻。
- ?節(jié)流(throttle):就是指連續(xù)觸發(fā)事件但是在 n 秒中只執(zhí)行一次函數(shù)藕届。節(jié)流會稀釋函數(shù)的執(zhí)行頻率。
- 節(jié)流函數(shù)原理:規(guī)定在一個單位時間內(nèi)亭饵,只能觸發(fā)一次函數(shù)休偶。如果這個單位時間內(nèi)觸發(fā)多次函數(shù),只有一次生效辜羊。
- 例:(連續(xù)不斷動都需要調(diào)用時用踏兜,設(shè)一時間間隔),像dom的拖拽八秃,如果用消抖的話碱妆,就會出現(xiàn)卡頓的感覺,因為只在停止的時候執(zhí)行了一次昔驱,這個時候就應(yīng)該用節(jié)流疹尾,在一定時間內(nèi)多次執(zhí)行,會流暢很多骤肛。
適用場景:
拖拽場景:固定時間內(nèi)只執(zhí)行一次纳本,防止超高頻次觸發(fā)位置變動
縮放場景:監(jiān)控瀏覽器resize
動畫場景:避免短時間內(nèi)多次觸發(fā)動畫引起性能問題
節(jié)流(throttle)// 手寫節(jié)流 時間戳版 const throttle = function (fn, delay) { let preTime = Date.now() //或者時間戳 new Date().getTime() return function () { let context = this let args = arguments let curTime = Date.now() if (curTime - preTime >= delay) { //當時間間隔大過于傳入的delay時,才開始執(zhí)行 fn.apply(context , args ) preTime = Date.now() } } } // 手寫節(jié)流 2 定時器版 const throttle2 = function (fn, delay) { let timer = null return function () { //let context = this //let args = arguments if (!timer) { timer = setTimeout(() => { fn.apply(this, arguments) //箭頭函數(shù)的this本身就指向父級作用域的this,也就是 return function () {}作用域里的this clearTimeout(timer) timer = null // 沒有這句話就廢了 }, delay); } } } //手寫節(jié)流3 時間戳+定時器 const throttle3 = function (fn, delay) { var timer = null; var preTime = Date.now(); return function () { var curTime = Date.now(); var remaining = delay - (curTime - preTime ); clearTimeout(timer); if (remaining <= 0) { fn(); preTime = Date.now(); } else { timer = setTimeout(fn, remaining); } } } function handle() { console.log(Math.random()); } window.addEventListener('scroll', throttle(handle, 1000));
參考:
https://blog.csdn.net/qq_36818627/article/details/108023381(專業(yè)版 防抖萌衬,節(jié)流)
https://www.cnblogs.com/magicg/p/12657349.html
https://www.cnblogs.com/xxxx0130/p/13591675.html
https://blog.csdn.net/weixin_45932733/article/details/111528937
11. 對象的深拷貝和淺拷貝
1??為什么需要拷貝饮醇?對象在賦值時,賦的是引用地址秕豫,所以在改變對象內(nèi)屬性值后朴艰,引用對象的變量也會改變;
let a = { a:1 } let b = a; a.a = 3; console.log(b.a) // 3
2??如何實現(xiàn)拷貝混移?(用不同的方式實現(xiàn)拷貝祠墅,用ES3,ES5歌径,ES6)
?深拷貝和淺拷貝的本質(zhì)區(qū)別:假設(shè)B復(fù)制了A毁嗦,當修改A時,看B是否會發(fā)生變化回铛,如果B也跟著變了狗准,說明這是淺拷貝,拿人手短茵肃,如果B沒變腔长,那就是深拷貝,自食其力
- 淺拷貝:利用循環(huán)對象屬性的方式
ES3: fo...in... 循環(huán)遍歷出對象的鍵名
ES5:
ES5中有 Object.getOwnPropertyNames(obj) 相當于ES6中的 Object.keys(obj)验残;
forEach + Object.getOwnPropertyNames(obj) 循環(huán)迭代對象的鍵名并賦值捞附;
ES6:
for...of... + Object.keys(obj) 循環(huán)迭代對象的鍵名并賦值;
for...of... + Object.entries(obj) 循環(huán)迭代對象的鍵值對并賦值;
Object.assign()方法:將所有可枚舉屬性的值從一個或多個源對象復(fù)制到目標對象鸟召,它將返回目標對象胆绊;
ES6的解構(gòu)賦值...;let obj = { a:1, //第一級屬性 b:2, c: { d:4, //第二級屬性 e:[1,2,3,4] } } // 淺拷貝 function simpleClone (obj) { let cloneObj = {}; /*********************************************************************/ // ES3: fo in for (var key in obj) { // key:鍵名 cloneObj[key] = obj[key]; } /*********************************************************************/ //ES6: for of :用于遍歷可迭代的對象 console.log(Object.keys(obj)); //返回保存鍵名的字符串數(shù)組 ['a','b'] console.log(Object.values(obj)); //返回保存鍵值的數(shù)組 [1,2] console.log(Object.entries(obj)); //返回保存鍵值對的數(shù)組 [['a',1],['b',2]] for (let key of Object.keys(obj)) { cloneObj[key] = obj[key]; } for (let [key,value] of Object.entries(obj)) { cloneObj[key] = value; } /*********************************************************************/ //ES5: 有Object.getOwnPropertyNames(obj) 相當于ES6中的 Object.keys(obj) Object.getOwnPropertyNames(obj).forEach(function (key) { cloneObj[key] = obj[key]; }) return cloneObj; } /*********************************************************************/ // Object.assign:第一級屬性深拷貝欧募,以后級別屬性淺拷貝 let obj1 = Object.assign({},obj) //返回的不是一個新對象压状,而是把一個或多個源對象添加到目標對象 //解構(gòu)賦值:第一級屬性深拷貝,以后級別屬性淺拷貝 let obj2 = {...obj}; obj.a = 11; obj.b = 22; obj.c.d = 44; obj.c.e.push(5); console.log(simpleClone(obj)); //{a: 11, b: 22, c: { d: 44, f:[1,2,3,4,5]}} console.log(obj1); // {a: 1, b: 2, c: { d: 44, f:[1,2,3,4,5]}} console.log(obj2); // {a: 1, b: 2, c: { d: 44, f:[1,2,3,4,5]}}
- 深拷貝:
原生JS-遞歸遍歷
JSON.parse 和 JSON.stringify
jquery 的 extend方法 (進入https://www.bootcdn.cn/搜索jQuery槽片,引用jQuery)let obj = { a:1, //第一級子屬性 b:{ c:3,//第二級子屬性 d:{ e:5, //第三級子屬性 f:[1,2,3,4,5,6] } } } // 深拷貝 function deepClone (obj,cloneInit) { // 源對象何缓,初始值 let cloneObj = cloneInit || {}; /*********************************************************************/ //原生js-遞歸遍歷:1.這個函數(shù)在做什么事情肢础? 2.遞歸的出口在哪里还栓?走else分支確定出口 for(let i in obj) { //遍歷一級子屬性 //判斷obj一級子屬性是否有對象,如果有传轰,遞歸復(fù)制 if(typeof obj[i] === 'object' && obj[i] !== null) { //null轉(zhuǎn)化成布爾值為'Object' cloneObj[i] = {}; for(let j in obj[i]) { //遍歷二級子屬性 if(typeof obj[i][j] === 'object' && obj[i][j] !== null) { //判斷obj的二級子屬性是否有對象剩盒,如果有,遞歸復(fù)制 cloneObj[i][j] = {}; for(let k in obj[i][j]) { //遍歷三級子屬性 //由于已知三級屬性沒有對象類型慨蛙,所以簡單復(fù)制 cloneObj[i][j][k] = obj[i][j][k] console.log(cloneObj[i][j][k]) //5 } }else{ //如果不是辽聊,簡單復(fù)制 cloneObj[i][j] = obj[i][j] console.log(cloneObj[i][j]) //3 } } }else{ cloneObj[i] = obj[i] } } /*********************************************************************/ // 改造遞歸遍歷:內(nèi)部調(diào)用自己實現(xiàn)遞歸 + 數(shù)組的判斷 for(let i in obj) { //遍歷一級子屬性 //判斷obj一級子屬性是否有對象,如果有期贫,遞歸復(fù)制 if(typeof obj[i] === 'object' && obj[i] !== null) { //null轉(zhuǎn)化成布爾值為'Object' //cloneObj[i] = {}; //數(shù)組的判斷: cloneObj[i] = Array.isArray(obj[i]) ? [] : {}; // instanceof 只能檢測引用類型跟匆,不能判斷普通數(shù)據(jù)類型 cloneObj[i] = obj[i] instanceof Array ? [] : {}; //Object.prototype.toString.call() 將當前的this指向調(diào)用者,用過返回值判斷 cloneObj[i] = Object.prototype.toString.call(obj[i]) === '[object,Array]' ? [] : {}; deepClone(obj[i],cloneObj[i]) // 源對象,初始值 }else{ //如果obj一級子屬性沒有對象通砍,直接復(fù)制 cloneObj[i] = obj[i] } } /*********************************************************************/ // JSON.parse 和 JSON.stringify return JSON.parse(JSON.stringify(obj)) /*********************************************************************/ //jquery 的 extend方法 // 引用jQuery<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script> // console.log($.extend(true,{},obj)) //extend(是否深拷貝玛臂,目標對象,源對象) return $.extend(true,{},obj); return cloneObj; } let obj1 = deepClone(obj); obj.b.c = 10; obj.b.d.e = 100; obj.b.d.f.push(7) console.log(obj1);
12. 你對閉包的理解? 閉包使用場景?
- 閉包就是:可以在一個內(nèi)層函數(shù)中訪問到其外層函數(shù)的作用域封孙。
創(chuàng)建閉包的最常見的方式就是在函數(shù)內(nèi)創(chuàng)建函數(shù)迹冤,通過函數(shù)訪問函數(shù)的局部變量,利用閉包可以突破作用鏈域。
舉一個簡單地??:function init(){ var name ="Mozilla";// name 是一個被 init 創(chuàng)建的局部變量 function displayName(){// displayName() 是內(nèi)部函數(shù)虎忌,一個閉包 alert(name);// 使用了父函數(shù)中聲明的變量 } displayName(); } init() // displayName() 沒有自己的局部變量泡徙。然而,由于閉包的特性膜蠢,它可以訪問到外部函數(shù)的變量
- 使用場景(離不開兩個特點):
?創(chuàng)建私有變量
?延長變量的生命周期- 特性:
函數(shù)內(nèi)再嵌套函數(shù)
內(nèi)部函數(shù)可以引用外層的參數(shù)和變量
參數(shù)和變量不會被垃圾回收機制回收- 優(yōu)缺點
優(yōu)點是可以避免全局變量的污染堪藐,設(shè)計私有的方法和變量來實現(xiàn)封裝
缺點是閉包會常駐內(nèi)存,會增大內(nèi)存使用量挑围,使用不當很容易造成內(nèi)存泄露礁竞,所以不能濫用閉包,退出函數(shù)之前贪惹,將不使用的局部變量全部刪除苏章。- 作用
讓函數(shù)外部可以操作函數(shù)內(nèi)部的數(shù)據(jù)
讓這些變量始終保持在內(nèi)存中,延長局部變量生命周期
封裝對象的私有屬性和私有方法
參考原文鏈接:
http://www.reibang.com/p/ba4cbf7cbe60
http://www.reibang.com/p/21b0bb221023
13.作用域和作用域鏈?
作用域:就是變量與函數(shù)的可訪問范圍枫绅。
作用域最大的用處:就是隔離變量泉孩,不同作用域下同名變量不會有沖突。
作用域分為:全局作用域并淋,函數(shù)作用域寓搬,塊集作用域。
全局作用域:編寫在script標簽中的js代碼县耽,都在全局作用域句喷。全局作用域在頁面打開時創(chuàng)建,在頁面關(guān)閉時銷毀兔毙。在全局作用域中有一個全局對象window唾琼,代表瀏覽器窗口,由瀏覽器創(chuàng)建澎剥,可以直接使用锡溯。我們創(chuàng)建的全局變量會作為window對象的屬性保存,創(chuàng)建的方法都會作為window對象的方法保存
函數(shù)作用域:用函數(shù)時創(chuàng)建哑姚,函數(shù)執(zhí)行完畢后銷毀祭饭,每調(diào)用一次函數(shù)就會創(chuàng)建一個函數(shù)作用域,相互獨立叙量。在函數(shù)作用域中可以訪問到全局作用域的變量倡蝙,在全局作用域中不能訪問到函數(shù)作用域的變量。當在函數(shù)作用域中操作某個變量時绞佩,先在自身作用域中尋找這個變量寺鸥,沒有的話再向上一級作用域找,直到找到全局作用域(也就是順著作用域鏈找)如果在函數(shù)作用域里有和全局作用域里同名的變量征炼,但是要訪問全局作用域里的變量析既,可通過window對象調(diào)用
塊集作用域:一般是通過let聲明和const聲明產(chǎn)生的變量,在所屬的塊的作用域外不能訪問谆奥。塊通常是一個被花括號包含的代碼塊眼坏。優(yōu)點是使外部作用域不能訪問內(nèi)部作用域的變量,規(guī)避命名沖突
作用域鏈:當在函數(shù)作用域中操作某個變量時酸些,先在自身作用域中尋找這個變量宰译,沒有的話再向上一級作用域找,直到找到全局作用域(也就是順著作用域鏈找)若還是沒找到魄懂,就宣布放棄沿侈。這種一層一層的關(guān)系,就是 作用域鏈 市栗。
參考原文:http://www.reibang.com/p/ba4cbf7cbe60
https://www.cnblogs.com/leftJS/p/11067908.html
14.JavaScript垃圾回收機制的了解?
JavaScript的解釋器可以檢測到什么時候程序不再使用這個對象了(數(shù)據(jù))缀拭,就會把它所占用的內(nèi)存釋放掉咳短。
針對JavaScript的回收機制有兩種方法(常用):標記清除,引用計數(shù)蛛淋。
1??標記清除:當變量進入到執(zhí)行環(huán)境時咙好,垃圾回收器就會將其標記為“進入環(huán)境”,當變量離開環(huán)境時褐荷,就會將其標記為“離開環(huán)境”勾效。
2??引用計數(shù):引用計數(shù)的含義是跟蹤記錄每個值被引用的次數(shù)。當聲明了一個變量并將一個引用類型賦值給該變量時叛甫,則這個值的引用次數(shù)就是1层宫。相反,如果包含對這個值引用的變量又取得了另外一個值其监,則這個值的引用次數(shù)就減1萌腿。當這個引用次數(shù)變成0時,則說明沒有辦法再訪問這個值了棠赛,因而就可以將其所占的內(nèi)存空間給收回來哮奇。
?引用計數(shù)有個最大的問題: 循環(huán)引用。
參考原文:http://www.reibang.com/p/fddd86d545b6
?內(nèi)存泄漏:在某些情況下睛约,不再使用到的變量所占用內(nèi)存沒有及時釋放,導(dǎo)致程序運行中哲身,內(nèi)存越占越大辩涝,極端情況下可以導(dǎo)致系統(tǒng)崩潰
15.柯里化函數(shù)?
柯里化是指這樣一個函數(shù)(假設(shè)叫做createCurry),他接收函數(shù)A作為參數(shù)勘天,運行后能夠返回一個新的函數(shù)怔揩。并且這個新的函數(shù)能夠處理函數(shù)A的剩余參數(shù)。
柯里化的目的:在于避免頻繁調(diào)用具有相同參數(shù)函數(shù)的同時脯丝,又能夠輕松的重用商膊。// 假設(shè)我們有一個求長方形面積的函數(shù) function getArea(width, height){ return width * height } // 如果我們碰到的長方形的寬老是10 const area1 = getArea(10,20) const area2 = getArea(10,30) const area3 = getArea(10,40) // 我們可以使用閉包柯里化這個計算面積的函數(shù) function getArea(width){ return height=>{ return width * height } } const getTenWidthArea = getArea(10) // 之后碰到寬度為10的長方形就可以這樣計算面積 const area1 = getTenWidthArea(20) // 而且如果遇到寬度偶爾變化也可以輕松復(fù)用 const getTwentyWidthArea = getArea(20)
??柯里化函數(shù)的運行過程:其實是一個參數(shù)的收集過程,我們將每一次傳入的參數(shù)收集起來宠进,并在最里層里面處理晕拆。
??柯里化確實是把簡答的問題復(fù)雜化了,但是復(fù)雜化的同時材蹬,我們使用函數(shù)擁有了
更加多的自由度实幕。而這里對于函數(shù)參數(shù)的自由處理,正是柯里化的核心所在堤器。舉個??:我們還可能會遇到驗證身份證號昆庇,驗證密碼等各種驗證信息。因此在實踐中闸溃,為了統(tǒng)一邏輯整吆,我們就會封裝一個更為通用的函數(shù)拱撵,將用于驗證的正則與將要被驗證的字符串作為參數(shù)傳入。
function check(targetString, reg) { return reg.test(targetString); }
但是這樣封裝之后表蝙,在使用時又會稍微麻煩一點裕膀,因為會總是輸入一串正則,這樣就導(dǎo)致了使用時的效率低下勇哗。
check(/^1[34578]\d{9}$/, '14900000088'); check(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/, 'test@163.com');
這個時候昼扛,我們就可以借助柯里化,在check的基礎(chǔ)上再做一層封裝欲诺,以簡化使用抄谐。
//封裝如下: // 簡單實現(xiàn),參數(shù)只能從右到左傳遞 function createCurry(func, args) { var arity = func.length; var args = args || []; return function() { var _args = [].slice.call(arguments); [].push.apply(_args, args); // 如果參數(shù)個數(shù)小于最初的func.length扰法,則遞歸調(diào)用蛹含,繼續(xù)收集參數(shù) if (_args.length < arity) { return createCurry.call(this, func, _args); } // 參數(shù)收集完畢,則執(zhí)行func return func.apply(this, _args); } } //這個createCurry函數(shù)的封裝借助閉包與遞歸塞颁,實現(xiàn)了一個參數(shù)收集浦箱,并在收集完畢之后執(zhí)行所有參數(shù)的一個過程。 var _check = createCurry(check); var checkPhone = _check(/^1[34578]\d{9}$/); var checkEmail = _check(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/);
最后在使用的時候就會變得更加直觀與簡潔了祠锣。
checkPhone('183888888'); checkEmail('xxxxx@test.com');
經(jīng)過這個過程我們發(fā)現(xiàn)酷窥,柯里化能夠應(yīng)對更加復(fù)雜的邏輯封裝。當情況變得多變伴网,柯里化依然能夠應(yīng)付自如蓬推。
參考原文:
http://www.reibang.com/p/2975c25e4d71
http://www.reibang.com/p/5e1899fe7d6b
http://www.reibang.com/p/21b0bb221023
16. 構(gòu)造函數(shù)和普通函數(shù)的區(qū)別?
1??首字母大寫
2??可以通過new 關(guān)鍵字的方式執(zhí)行澡腾。new的方式:是指在函數(shù)內(nèi)部能夠?qū)his指向?qū)嵗蟮膶ο?br> 3??構(gòu)造函數(shù)通過new關(guān)鍵字執(zhí)行后沸伏,函數(shù)內(nèi)部的this就會構(gòu)造函數(shù)創(chuàng)建的實例對象,也就是foo动分。所以在實例上就掛了a等于1的屬性毅糟。function Foo () { this.a = 1; } let foo = new Foo(); console.log(foo);
構(gòu)造函數(shù)
17.JavaScript原型(其實就是原型對象),原型鏈 ? 有什么特點澜公?
?原型對象是所有實例的公共祖先姆另。
?所有實例可以拿到原型對象上的公共屬性和方法。
?ptototype玛瘸,-proto-蜕青,constructor之間的三角戀:
ptototype哟旗,-proto-都指向的是原型等太;
prototype:通過構(gòu)造函數(shù)的方式指向原型;Foo.prototype
-proto-:通過實例的方式指向原型丛晌;foo.-proto-
constructor:原型內(nèi)置的一個屬性渺绒,原型的constructor屬性指向都是構(gòu)造函數(shù)本身贺喝。function Foo () { this.a = 1; } // 重新定義原型 Foo.prototype = { aa:10, bb:function () { console.log('20') }, //constructor:Foo 重寫原型對象菱鸥,會導(dǎo)致原型對象的 constructor 屬性指向 Object ,導(dǎo)致原型鏈關(guān)系混亂躏鱼,所以我們應(yīng)該在重寫原型對象的時候指定 constructor( instanceof 仍然會返回正確的值) } let foo = new Foo(); console.log(foo.aa); //10 foo.bb() //20 console.log(foo) console.log(Foo.prototype === foo.__proto__) //true console.log(Object.prototype === foo.__proto__.__proto__) //true //沒有重新定義原型之前: console.log(Foo.prototype.constructor === Foo) //true console.log(foo.constructor === Foo ) //true 因為實例可以訪問原型上的屬性和方法 //但是氮采,在重新定義原型后: // 由于沒有寫constructor:Foo這句話(意思是在實例的原型上定義構(gòu)造函數(shù)),所以在實例的原型中沒有找到構(gòu)造函數(shù)就會向原型的原型中查找染苛,最終在原型的原型中找到構(gòu)造函數(shù)Object并返回鹊漠; console.log(foo.constructor ) //Object 因為我們重寫原型后,并沒有定義構(gòu)造函數(shù)茶行,所以它會沿著原型鏈繼續(xù)查找躯概,拿到父級的父級中的構(gòu)造函數(shù)Object;
原型對象
重新定義原型后,實例foo.constructor
不推薦使用實例的方式訪問原型(foo.-proto-)畔师,一般情況下推薦使用構(gòu)造函數(shù)的方式訪問(Foo.prototype)娶靡,如果非要通過實例的方式訪問原型,可以使用ES6中的Object.getPrototypeOf(foo)
console.log(Foo.prototype === Object.getPrototypeOf(foo)) //true
?原型鏈:簡單理解就是原型組成的鏈看锉,每個對象(實例對象 new Preson)都有一個proto屬性指向它的原型姿锭,而原型也是一個對象,也有proto屬性伯铣,原型的proto又指向它自己的原型呻此,就這樣可以一直通過proto向上找,這就是原型鏈懂傀,當向上找到Object的原型的時候趾诗,這條原型鏈就算到頭了。
參考原文:原型鏈
知乎:https://zhuanlan.zhihu.com/p/62903507
https://www.imooc.com/article/275399?block_id=tuijian_wz
https://blog.csdn.net/haitunmin/article/details/78418656
18. 對象的繼承
對象的繼承:對象的繼承只能繼承自身原型上的屬性和方法蹬蚁;
假設(shè):我們想通過sub1獲取到Super構(gòu)造函數(shù)和它原型上的屬性和方法,這時候我們就需要用到對象的繼承郑兴。
原型鏈繼承:由于對象繼承只能繼承自身原型上的屬性和方法犀斋,所以我們通過改變原型鏈來實現(xiàn)原型鏈繼承。
- 原型鏈繼承 : 會出現(xiàn)引用值共享的問題情连;
- 構(gòu)造函數(shù)繼承:解決原型鏈繼承中引用值共享的問題叽粹,無法調(diào)用原型上的方法;
- 組合式繼承(偽金典繼承):解決引用值共享的問題和無法調(diào)用原型上的方法的問題却舀,但‘偽’又造成了構(gòu)造函數(shù)復(fù)用的問題虫几;
- 寄生組合繼承(金典繼承):通過Object.create() 創(chuàng)建了父類原型的副本,與父類原型完全隔離挽拔,解決偽金典繼承中構(gòu)造函數(shù)復(fù)用的問題;
- 圣杯布局:主要思想是利用一個臨時函數(shù)作為中間層以及原型鏈的方式實現(xiàn)繼承辆脸;將子對象的 prototype 指向父對象的 prototype;
- 拷貝繼承:如果把父對象的所有屬性和方法,拷貝進子對象螃诅;
- ES6 Class通過 extends 關(guān)鍵字實現(xiàn)繼承啡氢;
1??原型鏈繼承
//父類構(gòu)造函數(shù) function Super () { //this.a = '111'; this.a = [1,2,3,4]; } Super.prototype.say = function () { console.log(222) } //子類構(gòu)造函數(shù) function Sub () { } // 通過原型鏈繼承:由于sub1只能拿到自身原型上的屬性和方法状囱,所以將原型指向另一個對象的實例,讓對象的實例自身去繼承原型相關(guān)的屬性倘是; Sub.prototype = new Super(); //所以我們現(xiàn)在的原型鏈是:sub1 -> Sub.prototype(new Super) -> Super.prototype // let sub1 = new Sub(); // console.log(sub1.a) //111 // sub1.say() //222 // 原型鏈繼承存在弊端:引用值共享的問題亭枷; let sub1 = new Sub(); let sub2 = new Sub(); //此時修改sub1中屬性a的值 sub1.a = '333'; console.log(sub1.a) //333 console.log(sub2.a) //111 //從上面的結(jié)果看不出任何問題,但是搀崭,如果Super構(gòu)造函數(shù)中保存的屬性a是引用類型值叨粘,a=[1,2,3,4] sub1.a.push(5); console.log(sub1.a) // [1,2,3,4,5] console.log(sub2.a) // [1,2,3,4,5]
由于原型鏈繼承的特性,所以并不能保證引用類型的原始值不被修改瘤睹,但能用構(gòu)造函數(shù)繼承解決引用值共享的問題升敲;
2??構(gòu)造函數(shù)繼承:解決原型鏈繼承中引用值共享的問題;
用.call()和.apply()將父類構(gòu)造函數(shù)引入子類函數(shù),等于復(fù)制父類的實例屬性給子類默蚌;創(chuàng)建子類實例時冻晤,可以向父類傳遞參數(shù),可以實現(xiàn)多繼承绸吸,可以方便的繼承父類型的屬性;//父類構(gòu)造函數(shù) function Super () { //this.a = '111'; this.a = [1,2,3,4]; } Super.prototype.say = function () { console.log(222) } //子類構(gòu)造函數(shù) function Sub () { Super.call(this) //將Super內(nèi)部的this指向Sub創(chuàng)建的實例對象 } // 原型鏈繼承存在弊端:引用值共享的問題鼻弧; let sub1 = new Sub(); let sub2 = new Sub(); //此時修改sub1中屬性a的值 sub1.a = '333'; console.log(sub1.a) //333 console.log(sub2.a) // [1,2,3,4] // 以上結(jié)果,更改a的值不會有影響 sub1.a.push(5); console.log(sub1.a) // [1,2,3,4,5] console.log(sub2.a) // [1,2,3,4] // 以上結(jié)果锦茁,用push的方式更改a的值也不會有影響 // 構(gòu)造函數(shù)繼承的弊端:無法調(diào)用say方法攘轩,也就是無法調(diào)用原型上的方法;
雖然構(gòu)造函數(shù)通過改變this的指向解決了引用值共享的問題码俩,但是卻無法調(diào)用原型上的方法度帮;
3??組合式繼承(偽金典繼承):解決引用值共享的問題和無法調(diào)用原型上的方法的問題;//父類構(gòu)造函數(shù) function Super () { //this.a = '111' this.a = [1,2,3,4] } Super.prototype.say = function () { console.log(222) } //子類構(gòu)造函數(shù) function Sub () { // 構(gòu)造函數(shù)繼承 Super.call(this) } // 原型鏈繼承 Sub.prototype = new Super() let sub1 = new Sub() let sub2 = new Sub() sub.a = '333' console.log(sub1.a) // 333 console.log(sub2.a) // [1,2,3,4] // 以上結(jié)果稿存,普通賦值去更改a的值不會有影響 sub1.a.push(5) console.log(sub1.a) // [1,2,3,4,5] console.log(sub2.a) // [1,2,3,4] // 以上結(jié)果笨篷,用push的方式更改a的值也不會有影響 sub1.say() // 222 sub2.say() // 222 // 以上結(jié)果,調(diào)用原型上的方法也不會有影響
既然偽金典繼承把原型鏈繼承和構(gòu)造函數(shù)繼承結(jié)合的這么好瓣履,那么為什么還是‘偽’呢率翅?
那偽金典到底‘偽’在哪呢,就‘偽’在他當前調(diào)用了兩次Super實例袖迎,所以你看到的Super實際上執(zhí)行了兩次冕臭,我們通過指定原型鏈的方式繼承Super執(zhí)行了一次,然后在new Sub() 的時候燕锥,也就是在Sub中執(zhí)行.call()的時候辜贵,變相的在執(zhí)行了一次。
偽金典繼承的弊端:構(gòu)造函數(shù)復(fù)用的問題归形;
4??寄生組合繼承(金典繼承):解決偽金典繼承中構(gòu)造函數(shù)復(fù)用的問題托慨;//父類構(gòu)造函數(shù) function Super () { //this.a = '111' this.a = [1,2,3,4] } Super.prototype.say = function () { console.log(222) } //子類構(gòu)造函數(shù) function Sub () { // 構(gòu)造函數(shù)繼承 Super.call(this) } // Sub.prototype = new Super() // 我們希望Sub的原型== Super的實例,也就是能直接獲取到Super原型身上的方法 // Object.create()方法創(chuàng)建一個新對象连霉,使用現(xiàn)有的對象來提供新創(chuàng)建的對象的 __proto__ if(!Object.create){// 兼容方案榴芳,如果Object.create這個不存在 Object.create = function (proto) { //這里傳入的proto是一個對象 function F () { } F.prototype = proto return new F() } } // Sub.prototype.subSay = function () { //如果Sub原型的方法寫在重新定義Sub.prototype之前嗡靡,該方法就會被覆蓋 // console.log(444) //} //Object.create是創(chuàng)建了父類原型的副本,與父類原型完全隔離 Sub.prototype = Object.create(Super.prototype) //這是ES5的方法窟感,所以需要些兼容方案 //由于上面我們重寫了Sub.prototype的原型讨彼,導(dǎo)致經(jīng)典繼承拿不到Sub原型上的方法,但將自定義的subSay放法寫在 重定義Sub.prototype的原型之后就能拿到; Sub.prototype.subSay = function () { //如果Sub原型的方法寫在重新定義Sub.prototype之前柿祈,該方法就會被覆蓋 console.log(444) } // 注意記得把子類的構(gòu)造指向子類本身 Sub.prototype.constructor = Super; let sub1 = new Sub() let sub2 = new Sub() sub1.a = '333' console.log(sub1.a) // 333 console.log(sub2.a) // [1,2,3,4] // 以上結(jié)果哈误,普通賦值去更改a的值不會有影響 sub1.a.push(5) console.log(sub1.a) // [1,2,3,4,5] console.log(sub2.a) // [1,2,3,4] // 以上結(jié)果,用push的方式更改a的值也不會有影響 sub1.say() // 222 sub2.say() // 222 // 以上結(jié)果躏嚎,調(diào)用原型上的方法也不會有影響
5??圣杯布局
主要思想是利用一個臨時函數(shù)作為中間層以及原型鏈的方式實現(xiàn)繼承蜜自。//第一種方法 function inherit( Target , Origin){ function F(){}; F.prototype = Origin.prototype; //不能與上邊一行互換位置 Target.prototype = new F(); //要讓Target的構(gòu)造函數(shù)constructor歸位 Target.prototype.constructor = Target; //使構(gòu)造出的對象能夠找到自己超級父級是誰,就是真正繼承于誰 Target.prototype.uber= Origin.prototype; } /***********************************************************************************/ // //第二種使用立即執(zhí)行函數(shù) var inherit = (function(){ //聲明局部變量F var F =function (){} ; //返回的匿名函數(shù)使用上邊局部變量F,形成閉包,返回給inherit return function(Target,Origin){ F.prototype = Origin.prototype; Target.prototype = new F(); //要讓Target的構(gòu)造函數(shù)constructor歸位 Target.prototype.constructor = Target; //使構(gòu)造出的對象能夠找到自己超級父級是誰,就是真正繼承于誰 Target.prototype.uber = Origin.prototype; } }())
6??拷貝繼承:如果把父對象的所有屬性和方法,拷貝進子對象
function extend(Child, Parent) { var p = Parent.prototype; var c = Child.prototype; for (var i in p) { c[i] = p[i]; } c.uber = p; }
7??ES6 Class通過 extends 關(guān)鍵字實現(xiàn)繼承
class 實現(xiàn)繼承的核心在于使用 extends 表明繼承自哪個父類卢佣,并在子類構(gòu)造函數(shù)中必須調(diào)用 superclass Super { constructor(value) { this.a= value; } say() { console.log(this.a); } } class Sub extends Super{ constructor(value) { super(value); // // 調(diào)用父類的constructor(value) //this.a = value; } subSay() { console.log(this.a); } } let sub1 = new Sub([1,2,3,4]); let sub2 = new Sub([1,2,3,4]); sub1.a.push(5) console.log(sub1.a) //[1,2,3,4,5] console.log(sub2.a) // [1,2,3,4] sub1.say() //[1,2,3,4,5] sub2.say() // [1,2,3,4] sub1.subSay() //[1,2,3,4,5] sub2.subSay() // [1,2,3,4] sub1 instanceof Super // true
19. ES5和ES6繼承的區(qū)別
- ES5的繼承實質(zhì)上是先創(chuàng)建子類的實例對象重荠,然后再將父類的方法添加到this上Parent.apply(this)
- ES6的繼承機制實質(zhì)上是先創(chuàng)建父類的實例對象this(所以必須先調(diào)用父類的super()方法),然后再用子類的構(gòu)造函數(shù)修改this,使得父類的所有行為都可以繼承虚茶。
- ES5的繼承時通過原型或構(gòu)造函數(shù)機制來實現(xiàn)戈鲁。
- ES6通過class關(guān)鍵字定義類嘹叫,有構(gòu)造方法婆殿,類之間通過extends關(guān)鍵字實現(xiàn)繼承。子類必須在constructor方法中調(diào)用super方法罩扇,否則新建實例報錯婆芦。因為子類沒有自己的this對象,而是繼承了父類的this對象喂饥,然后對其進行加工消约。如果不調(diào)用super方法,子類得不到this對象员帮。
參考原文:http://www.reibang.com/p/ba4cbf7cbe60
20. this指向
- 默認綁定規(guī)則:this指向window荆陆;
?默認綁定:函數(shù)獨立調(diào)用時,this指向window集侯,立即執(zhí)行函數(shù)也屬于函數(shù)獨立調(diào)用;
?默認綁定:嚴格模式下this指向undefined帜消,非嚴格模式this指向window- 隱式綁定規(guī)則:指對象調(diào)用棠枉, obj.foo() 誰調(diào)用this就指向誰;(隱式丟失泡挺,參數(shù)賦值)
- 顯示綁定規(guī)則:call辈讶,apply,bind;
- new 綁定:this指向?qū)嵗齽?chuàng)建后的對象娄猫;
- 箭頭函數(shù)綁定:箭頭函數(shù)本身是不存在this的贱除,箭頭函數(shù)的this是父作用域的this生闲,不是調(diào)用時的this,其他方法的this是動態(tài)的月幌,而箭頭函數(shù)的this是靜態(tài)的碍讯;
??優(yōu)先級:箭頭函數(shù)>new綁定>顯示綁定/apply/bind/call>隱式綁定>默認綁定var obj = { a: 1, foo: function (){ console.log(this) //函數(shù)獨立調(diào)用 function test () { console.log(this) } test() //函數(shù)獨立調(diào)用 (function () {console.log(this)})() //window // 閉包:當函數(shù)執(zhí)行的時候,導(dǎo)致函數(shù)被定義扯躺,并拋出 function fn () { console.log(this) //window } return fn; } } obj.foo(); // 閉包調(diào)用: obj.foo()(); // 相當于fn(); 就相當于函數(shù)獨立調(diào)用 /**********************************************************************/ // 隱式丟失:(相當于參數(shù)賦值) function foo () { console.log(this) } var obj = { a:1, foo:foo } obj.foo(); //obj var bar = obj.foo; //相當于參數(shù)賦值捉兴,var bar = function foo () {console.log(this)} bar(); //window var bar = foo; bar(); //window /**********************************************************************/ // 參數(shù)(形參)賦值: function foo () { console.log(this) //window } // bar-父函數(shù),fn-形參(子函數(shù))或(回調(diào)函數(shù)) function bar (fn) { fn(); //function foo () {console.log(this)} 相當于函數(shù)獨立調(diào)用 // 如果想要this指向obj,可以有多種方法改變this指向 fn.call(obj); //fn.apply(obj); //fn.bind(obj)(); } // 父函數(shù)有能力決定 子函數(shù)this 的指向 var obj = { a:2, foo:foo } bar(obj.foo) /**********************************************************************/ // new 綁定: function Person () { //let this = {}; //this.a = 1; //return this; // 這里的this指的是當前實例化后的對象(person) //return 1; //這里的this指向?qū)嵗齽?chuàng)建后的person return {}; //如果返回引用類型录语,this指向的是空對象{} } let person = new Person(); console.log(person); /**********************************************************************/ // 箭頭函數(shù): window.name='a' const obj={ name:'b', age:22, getName:()=>{ console.log(this) console.log(this.name) }, getAge:function(){ setTimeout(()=>{ console.log(this.age) }) } } obj.getName();//window a 箭頭函數(shù)的this指向父級作用域的this倍啥,不是調(diào)用它的this obj.getAge();//22 /**********************************************************************/ // 練習(xí): var name = 'window'; var obj1 = { name:'1', fn1:function () { console.log(this.name) }, fn2: () => console.log(this.name), fn3:function () { return function () { console.log(this.name) } }, fn4:function () { return () => console.log(this.name) } } var obj2 = { name:'2' } obj1.fn1(); // 1 對象調(diào)用,誰調(diào)用就指向誰 obj1.fn1.call(obj2); // 2 顯示綁定規(guī)則 > 隱式綁定中的對象調(diào)用 obj1.fn2(); // window 箭頭函數(shù)不存在this澎埠,直接找父作用域里的this obj1.fn2.call(obj2); // window 箭頭函數(shù)的優(yōu)先級最高虽缕,顯示綁定無法改變箭頭函數(shù)的this規(guī)則 obj1.fn3()(); // window 返回一個普通函數(shù),這個函數(shù)獨立調(diào)用 obj1.fn3().call(obj2); // 2 普通函數(shù)通過顯示綁定改變this指向 obj1.fn3.call(obj2)(); // window 執(zhí)行obj1.fn3.call(obj2) 使父函數(shù)通過顯示綁定規(guī)則改變this指向蒲稳,但子函數(shù)獨立調(diào)用氮趋,所以是window obj1.fn4()(); // 1 父函數(shù)對象調(diào)用,this指向obj1弟塞,子函數(shù)是箭頭函數(shù)且獨自調(diào)用凭峡,this指向父級作用域 obj1.fn4().call(obj2); // 1 父函數(shù)對象調(diào)用,this指向obj1决记,但子函數(shù)是箭頭函數(shù)摧冀,由于箭頭函數(shù)優(yōu)先級最高,顯示綁定不能改變this規(guī)則 obj1.fn4.call(obj2)(); // 2 執(zhí)行obj1.fn4.call(obj2) 使父函數(shù)this指向為obj2系宫,子函數(shù)是箭頭函數(shù)且獨自執(zhí)行索昂,所以指向父作用域this
21. call、apply扩借、bind
- 顯示綁定:call椒惨、apply、bind都可以改變this的指向潮罪,但是apply接收參數(shù)數(shù)組康谆,call接收的是參數(shù)列表 bind接收的是參數(shù)列表,但是apply和call調(diào)用就執(zhí)行嫉到,bind需要手動執(zhí)行沃暗;
function foo (a,b,c,d) { console.log(a,b,c,d) console.log(this) } var obj = { a:3, foo:foo } // 隱式丟失:變量賦值 var bar = obj.foo; obj.foo(1,2,3,4); // obj 1,2,3,4 bar.call(obj,1,2,3,4); // obj 1,2,3,4 bar.apply(obj,[1,2,3,4]); // obj 1,2,3,4 bar.bind(obj)(1,2,3,4); // obj 1,2,3,4