成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專(zhuān)欄INFORMATION COLUMN

春招季如何橫掃 Javascript 面試核心考點(diǎn)(基礎(chǔ)版)?

impig33 / 1305人閱讀

摘要:當(dāng)前函數(shù)執(zhí)行完成后,當(dāng)前函數(shù)的執(zhí)行上下文出棧,并等待垃圾回收。作用域與作用域鏈到來(lái)有全局作用域函數(shù)作用域和塊級(jí)作用域新增。

引言

Javascript是前端面試的重點(diǎn),本文重點(diǎn)梳理下 Javascript 中的常考知識(shí)點(diǎn),然后就一些容易出現(xiàn)的題目進(jìn)行解析。限于文章的篇幅,無(wú)法將知識(shí)點(diǎn)講解的面面俱到,本文只羅列了一些重難點(diǎn),如果想要了解更多內(nèi)容歡迎點(diǎn)擊我的博客

一、變量類(lèi)型 1.JS 的數(shù)據(jù)類(lèi)型分類(lèi)

根據(jù) JavaScript 中的變量類(lèi)型傳遞方式,分為基本數(shù)據(jù)類(lèi)型和引用數(shù)據(jù)類(lèi)型。其中基本數(shù)據(jù)類(lèi)型包括Undefined、Null、Boolean、Number、String、Symbol (ES6新增,表示獨(dú)一無(wú)二的值),而引用數(shù)據(jù)類(lèi)型統(tǒng)稱(chēng)為Object對(duì)象,主要包括對(duì)象、數(shù)組和函數(shù)。

在參數(shù)傳遞方式上,有所不同:

函數(shù)的參數(shù)如果是簡(jiǎn)單類(lèi)型,會(huì)將一個(gè)值類(lèi)型的數(shù)值副本傳到函數(shù)內(nèi)部,函數(shù)內(nèi)部不影響函數(shù)外部傳遞的參數(shù)變量

如果是一個(gè)參數(shù)是引用類(lèi)型,會(huì)將引用類(lèi)型的地址值復(fù)制給傳入函數(shù)的參數(shù),函數(shù)內(nèi)部修改會(huì)影響傳遞

題目:基本類(lèi)型和引用類(lèi)型的區(qū)別

基本類(lèi)型和引用類(lèi)型存儲(chǔ)于內(nèi)存的位置不同,基本類(lèi)型直接存儲(chǔ)在棧中,而引用類(lèi)型的對(duì)象存儲(chǔ)在堆中,與此同時(shí),在棧中存儲(chǔ)了指針,而這個(gè)指針指向正是堆中實(shí)體的起始位置。下面通過(guò)一個(gè)小題目,來(lái)看下兩者的主要區(qū)別:

// 基本類(lèi)型
var a = 10
var b = a
b = 20
console.log(a)  // 10
console.log(b)  // 20

上述代碼中,a b都是值類(lèi)型,兩者分別修改賦值,相互之間沒(méi)有任何影響。再看引用類(lèi)型的例子:

// 引用類(lèi)型
var a = {x: 10, y: 20}
var b = a
b.x = 100
b.y = 200
console.log(a)  // {x: 100, y: 200}
console.log(b)  // {x: 100, y: 200}

上述代碼中,a b都是引用類(lèi)型。在執(zhí)行了b = a之后,修改b的屬性值,a的也跟著變化。因?yàn)閍和b都是引用類(lèi)型,指向了同一個(gè)內(nèi)存地址,即兩者引用的是同一個(gè)值,因此b修改屬性時(shí),a的值隨之改動(dòng)

2.數(shù)據(jù)類(lèi)型的判斷 1)typeof

typeof返回一個(gè)表示數(shù)據(jù)類(lèi)型的字符串,返回結(jié)果包括:number、boolean、string、symbol、object、undefined、function等7種數(shù)據(jù)類(lèi)型,但不能判斷null、array等

typeof Symbol(); // symbol 有效
typeof ""; // string 有效
typeof 1; // number 有效
typeof true; //boolean 有效
typeof undefined; //undefined 有效
typeof new Function(); // function 有效
typeof null; //object 無(wú)效
typeof [] ; //object 無(wú)效
typeof new Date(); //object 無(wú)效
typeof new RegExp(); //object 無(wú)效
2)instanceof

instanceof 是用來(lái)判斷A是否為B的實(shí)例,表達(dá)式為:A instanceof B,如果A是B的實(shí)例,則返回true,否則返回false。instanceof 運(yùn)算符用來(lái)測(cè)試一個(gè)對(duì)象在其原型鏈中是否存在一個(gè)構(gòu)造函數(shù)的 prototype 屬性,但它不能檢測(cè)null 和 undefined

[] instanceof Array; //true
{} instanceof Object;//true
new Date() instanceof Date;//true
new RegExp() instanceof RegExp//true
null instanceof Null//報(bào)錯(cuò)
undefined instanceof undefined//報(bào)錯(cuò)
3)constructor

constructor作用和instanceof非常相似。但constructor檢測(cè) Object與instanceof不一樣,還可以處理基本數(shù)據(jù)類(lèi)型的檢測(cè)。
不過(guò)函數(shù)的 constructor 是不穩(wěn)定的,這個(gè)主要體現(xiàn)在把類(lèi)的原型進(jìn)行重寫(xiě),在重寫(xiě)的過(guò)程中很有可能出現(xiàn)把之前的constructor給覆蓋了,這樣檢測(cè)出來(lái)的結(jié)果就是不準(zhǔn)確的。

4)Object.prototype.toString.call()

