JS面試題

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动分。

function Foo () {
 this.a = 1;
}

let foo = new Foo();
console.log(foo);

所以在實例上就掛了a等于1的屬性毅糟。
構(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)原型鏈繼承。

    1. 原型鏈繼承 : 會出現(xiàn)引用值共享的問題情连;
    1. 構(gòu)造函數(shù)繼承:解決原型鏈繼承中引用值共享的問題叽粹,無法調(diào)用原型上的方法;
    1. 組合式繼承(偽金典繼承):解決引用值共享的問題和無法調(diào)用原型上的方法的問題却舀,但‘偽’又造成了構(gòu)造函數(shù)復(fù)用的問題虫几;
    1. 寄生組合繼承(金典繼承):通過Object.create() 創(chuàng)建了父類原型的副本,與父類原型完全隔離挽拔,解決偽金典繼承中構(gòu)造函數(shù)復(fù)用的問題;
    1. 圣杯布局:主要思想是利用一個臨時函數(shù)作為中間層以及原型鏈的方式實現(xiàn)繼承辆脸;將子對象的 prototype 指向父對象的 prototype;
    1. 拷貝繼承:如果把父對象的所有屬性和方法,拷貝進子對象螃诅;
    1. 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)用 super

class 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
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市何恶,隨后出現(xiàn)的幾起案子孽锥,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件惜辑,死亡現(xiàn)場離奇詭異唬涧,居然都是意外死亡,警方通過查閱死者的電腦和手機盛撑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進店門碎节,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人撵彻,你說我怎么就攤上這事钓株。” “怎么了陌僵?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵轴合,是天一觀的道長。 經(jīng)常有香客問我碗短,道長受葛,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任偎谁,我火速辦了婚禮总滩,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘巡雨。我一直安慰自己闰渔,他們只是感情好,可當我...
    茶點故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布铐望。 她就那樣靜靜地躺著冈涧,像睡著了一般。 火紅的嫁衣襯著肌膚如雪正蛙。 梳的紋絲不亂的頭發(fā)上督弓,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天,我揣著相機與錄音乒验,去河邊找鬼愚隧。 笑死,一個胖子當著我的面吹牛锻全,可吹牛的內(nèi)容都是我干的狂塘。 我是一名探鬼主播,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼鳄厌,長吁一口氣:“原來是場噩夢啊……” “哼睹耐!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起部翘,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎响委,沒想到半個月后新思,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體窖梁,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年夹囚,在試婚紗的時候發(fā)現(xiàn)自己被綠了纵刘。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡荸哟,死狀恐怖假哎,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情鞍历,我是刑警寧澤舵抹,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站劣砍,受9級特大地震影響惧蛹,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜刑枝,卻給世界環(huán)境...
    茶點故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一香嗓、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧装畅,春花似錦靠娱、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至徽千,卻和暖如春苫费,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背双抽。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工百框, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人牍汹。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓铐维,卻偏偏與公主長得像,于是被迫代替她去往敵國和親慎菲。 傳聞我的和親對象是個殘疾皇子嫁蛇,可洞房花燭夜當晚...
    茶點故事閱讀 45,044評論 2 355

推薦閱讀更多精彩內(nèi)容