Object.prototype.toString.call() 是最準(zhǔn)確最常用的方式。

Object.prototype.toString.call("") ;   // [object String]
Object.prototype.toString.call(1) ;    // [object Number]
Object.prototype.toString.call(true) ; // [object Boolean]
Object.prototype.toString.call(undefined) ; // [object Undefined]
Object.prototype.toString.call(null) ; // [object Null]
Object.prototype.toString.call(new Function()) ; // [object Function]
Object.prototype.toString.call(new Date()) ; // [object Date]
Object.prototype.toString.call([]) ; // [object Array]
Object.prototype.toString.call(new RegExp()) ; // [object RegExp]
Object.prototype.toString.call(new Error()) ; // [object Error]
3.淺拷貝與深拷貝

淺拷貝只復(fù)制指向某個(gè)對(duì)象的指針,而不復(fù)制對(duì)象本身,新舊對(duì)象還是共享同一塊內(nèi)存。

淺拷貝的實(shí)現(xiàn)方式(詳見(jiàn)淺拷貝與深拷貝):

Object.assign():需注意的是目標(biāo)對(duì)象只有一層的時(shí)候,是深拷貝

Array.prototype.concat()

Array.prototype.slice()

深拷貝就是在拷貝數(shù)據(jù)的時(shí)候,將數(shù)據(jù)的所有引用結(jié)構(gòu)都拷貝一份。簡(jiǎn)單的說(shuō)就是,在內(nèi)存中存在兩個(gè)數(shù)據(jù)結(jié)構(gòu)完全相同又相互獨(dú)立的數(shù)據(jù),將引用型類(lèi)型進(jìn)行復(fù)制,而不是只復(fù)制其引用關(guān)系。

深拷貝的實(shí)現(xiàn)方式:

熱門(mén)的函數(shù)庫(kù)lodash,也有提供_.cloneDeep用來(lái)做深拷貝

jquery 提供一個(gè)$.extend可以用來(lái)做深拷貝

JSON.parse(JSON.stringify())

手寫(xiě)遞歸方法

遞歸實(shí)現(xiàn)深拷貝的原理:要拷貝一個(gè)數(shù)據(jù),我們肯定要去遍歷它的屬性,如果這個(gè)對(duì)象的屬性仍是對(duì)象,繼續(xù)使用這個(gè)方法,如此往復(fù)。

//定義檢測(cè)數(shù)據(jù)類(lèi)型的功能函數(shù)
function checkedType(target) {
  return Object.prototype.toString.call(target).slice(8, -1)
}
//實(shí)現(xiàn)深度克隆---對(duì)象/數(shù)組
function clone(target) {
  //判斷拷貝的數(shù)據(jù)類(lèi)型
  //初始化變量result 成為最終克隆的數(shù)據(jù)
  let result,
    targetType = checkedType(target)
  if (targetType === "Object") {
    result = {}
  } else if (targetType === "Array") {
    result = []
  } else {
    return target
  }
  //遍歷目標(biāo)數(shù)據(jù)
  for (let i in target) {
    //獲取遍歷數(shù)據(jù)結(jié)構(gòu)的每一項(xiàng)值。
    let value = target[i]
    //判斷目標(biāo)結(jié)構(gòu)里的每一值是否存在對(duì)象/數(shù)組
    if (checkedType(value) === "Object" || checkedType(value) === "Array") {
      //對(duì)象/數(shù)組里嵌套了對(duì)象/數(shù)組
      //繼續(xù)遍歷獲取到value值
      result[i] = clone(value)
    } else {
      //獲取到value值是基本的數(shù)據(jù)類(lèi)型或者是函數(shù)。
      result[i] = value
    }
  }
  return result
}
二、作用域和閉包 1.執(zhí)行上下文和執(zhí)行棧

執(zhí)行上下文就是當(dāng)前 JavaScript 代碼被解析和執(zhí)行時(shí)所在環(huán)境的抽象概念, JavaScript 中運(yùn)行任何的代碼都是在執(zhí)行上下文中運(yùn)行。
執(zhí)行上下文的生命周期包括三個(gè)階段:創(chuàng)建階段→執(zhí)行階段→回收階段,我們重點(diǎn)介紹創(chuàng)建階段。

創(chuàng)建階段(當(dāng)函數(shù)被調(diào)用,但未執(zhí)行任何其內(nèi)部代碼之前)會(huì)做以下三件事:

創(chuàng)建變量對(duì)象:首先初始化函數(shù)的參數(shù)arguments,提升函數(shù)聲明和變量聲明。

創(chuàng)建作用域鏈:下文會(huì)介紹

確定this指向:下文會(huì)介紹

function test(arg){
    // 1. 形參 arg 是 "hi"
    // 2. 因?yàn)楹瘮?shù)聲明比變量聲明優(yōu)先級(jí)高,所以此時(shí) arg 是 function
    console.log(arg);  
    var arg = "hello"; // 3.var arg 變量聲明被忽略, arg = "hello"被執(zhí)行
    function arg(){
    console.log("hello world") 
    }
    console.log(arg);  
}
test("hi");
/* 輸出:
function arg() {
    console.log("hello world");
    }
hello 
*/

這是因?yàn)楫?dāng)函數(shù)執(zhí)行的時(shí)候,首先會(huì)形成一個(gè)新的私有的作用域,然后依次按照如下的步驟執(zhí)行:

如果有形參,先給形參賦值

進(jìn)行私有作用域中的預(yù)解釋?zhuān)瘮?shù)聲明優(yōu)先級(jí)比變量聲明高,最后后者會(huì)被前者所覆蓋,但是可以重新賦值

私有作用域中的代碼從上到下執(zhí)行

函數(shù)多了,就有多個(gè)函數(shù)執(zhí)行上下文,每次調(diào)用函數(shù)創(chuàng)建一個(gè)新的執(zhí)行上下文,那如何管理創(chuàng)建的那么多執(zhí)行上下文呢?

JavaScript 引擎創(chuàng)建了執(zhí)行棧來(lái)管理執(zhí)行上下文。可以把執(zhí)行棧認(rèn)為是一個(gè)存儲(chǔ)函數(shù)調(diào)用的棧結(jié)構(gòu),遵循先進(jìn)后出的原則


從上面的流程圖,我們需要記住幾個(gè)關(guān)鍵點(diǎn):

JavaScript執(zhí)行在單線程上,所有的代碼都是排隊(duì)執(zhí)行。

一開(kāi)始瀏覽器執(zhí)行全局的代碼時(shí),首先創(chuàng)建全局的執(zhí)行上下文,壓入執(zhí)行棧的頂部。

每當(dāng)進(jìn)入一個(gè)函數(shù)的執(zhí)行就會(huì)創(chuàng)建函數(shù)的執(zhí)行上下文,并且把它壓入執(zhí)行棧的頂部。當(dāng)前函數(shù)執(zhí)行完成后,當(dāng)前函數(shù)的執(zhí)行上下文出棧,并等待垃圾回收。

瀏覽器的JS執(zhí)行引擎總是訪問(wèn)棧頂?shù)膱?zhí)行上下文。

全局上下文只有唯一的一個(gè),它在瀏覽器關(guān)閉時(shí)出棧。

2.作用域與作用域鏈

ES6 到來(lái)JavaScript 有全局作用域、函數(shù)作用域和塊級(jí)作用域(ES6新增)。我們可以這樣理解:作用域就是一個(gè)獨(dú)立的地盤(pán),讓變量不會(huì)外泄、暴露出去。也就是說(shuō)作用域最大的用處就是隔離變量,不同作用域下同名變量不會(huì)有沖突。
在介紹作用域鏈之前,先要了解下自由變量,如下代碼中,console.log(a)要得到a變量,但是在當(dāng)前的作用域中沒(méi)有定義a(可對(duì)比一下b)。當(dāng)前作用域沒(méi)有定義的變量,這成為 自由變量。

var a = 100
function fn() {
    var b = 200
    console.log(a) // 這里的a在這里就是一個(gè)自由變量
    console.log(b)
}
fn()

自由變量的值如何得到 —— 向父級(jí)作用域(創(chuàng)建該函數(shù)的那個(gè)父級(jí)作用域)尋找。如果父級(jí)也沒(méi)呢?再一層一層向上尋找,直到找到全局作用域還是沒(méi)找到,就宣布放棄。這種一層一層的關(guān)系,就是作用域鏈 。

function F1() {
    var a = 100
    return function () {
        console.log(a)
    }
}
function F2(f1) {
    var a = 200
    console.log(f1())
}
var f1 = F1()
F2(f1) // 100 

上述代碼中,自由變量a的值,從函數(shù)F1中查找而不是F2,這是因?yàn)?strong>當(dāng)自由變量從作用域鏈中去尋找,依據(jù)的是函數(shù)定義時(shí)的作用域鏈,而不是函數(shù)執(zhí)行時(shí)。

3.閉包是什么

閉包這個(gè)概念也是JavaScript中比較抽象的概念,我個(gè)人理解,閉包是就是函數(shù)中的函數(shù)(其他語(yǔ)言不能這樣),里面的函數(shù)可以訪問(wèn)外面函數(shù)的變量,外面的變量的是這個(gè)內(nèi)部函數(shù)的一部分。

閉包的作用:

使用閉包可以訪問(wèn)函數(shù)中的變量。

可以使變量長(zhǎng)期保存在內(nèi)存中,生命周期比較長(zhǎng)。

閉包不能濫用,否則會(huì)導(dǎo)致內(nèi)存泄露,影響網(wǎng)頁(yè)的性能。閉包使用完了后,要立即釋放資源,將引用變量指向null。

閉包主要有兩個(gè)應(yīng)用場(chǎng)景:

函數(shù)作為參數(shù)傳遞(見(jiàn)作用域部分例子)

函數(shù)作為返回值(如下例)

function outer() {
  var num = 0 //內(nèi)部變量
  return function add() {
    //通過(guò)return返回add函數(shù),就可以在outer函數(shù)外訪問(wèn)了。
    num++ //內(nèi)部函數(shù)有引用,作為add函數(shù)的一部分了
    console.log(num)
  }
}
var func1 = outer() //
func1() //實(shí)際上是調(diào)用add函數(shù), 輸出1
func1() //輸出2
var func2 = outer()
func2() // 輸出1
func2() // 輸出2
4.this全面解析

先搞明白一個(gè)很重要的概念 —— this的值是在執(zhí)行的時(shí)候才能確認(rèn),定義的時(shí)候不能確認(rèn)! 為什么呢 —— 因?yàn)閠his是執(zhí)行上下文環(huán)境的一部分,而執(zhí)行上下文需要在代碼執(zhí)行之前確定,而不是定義的時(shí)候。看如下例子:

// 情況1
function foo() {
  console.log(this.a) //1
}
var a = 1
foo()

// 情況2
function fn(){
  console.log(this);
}
var obj={fn:fn};
obj.fn(); //this->obj

// 情況3
function CreateJsPerson(name,age){
//this是當(dāng)前類(lèi)的一個(gè)實(shí)例p1
this.name=name; //=>p1.name=name
this.age=age; //=>p1.age=age
}
var p1=new CreateJsPerson("尹華芝",48);

// 情況4
function add(c, d){
  return this.a + this.b + c + d;
}
var o = {a:1, b:3};
add.call(o, 5, 7); // 1 + 3 + 5 + 7 = 16
add.apply(o, [10, 20]); // 1 + 3 + 10 + 20 = 34

// 情況5

接下來(lái)我們逐一解釋上面幾種情況

對(duì)于直接調(diào)用 foo 來(lái)說(shuō),不管 foo 函數(shù)被放在了什么地方,this 一定是 window

對(duì)于 obj.foo() 來(lái)說(shuō),我們只需要記住,誰(shuí)調(diào)用了函數(shù),誰(shuí)就是 this,所以在這個(gè)場(chǎng)景下 foo 函數(shù)中的 this 就是 obj 對(duì)象

在構(gòu)造函數(shù)模式中,類(lèi)中(函數(shù)體中)出現(xiàn)的this.xxx=xxx中的this是當(dāng)前類(lèi)的一個(gè)實(shí)例

call、apply和bind:this 是第一個(gè)參數(shù)

箭頭函數(shù)this指向:箭頭函數(shù)沒(méi)有自己的this,看其外層的是否有函數(shù),如果有,外層函數(shù)的this就是內(nèi)部箭頭函數(shù)的this,如果沒(méi)有,則this是window。

三、異步 1.同步 vs 異步

同步,我的理解是一種線性執(zhí)行的方式,執(zhí)行的流程不能跨越。比如說(shuō)話后在吃飯,吃完飯后在看手機(jī),必須等待上一件事完了,才執(zhí)行后面的事情。

異步,是一種并行處理的方式,不必等待一個(gè)程序執(zhí)行完,可以執(zhí)行其它的任務(wù)。比方說(shuō)一個(gè)人邊吃飯,邊看手機(jī),邊說(shuō)話,就是異步處理的方式。在程序中異步處理的結(jié)果通常使用回調(diào)函數(shù)來(lái)處理結(jié)果。

// 同步
console.log(100)
alert(200);
console.log(300)  //100 200 300
// 異步
console.log(100) 
setTimeout(function(){ 
  console.log(200) 
}) 
console.log(300) //100 300 200 
2.異步和單線程

JS 需要異步的根本原因是 JS 是單線程運(yùn)行的,即在同一時(shí)間只能做一件事,不能“一心二用”。為了利用多核CPU的計(jì)算能力,HTML5提出Web Worker標(biāo)準(zhǔn),允許JavaScript腳本創(chuàng)建多個(gè)線程,但是子線程完全受主線程控制,且不得操作DOM。所以,這個(gè)新標(biāo)準(zhǔn)并沒(méi)有改變JavaScript單線程的本質(zhì)。

一個(gè) Ajax 請(qǐng)求由于網(wǎng)絡(luò)比較慢,請(qǐng)求需要 5 秒鐘。如果是同步,這 5 秒鐘頁(yè)面就卡死在這里啥也干不了了。異步的話,就好很多了,5 秒等待就等待了,其他事情不耽誤做,至于那 5 秒鐘等待是網(wǎng)速太慢,不是因?yàn)?JS 的原因。

3.前端異步的場(chǎng)景

前端使用異步的場(chǎng)景

定時(shí)任務(wù):setTimeout,setInterval

網(wǎng)絡(luò)請(qǐng)求:ajax請(qǐng)求,動(dòng)態(tài)加載

事件綁定

4.Event Loop

一個(gè)完整的 Event Loop 過(guò)程,可以概括為以下階段:

一開(kāi)始執(zhí)行???我們可以把執(zhí)行棧認(rèn)為是一個(gè)存儲(chǔ)函數(shù)調(diào)用的棧結(jié)構(gòu),遵循先進(jìn)后出的原則。micro 隊(duì)列空,macro 隊(duì)列里有且只有一個(gè) script 腳本(整體代碼)。

全局上下文(script 標(biāo)簽)被推入執(zhí)行棧,同步代碼執(zhí)行。在執(zhí)行的過(guò)程中,會(huì)判斷是同步任務(wù)還是異步任務(wù),通過(guò)對(duì)一些接口的調(diào)用,可以產(chǎn)生新的 macro-task 與 micro-task,它們會(huì)分別被推入各自的任務(wù)隊(duì)列里。同步代碼執(zhí)行完了,script 腳本會(huì)被移出 macro 隊(duì)列,這個(gè)過(guò)程本質(zhì)上是隊(duì)列的 macro-task 的執(zhí)行和出隊(duì)的過(guò)程。

上一步我們出隊(duì)的是一個(gè) macro-task,這一步我們處理的是 micro-task。但需要注意的是:當(dāng) macro-task 出隊(duì)時(shí),任務(wù)是一個(gè)一個(gè)執(zhí)行的;而 micro-task 出隊(duì)時(shí),任務(wù)是一隊(duì)一隊(duì)執(zhí)行的。因此,我們處理 micro 隊(duì)列這一步,會(huì)逐個(gè)執(zhí)行隊(duì)列中的任務(wù)并把它出隊(duì),直到隊(duì)列被清空。

執(zhí)行渲染操作,更新界面

檢查是否存在 Web worker 任務(wù),如果有,則對(duì)其進(jìn)行處理

上述過(guò)程循環(huán)往復(fù),直到兩個(gè)隊(duì)列都清空

接下來(lái)我們看道例子來(lái)介紹上面流程:

Promise.resolve().then(()=>{
  console.log("Promise1")  
  setTimeout(()=>{
    console.log("setTimeout2")
  },0)
})
setTimeout(()=>{
  console.log("setTimeout1")
  Promise.resolve().then(()=>{
    console.log("Promise2")    
  })
},0)

最后輸出結(jié)果是Promise1,setTimeout1,Promise2,setTimeout2

一開(kāi)始執(zhí)行棧的同步任務(wù)(這屬于宏任務(wù))執(zhí)行完畢,會(huì)去查看是否有微任務(wù)隊(duì)列,上題中存在(有且只有一個(gè)),然后執(zhí)行微任務(wù)隊(duì)列中的所有任務(wù)輸出Promise1,同時(shí)會(huì)生成一個(gè)宏任務(wù) setTimeout2

然后去查看宏任務(wù)隊(duì)列,宏任務(wù) setTimeout1 在 setTimeout2 之前,先執(zhí)行宏任務(wù) setTimeout1,輸出 setTimeout1

在執(zhí)行宏任務(wù)setTimeout1時(shí)會(huì)生成微任務(wù)Promise2 ,放入微任務(wù)隊(duì)列中,接著先去清空微任務(wù)隊(duì)列中的所有任務(wù),輸出 Promise2

清空完微任務(wù)隊(duì)列中的所有任務(wù)后,就又會(huì)去宏任務(wù)隊(duì)列取一個(gè),這回執(zhí)行的是 setTimeout2

四、原型鏈與繼承 1.原型和原型鏈

原型:在JavaScript中原型是一個(gè)prototype對(duì)象,用于表示類(lèi)型之間的關(guān)系。

原型鏈:JavaScript萬(wàn)物都是對(duì)象,對(duì)象和對(duì)象之間也有關(guān)系,并不是孤立存在的。對(duì)象之間的繼承關(guān)系,在JavaScript中是通過(guò)prototype對(duì)象指向父類(lèi)對(duì)象,直到指向Object對(duì)象為止,這樣就形成了一個(gè)原型指向的鏈條,專(zhuān)業(yè)術(shù)語(yǔ)稱(chēng)之為原型鏈。

var Person = function() {
  this.age = 18
  this.name = "匿名"
}
var Student = function() {}
//創(chuàng)建繼承關(guān)系,父類(lèi)實(shí)例作為子類(lèi)原型
Student.prototype = new Person()
var s1 = new Student()
console.log(s1)

原型關(guān)系圖:

當(dāng)試圖得到一個(gè)對(duì)象的某個(gè)屬性時(shí),如果這個(gè)對(duì)象本身沒(méi)有這個(gè)屬性,那么會(huì)去它的__proto__(即它的構(gòu)造函數(shù)的prototype)中尋找。如果一直找到最上層都沒(méi)有找到,那么就宣告失敗,返回undefined。最上層是什么 —— Object.prototype.__proto__ === null

2.繼承

介紹幾種常見(jiàn)繼承方式(如需了解更多,請(qǐng)點(diǎn)擊JavaScript常見(jiàn)的六種繼承方式):

原型鏈+借用構(gòu)造函數(shù)的組合繼承

function Parent(value) {
  this.val = value
}
Parent.prototype.getValue = function() {
  console.log(this.val)
}
function Child(value) {
  Parent.call(this, value)
}
Child.prototype = new Parent()
const child = new Child(1)
child.getValue() // 1
child instanceof Parent // true

以上繼承的方式核心是在子類(lèi)的構(gòu)造函數(shù)中通過(guò) Parent.call(this) 繼承父類(lèi)的屬性,然后改變子類(lèi)的原型為 new Parent() 來(lái)繼承父類(lèi)的函數(shù)。

這種繼承方式優(yōu)點(diǎn)在于構(gòu)造函數(shù)可以傳參,不會(huì)與父類(lèi)引用屬性共享,可以復(fù)用父類(lèi)的函數(shù),但是也存在一個(gè)缺點(diǎn)就是在繼承父類(lèi)函數(shù)的時(shí)候調(diào)用了父類(lèi)構(gòu)造函數(shù),導(dǎo)致子類(lèi)的原型上多了不需要的父類(lèi)屬性,存在內(nèi)存上的浪費(fèi)。

寄生組合繼承:這種繼承方式對(duì)上一種組合繼承進(jìn)行了優(yōu)化

function Parent(value) {
  this.val = value
}
Parent.prototype.getValue = function() {
  console.log(this.val)
}
function Child(value) {
  Parent.call(this, value)
}
Child.prototype = Object.create(Parent.prototype, {
  constructor: {
    value: Child,
    enumerable: false,
    writable: true,
    configurable: true
  }
})
const child = new Child(1)
child.getValue() // 1
child instanceof Parent // true

以上繼承實(shí)現(xiàn)的核心就是將父類(lèi)的原型賦值給了子類(lèi),并且將構(gòu)造函數(shù)設(shè)置為子類(lèi),這樣既解決了無(wú)用的父類(lèi)屬性問(wèn)題,還能正確的找到子類(lèi)的構(gòu)造函數(shù)。

ES6中class 的繼承

ES6中引入了class關(guān)鍵字,class可以通過(guò)extends關(guān)鍵字實(shí)現(xiàn)繼承,還可以通過(guò)static關(guān)鍵字定義類(lèi)的靜態(tài)方法,這比 ES5 的通過(guò)修改原型鏈實(shí)現(xiàn)繼承,要清晰和方便很多。需要注意的是,class關(guān)鍵字只是原型的語(yǔ)法糖,JavaScript繼承仍然是基于原型實(shí)現(xiàn)的。

class Parent {
  constructor(value) {
    this.val = value
  }
  getValue() {
    console.log(this.val)
  }
}
class Child extends Parent {
  constructor(value) {
    super(value)
    this.val = value
  }
}
let child = new Child(1)
child.getValue() // 1
child instanceof Parent // true

class 實(shí)現(xiàn)繼承的核心在于使用 extends 表明繼承自哪個(gè)父類(lèi),并且在子類(lèi)構(gòu)造函數(shù)中必須調(diào)用 super,因?yàn)檫@段代碼可以看成 Parent.call(this, value)。

五、DOM操作與BOM操作 1.DOM操作

當(dāng)網(wǎng)頁(yè)被加載時(shí),瀏覽器會(huì)創(chuàng)建頁(yè)面的文檔對(duì)象模型(DOM),我們可以認(rèn)為 DOM 就是 JS 能識(shí)別的 HTML 結(jié)構(gòu),一個(gè)普通的 JS 對(duì)象或者數(shù)組。接下來(lái)我們介紹常見(jiàn)DOM操作:

新增節(jié)點(diǎn)和移動(dòng)節(jié)點(diǎn)

var div1 = document.getElementById("div1")
// 添加新節(jié)點(diǎn)
var p1 = document.createElement("p")
p1.innerHTML = "this is p1"
div1.appendChild(p1) // 添加新創(chuàng)建的元素
// 移動(dòng)已有節(jié)點(diǎn)。注意,這里是“移動(dòng)”,并不是拷貝
var p2 = document.getElementById("p2")
div1.appendChild(p2)

獲取父元素

var div1 = document.getElementById("div1")
var parent = div1.parentElement

獲取子元素

var div1 = document.getElementById("div1")
var child = div1.childNodes

刪除節(jié)點(diǎn)

var div1 = document.getElementById("div1")
var child = div1.childNodes
div1.removeChild(child[0])
2.DOM事件模型和事件流

DOM事件模型分為捕獲和冒泡。一個(gè)事件發(fā)生后,會(huì)在子元素和父元素之間傳播(propagation)。這種傳播分成三個(gè)階段。

(1)捕獲階段:事件從window對(duì)象自上而下向目標(biāo)節(jié)點(diǎn)傳播的階段;

(2)目標(biāo)階段:真正的目標(biāo)節(jié)點(diǎn)正在處理事件的階段;

(3)冒泡階段:事件從目標(biāo)節(jié)點(diǎn)自下而上向window對(duì)象傳播的階段。

DOM事件捕獲的具體流程

捕獲是從上到下,事件先從window對(duì)象,然后再到document(對(duì)象),然后是html標(biāo)簽(通過(guò)document.documentElement獲取html標(biāo)簽),然后是body標(biāo)簽(通過(guò)document.body獲取body標(biāo)簽),然后按照普通的html結(jié)構(gòu)一層一層往下傳,最后到達(dá)目標(biāo)元素。

接下來(lái)我們看個(gè)事件冒泡的例子:

// 事件冒泡
...... window.onclick = function() { console.log("window"); }; document.onclick = function() { console.log("document"); }; document.documentElement.onclick = function() { console.log("html"); }; document.body.onclick = function() { console.log("body"); } outer.onclick = function(ev) { console.log("outer"); }; inner.onclick = function(ev) { console.log("inner"); };

如何阻止冒泡?

通過(guò)event.stopPropagation() 方法阻止事件冒泡到父元素,阻止任何父事件處理程序被執(zhí)行。
我們可以在上例中inner元素的click事件上,添加event.stopPropagation()這句話后,就阻止了父事件的執(zhí)行,最后只打印了"inner"。

 inner.onclick = function(ev) {
    console.log("inner")
    ev.stopPropagation()
}
3.事件代理(事件委托)

由于事件會(huì)在冒泡階段向上傳播到父節(jié)點(diǎn),因此可以把子節(jié)點(diǎn)的監(jiān)聽(tīng)函數(shù)定義在父節(jié)點(diǎn)上,由父節(jié)點(diǎn)的監(jiān)聽(tīng)函數(shù)統(tǒng)一處理多個(gè)子元素的事件。這種方法叫做事件的代理。

我們?cè)O(shè)定一種場(chǎng)景,如下代碼,一個(gè)

中包含了若干個(gè),而且還能繼續(xù)增加。那如何快捷方便地為所有綁定事件呢?


如果給每個(gè)標(biāo)簽一一都綁定一個(gè)事件,那對(duì)于內(nèi)存消耗是非常大的。借助事件代理,我們只需要給父容器div綁定方法即可,這樣不管點(diǎn)擊的是哪一個(gè)后代元素,都會(huì)根據(jù)冒泡傳播的傳遞機(jī)制,把父容器的click行為觸發(fā),然后把對(duì)應(yīng)的方法執(zhí)行,根據(jù)事件源,我們可以知道點(diǎn)擊的是誰(shuí),從而完成不同的事。

var div1 = document.getElementById("div1")
div1.addEventListener("click", function (e) {
    // e.target 可以監(jiān)聽(tīng)到觸發(fā)點(diǎn)擊事件的元素是哪一個(gè)
    var target = e.target
    if (e.nodeName === "A") {
        // 點(diǎn)擊的是  元素
        alert(target.innerHTML)
    }
})

最后,使用代理的優(yōu)點(diǎn)如下:

使代碼簡(jiǎn)潔

減少瀏覽器的內(nèi)存占用

4.BOM 操作

BOM(瀏覽器對(duì)象模型)是瀏覽器本身的一些信息的設(shè)置和獲取,例如獲取瀏覽器的寬度、高度,設(shè)置讓瀏覽器跳轉(zhuǎn)到哪個(gè)地址。

window.screen對(duì)象:包含有關(guān)用戶屏幕的信息

window.location對(duì)象:用于獲得當(dāng)前頁(yè)面的地址(URL),并把瀏覽器重定向到新的頁(yè)面

window.history對(duì)象:瀏覽歷史的前進(jìn)后退等

window.navigator對(duì)象:常常用來(lái)獲取瀏覽器信息、是否移動(dòng)端訪問(wèn)等等

獲取屏幕的寬度和高度

console.log(screen.width)
console.log(screen.height)

獲取網(wǎng)址、協(xié)議、path、參數(shù)、hash 等

// 例如當(dāng)前網(wǎng)址是 https://juejin.im/timeline/frontend?a=10&b=10#some
console.log(location.href)  // https://juejin.im/timeline/frontend?a=10&b=10#some
console.log(location.protocol) // https:
console.log(location.pathname) // /timeline/frontend
console.log(location.search) // ?a=10&b=10
console.log(location.hash) // #some

另外,還有調(diào)用瀏覽器的前進(jìn)、后退功能等

history.back()
history.forward()

獲取瀏覽器特性(即俗稱(chēng)的UA)然后識(shí)別客戶端,例如判斷是不是 Chrome 瀏覽器

var ua = navigator.userAgent
var isChrome = ua.indexOf("Chrome")
console.log(isChrome)
5.Ajax與跨域

Ajax 是一種異步請(qǐng)求數(shù)據(jù)的一種技術(shù),對(duì)于改善用戶的體驗(yàn)和程序的性能很有幫助。
簡(jiǎn)單地說(shuō),在不需要重新刷新頁(yè)面的情況下,Ajax 通過(guò)異步請(qǐng)求加載后臺(tái)數(shù)據(jù),并在網(wǎng)頁(yè)上呈現(xiàn)出來(lái)。常見(jiàn)運(yùn)用場(chǎng)景有表單驗(yàn)證是否登入成功、百度搜索下拉框提示和快遞單號(hào)查詢(xún)等等。Ajax的目的是提高用戶體驗(yàn),較少網(wǎng)絡(luò)數(shù)據(jù)的傳輸量。

如何手寫(xiě) XMLHttpRequest 不借助任何庫(kù)

var xhr = new XMLHttpRequest()
xhr.onreadystatechange = function () {
    // 這里的函數(shù)異步執(zhí)行
    if (xhr.readyState == 4) {
        if (xhr.status == 200) {
            alert(xhr.responseText)
        }
    }
}
xhr.open("GET", "/api", false)
xhr.send(null)

因?yàn)闉g覽器出于安全考慮,有同源策略。也就是說(shuō),如果協(xié)議、域名或者端口有一個(gè)不同就是跨域,Ajax 請(qǐng)求會(huì)失敗。

那么是出于什么安全考慮才會(huì)引入這種機(jī)制呢? 其實(shí)主要是用來(lái)防止 CSRF 攻擊的。簡(jiǎn)單點(diǎn)說(shuō),CSRF 攻擊是利用用戶的登錄態(tài)發(fā)起惡意請(qǐng)求。

然后我們來(lái)考慮一個(gè)問(wèn)題,請(qǐng)求跨域了,那么請(qǐng)求到底發(fā)出去沒(méi)有? 請(qǐng)求必然是發(fā)出去了,但是瀏覽器攔截了響應(yīng)。

常見(jiàn)的幾種跨域解決方案(具體如何實(shí)現(xiàn)詳見(jiàn)九種跨域方式實(shí)現(xiàn)原理(完整版)):

JSONP:利用同源策略對(duì)
生命周期:localStorage 是持久化的本地存儲(chǔ),存儲(chǔ)在其中的數(shù)據(jù)是永遠(yuǎn)不會(huì)過(guò)期的,使其消失的唯一辦法是手動(dòng)刪除;而 sessionStorage 是臨時(shí)性的本地存儲(chǔ),它是會(huì)話級(jí)別的存儲(chǔ),當(dāng)會(huì)話結(jié)束(頁(yè)面被關(guān)閉)時(shí),存儲(chǔ)內(nèi)容也隨之被釋放。

六、模塊化

幾種常見(jiàn)模塊化規(guī)范的簡(jiǎn)介(詳情請(qǐng)點(diǎn)擊前端模塊化詳解(完整版)):

CommonJS規(guī)范主要用于服務(wù)端編程,加載模塊是同步的,這并不適合在瀏覽器環(huán)境,因?yàn)橥揭馕吨枞虞d,瀏覽器資源是異步加載的,因此有了AMD CMD解決方案

AMD規(guī)范在瀏覽器環(huán)境中異步加載模塊,而且可以并行加載多個(gè)模塊。不過(guò),AMD規(guī)范開(kāi)發(fā)成本高,代碼的閱讀和書(shū)寫(xiě)比較困難,模塊定義方式的語(yǔ)義不順暢。

CMD規(guī)范與AMD規(guī)范很相似,都用于瀏覽器編程,依賴(lài)就近,延遲執(zhí)行,可以很容易在Node.js中運(yùn)行。不過(guò),依賴(lài)SPM 打包,模塊的加載邏輯偏重

ES6 在語(yǔ)言標(biāo)準(zhǔn)的層面上,實(shí)現(xiàn)了模塊功能,而且實(shí)現(xiàn)得相當(dāng)簡(jiǎn)單,完全可以取代 CommonJS 和 AMD 規(guī)范,成為瀏覽器和服務(wù)器通用的模塊解決方案

歡迎關(guān)注公眾號(hào):前端工匠,你的成長(zhǎng)我們一起見(jiàn)證!

優(yōu)質(zhì)交流群 微信公眾號(hào)
參考資料

浪里行舟博客

前端面試之道

Web前端面試指導(dǎo)

前端入門(mén)4--DOM和BOM

Web 前端面試指南與高頻考題解析

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/54778.html

相關(guān)文章

  • 春招如何橫掃 Javascript 面試核心考點(diǎn)(基礎(chǔ))?

    摘要:當(dāng)前函數(shù)執(zhí)行完成后,當(dāng)前函數(shù)的執(zhí)行上下文出棧,并等待垃圾回收。作用域與作用域鏈到來(lái)有全局作用域函數(shù)作用域和塊級(jí)作用域新增。 引言 Javascript是前端面試的重點(diǎn),本文重點(diǎn)梳理下 Javascript 中的??贾R(shí)點(diǎn),然后就一些容易出現(xiàn)的題目進(jìn)行解析。限于文章的篇幅,無(wú)法將知識(shí)點(diǎn)講解的面面俱到,本文只羅列了一些重難點(diǎn),如果想要了解更多內(nèi)容歡迎點(diǎn)擊我的博客。 一、變量類(lèi)型 1.JS ...

    jayce 評(píng)論0 收藏0
  • 摸倚天魚(yú)文章推薦系列 - 19/03/31

    摘要:摸倚天魚(yú)文章推薦系列歡迎來(lái)到摸倚天魚(yú)系列以休閑態(tài)度推硬核文章探索亞文化摸魚(yú)倚分鐘問(wèn)網(wǎng)癮少年今晚做些什么答作為一個(gè)平均每天上網(wǎng)小時(shí)的資深網(wǎng)癮少年真想把這個(gè)問(wèn)題給撕了網(wǎng)癮少年每天看文章看影視聽(tīng)音樂(lè)聽(tīng)電臺(tái)白天若是摸魚(yú)晚上繼續(xù)摸魚(yú)難不成你以為網(wǎng)癮 摸倚天魚(yú)文章推薦系列 - 19/03/31 歡迎來(lái)到摸倚天魚(yú)系列, 以休閑態(tài)度, 推硬核文章, 探索亞文化~ 摸魚(yú)倚分鐘 問(wèn): 網(wǎng)癮少年今晚做些什么...

    xiaolinbang 評(píng)論0 收藏0
  • 摸倚天魚(yú)文章推薦系列 - 19/03/31

    摘要:摸倚天魚(yú)文章推薦系列歡迎來(lái)到摸倚天魚(yú)系列以休閑態(tài)度推硬核文章探索亞文化摸魚(yú)倚分鐘問(wèn)網(wǎng)癮少年今晚做些什么答作為一個(gè)平均每天上網(wǎng)小時(shí)的資深網(wǎng)癮少年真想把這個(gè)問(wèn)題給撕了網(wǎng)癮少年每天看文章看影視聽(tīng)音樂(lè)聽(tīng)電臺(tái)白天若是摸魚(yú)晚上繼續(xù)摸魚(yú)難不成你以為網(wǎng)癮 摸倚天魚(yú)文章推薦系列 - 19/03/31 歡迎來(lái)到摸倚天魚(yú)系列, 以休閑態(tài)度, 推硬核文章, 探索亞文化~ 摸魚(yú)倚分鐘 問(wèn): 網(wǎng)癮少年今晚做些什么...

    gitmilk 評(píng)論0 收藏0
  • JS前端面試總結(jié)

    摘要:春招季如何橫掃面試核心考點(diǎn)基礎(chǔ)版前端面試之路二基礎(chǔ)整理的繼承和的繼承有什么區(qū)別的繼承時(shí)通過(guò)或構(gòu)造函數(shù)機(jī)制來(lái)實(shí)現(xiàn)。作用創(chuàng)建私有變量,減少全局變量,防止變量名污染。異步瀏覽器訪問(wèn)服務(wù)器請(qǐng)求,用戶正常操作,瀏覽器后端進(jìn)行請(qǐng)求。 春招季如何橫掃 Javascript 面試核心考點(diǎn)(基礎(chǔ)版)?前端面試之路二(javaScript基礎(chǔ)整理) ES5的繼承和ES6的繼承有什么區(qū)別 ES5的繼承時(shí)通過(guò)...

    ThinkSNS 評(píng)論0 收藏0
  • 【備戰(zhàn)春招/秋招系列】美團(tuán)Java面經(jīng)總結(jié)進(jìn)階篇 (附詳解答案)

    摘要:我在前面的文章中也提到了應(yīng)該怎么做自我介紹與項(xiàng)目介紹,詳情可以查看這篇文章備戰(zhàn)春招秋招系列初出茅廬的程序員該如何準(zhǔn)備面試。因此基于事件消息對(duì)象驅(qū)動(dòng)的業(yè)務(wù)架構(gòu)可以是一系列流程。 showImg(https://user-gold-cdn.xitu.io/2018/11/14/16711ac29c2ae52c?w=928&h=531&f=png&s=798562); 一 消息隊(duì)列MQ的...

    chengjianhua 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

閱讀需要支付1元查看
<