你所不知道的javaScript

一、變量類型

1.JS 的數(shù)據(jù)類型分類

根據(jù) JavaScript 中的變量類型傳遞方式猫牡,分為基本數(shù)據(jù)類型和引用數(shù)據(jù)類型卷胯。其中基本數(shù)據(jù)類型包括Undefined、Null浴韭、Boolean丘喻、Number、String念颈、Symbol (ES6新增泉粉,表示獨(dú)一無二的值),而引用數(shù)據(jù)類型統(tǒng)稱為Object對象榴芳,主要包括對象嗡靡、數(shù)組和函數(shù)。

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

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

  • 如果是一個(gè)參數(shù)是引用類型柿祈,會將引用類型的地址值復(fù)制給傳入函數(shù)的參數(shù)哈误,函數(shù)內(nèi)部修改會影響傳遞參數(shù)的引用對象哩至。

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

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

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

上述代碼中,a b都是值類型戈鲁,兩者分別修改賦值仇参,相互之間沒有任何影響。再看引用類型的例子:

 // 引用類型
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都是引用類型诈乒。在執(zhí)行了b = a之后,修改b的屬性值婆芦,a的也跟著變化抓谴。因?yàn)閍和b都是引用類型,指向了同一個(gè)內(nèi)存地址寞缝,即兩者引用的是同一個(gè)值,因此b修改屬性時(shí)仰泻,a的值隨之改動

2.數(shù)據(jù)類型的判斷

1)typeof

typeof返回一個(gè)表示數(shù)據(jù)類型的字符串荆陆,返回結(jié)果包括:number、boolean集侯、string被啼、symbol、object棠枉、undefined浓体、function等7種數(shù)據(jù)類型,但不能判斷null辈讶、array等

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

2)instanceof

instanceof 是用來判斷A是否為B的實(shí)例命浴,表達(dá)式為:A instanceof B,如果A是B的實(shí)例贱除,則返回true,否則返回false生闲。instanceof 運(yùn)算符用來測試一個(gè)對象在其原型鏈中是否存在一個(gè)構(gòu)造函數(shù)的 prototype 屬性,但它不能檢測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檢測 Object與instanceof不一樣碍讯,還可以處理基本數(shù)據(jù)類型的檢測。不過函數(shù)的 constructor 是不穩(wěn)定的扯躺,這個(gè)主要體現(xiàn)在把類的原型進(jìn)行重寫捉兴,在重寫的過程中很有可能出現(xiàn)把之前的constructor給覆蓋了蝎困,這樣檢測出來的結(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è)對象的指針禾乘,而不復(fù)制對象本身,新舊對象還是共享同一塊內(nèi)存逗栽。

淺拷貝的實(shí)現(xiàn)方式(詳見https://github.com/ljianshu/Blog/issues/5):

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

  • Array.prototype.concat()

  • Array.prototype.slice()

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

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

  • 熱門的函數(shù)庫lodash,也有提供_.cloneDeep用來做深拷貝

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

  • JSON.parse(JSON.stringify())

  • 手寫遞歸方法

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

 //定義檢測數(shù)據(jù)類型的功能函數(shù)
    function checkedType(target) {
      return Object.prototype.toString.call(target).slice(8, -1);
    }
    //實(shí)現(xiàn)深度克隆---對象/數(shù)組
    function clone(target) {
      //判斷拷貝的數(shù)據(jù)類型
      //初始化變量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)里的每一值是否存在對象/數(shù)組
        if (checkedType(value) === "Object" || checkedType(value) === "Array") {
          //對象/數(shù)組里嵌套了對象/數(shù)組
          //繼續(xù)遍歷獲取到value值
          result[i] = clone(value);
        } else {
          //獲取到value值是基本的數(shù)據(jù)類型或者是函數(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)部代碼之前)會做以下三件事:

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

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

  • 確定this指向:下文會介紹

 function test(arg) {
      // 1\. 形參 arg 是 "hi"
      // 2\. 因?yàn)楹瘮?shù)聲明比變量聲明優(yōu)先級高孽锥,所以此時(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í)候,首先會形成一個(gè)新的私有的作用域,然后依次按照如下的步驟執(zhí)行:

  • 如果有形參惜辑,先給形參賦值

  • 進(jìn)行私有作用域中的預(yù)解釋隔崎,函數(shù)聲明優(yōu)先級比變量聲明高,最后后者會被前者所覆蓋韵丑,但是可以重新賦值

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

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

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

image

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

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

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

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

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

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

2.作用域與作用域鏈

ES6 到來JavaScript 有全局作用域督弓、函數(shù)作用域和塊級作用域(ES6新增)营曼。我們可以這樣理解:作用域就是一個(gè)獨(dú)立的地盤,讓變量不會外泄愚隧、暴露出去溶推。也就是說作用域最大的用處就是隔離變量,不同作用域下同名變量不會有沖突奸攻。 在介紹作用域鏈之前,先要了解下自由變量虱痕,如下代碼中睹耐,console.log(a)要得到a變量,但是在當(dāng)前的作用域中沒有定義a(可對比一下b)部翘。當(dāng)前作用域沒有定義的變量硝训,這成為 自由變量。

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

自由變量的值如何得到 —— 向父級作用域(創(chuàng)建該函數(shù)的那個(gè)父級作用域)尋找新思。如果父級也沒呢窖梁?再一層一層向上尋找,直到找到全局作用域還是沒找到夹囚,就宣布放棄纵刘。這種一層一層的關(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ù)(其他語言不能這樣),里面的函數(shù)可以訪問外面函數(shù)的變量扇救,外面的變量的是這個(gè)內(nèi)部函數(shù)的一部分。

閉包的作用:

  • 使用閉包可以訪問函數(shù)中的變量香嗓。

  • 可以使變量長期保存在內(nèi)存中迅腔,生命周期比較長

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

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

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

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

function outer() {
    var num = 0; //內(nèi)部變量
    return function add() {
    //通過return返回add函數(shù)苫费,就可以在outer函數(shù)外訪問了汤锨。
    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)前類的一個(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
<button id="btn1">箭頭函數(shù)this</button>;
let btn1 = document.getElementById("btn1");
let obj = {
    name: "kobe",
    age: 39,
    getName: function() {
    btn1.onclick = () => {
        console.log(this); //obj
    };
    }
};
obj.getName();

接下來我們逐一解釋上面幾種情況

  • 對于直接調(diào)用 foo 來說锨并,不管 foo 函數(shù)被放在了什么地方,this 一定是 window

  • 對于 obj.foo() 來說睬棚,我們只需要記住第煮,誰調(diào)用了函數(shù),誰就是 this抑党,所以在這個(gè)場景下 foo 函數(shù)中的 this 就是 obj 對象

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

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

  • 箭頭函數(shù)this指向:箭頭函數(shù)沒有自己的this底靠,看其外層的是否有函數(shù)害晦,如果有,外層函數(shù)的this就是內(nèi)部箭頭函數(shù)的this暑中,如果沒有篱瞎,則this是window苟呐。

image

三、異步

1.同步 vs 異步

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

異步赠幕,是一種并行處理的方式,不必等待一個(gè)程序執(zhí)行完询筏,可以執(zhí)行其它的任務(wù)榕堰。比方說一個(gè)人邊吃飯,邊看手機(jī)嫌套,邊說話逆屡,就是異步處理的方式。在程序中異步處理的結(jié)果通常使用回調(diào)函數(shù)來處理結(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)并沒有改變JavaScript單線程的本質(zhì)榄审。

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

3.前端異步的場景

前端使用異步的場景

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

  • 網(wǎng)絡(luò)請求:ajax請求驮吱,動態(tài)加載

  • 事件綁定

4.Event Loop

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

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

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

  • 上一步我們出隊(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ì)列這一步板鬓,會逐個(gè)執(zhí)行隊(duì)列中的任務(wù)并把它出隊(duì)悲敷,直到隊(duì)列被清空。

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

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

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

接下來我們看道例子來介紹上面流程:

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

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

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

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

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

四病瞳、原型鏈與繼承

1.原型和原型鏈

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

原型鏈:JavaScript萬物都是對象套菜,對象和對象之間也有關(guān)系亲善,并不是孤立存在的。對象之間的繼承關(guān)系逗柴,在JavaScript中是通過prototype對象指向父類對象蛹头,直到指向Object對象為止,這樣就形成了一個(gè)原型指向的鏈條戏溺,專業(yè)術(shù)語稱之為原型鏈渣蜗。

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

原型關(guān)系圖:

image

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

image

2.繼承

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

  • 原型鏈+借用構(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

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

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

  • 寄生組合繼承:這種繼承方式對上一種組合繼承進(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)的核心就是將父類的原型賦值給了子類,并且將構(gòu)造函數(shù)設(shè)置為子類迄沫,這樣既解決了無用的父類屬性問題稻扬,還能正確的找到子類的構(gòu)造函數(shù)。

  • ES6中class 的繼承

ES6中引入了class關(guān)鍵字邢滑,class可以通過extends關(guān)鍵字實(shí)現(xiàn)繼承腐螟,還可以通過static關(guān)鍵字定義類的靜態(tài)方法,這比 ES5 的通過修改原型鏈實(shí)現(xiàn)繼承,要清晰和方便很多困后。需要注意的是乐纸,class關(guān)鍵字只是原型的語法糖,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è)父類汽绢,并且在子類構(gòu)造函數(shù)中必須調(diào)用 super,因?yàn)檫@段代碼可以看成 Parent.call(this,value)侧戴。

五宁昭、DOM操作與BOM操作

1.DOM操作

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

  • 新增節(jié)點(diǎn)和移動節(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)建的元素
// 移動已有節(jié)點(diǎn)。注意蜕猫,這里是“移動”寂曹,并不是拷貝
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ā)生后回右,會在子元素和父元素之間傳播(propagation)隆圆。這種傳播分成三個(gè)階段。

(1)捕獲階段:事件從window對象自上而下向目標(biāo)節(jié)點(diǎn)傳播的階段翔烁; (2)目標(biāo)階段:真正的目標(biāo)節(jié)點(diǎn)正在處理事件的階段渺氧; (3)冒泡階段:事件從目標(biāo)節(jié)點(diǎn)自下而上向window對象傳播的階段。

DOM事件捕獲的具體流程

image

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

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

<div id="outer">
    <div id="inner"></div>
</div>;

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");
};
image

如何阻止冒泡憔杨?

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

inner.onclick = function(ev) {
    console.log('inner')
    ev.stopPropagation()
}

3.事件代理(事件委托)

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

我們設(shè)定一種場景,如下代碼纠亚,一個(gè) <div>中包含了若干個(gè) <a>塘慕,而且還能繼續(xù)增加。那如何快捷方便地為所有 <a>綁定事件呢蒂胞?

<div id="div1">
  <a href="#">a1</a>
  <a href="#">a2</a>
  <a href="#">a3</a>
  <a href="#">a4</a>
</div>
<button>點(diǎn)擊增加一個(gè) a 標(biāo)簽</button>

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

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

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

  • 使代碼簡潔

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

4.BOM 操作

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

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

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

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

  • window.navigator對象:常常用來獲取瀏覽器信息赢乓、是否移動端訪問等等

獲取屏幕的寬度和高度

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()

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

var ua = navigator.userAgent
var isChrome = ua.indexOf('Chrome')
console.log(isChrome

5.Ajax與跨域

Ajax 是一種異步請求數(shù)據(jù)的一種技術(shù),對于改善用戶的體驗(yàn)和程序的性能很有幫助犀暑。 簡單地說驯击,在不需要重新刷新頁面的情況下,Ajax 通過異步請求加載后臺數(shù)據(jù)耐亏,并在網(wǎng)頁上呈現(xiàn)出來徊都。常見運(yùn)用場景有表單驗(yàn)證是否登入成功、百度搜索下拉框提示和快遞單號查詢等等广辰。Ajax的目的是提高用戶體驗(yàn)暇矫,較少網(wǎng)絡(luò)數(shù)據(jù)的傳輸量主之。

如何手寫 XMLHttpRequest 不借助任何庫

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覽器出于安全考慮,有同源策略李根。也就是說杀餐,如果協(xié)議、域名或者端口有一個(gè)不同就是跨域朱巨,Ajax 請求會失敗。

那么是出于什么安全考慮才會引入這種機(jī)制呢枉长? 其實(shí)主要是用來防止 CSRF 攻擊的冀续。簡單點(diǎn)說,CSRF 攻擊是利用用戶的登錄態(tài)發(fā)起惡意請求必峰。

然后我們來考慮一個(gè)問題洪唐,請求跨域了,那么請求到底發(fā)出去沒有吼蚁? 請求必然是發(fā)出去了凭需,但是瀏覽器攔截了響應(yīng)。

常見的幾種跨域解決方案(https://github.com/ljianshu/Blog/issues/55):

  • JSONP:利用同源策略對 <script>標(biāo)簽不受限制,不過只支持GET請求

  • CORS:實(shí)現(xiàn) CORS 通信的關(guān)鍵是后端肝匆,服務(wù)端設(shè)置 Access-Control-Allow-Origin 就可以開啟粒蜈,備受推崇的跨域解決方案,比 JSONP 簡單許多

  • Node中間件代理或nginx反向代理:主要是通過同源策略對服務(wù)器不加限制

6.存儲

sessionStorage 旗国、localStorage 和 cookie 之間的區(qū)別

  • 共同點(diǎn):都是保存在瀏覽器端枯怖,且都遵循同源策略。

  • 不同點(diǎn):在于生命周期與作用域的不同

作用域:localStorage只要在相同的協(xié)議能曾、相同的主機(jī)名度硝、相同的端口下,就能讀取/修改到同一份localStorage數(shù)據(jù)寿冕。sessionStorage比localStorage更嚴(yán)苛一點(diǎn)蕊程,除了協(xié)議、主機(jī)名驼唱、端口外藻茂,還要求在同一窗口(也就是瀏覽器的標(biāo)簽頁)下

image

生命周期:localStorage 是持久化的本地存儲,存儲在其中的數(shù)據(jù)是永遠(yuǎn)不會過期的玫恳,使其消失的唯一辦法是手動刪除捌治;而 sessionStorage 是臨時(shí)性的本地存儲,它是會話級別的存儲纽窟,當(dāng)會話結(jié)束(頁面被關(guān)閉)時(shí)肖油,存儲內(nèi)容也隨之被釋放。

image

六臂港、模塊化

幾種常見模塊化規(guī)范的簡介

詳情請點(diǎn)擊https://github.com/ljianshu/Blog/issues/48

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

  • AMD規(guī)范在瀏覽器環(huán)境中異步加載模塊,而且可以并行加載多個(gè)模塊式散。不過筋遭,AMD規(guī)范開發(fā)成本高,代碼的閱讀和書寫比較困難暴拄,模塊定義方式的語義不順暢漓滔。

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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末日熬,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子肾胯,更是在濱河造成了極大的恐慌竖席,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,692評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件敬肚,死亡現(xiàn)場離奇詭異毕荐,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)艳馒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評論 3 392
  • 文/潘曉璐 我一進(jìn)店門憎亚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人弄慰,你說我怎么就攤上這事第美。” “怎么了陆爽?”我有些...
    開封第一講書人閱讀 162,995評論 0 353
  • 文/不壞的土叔 我叫張陵什往,是天一觀的道長。 經(jīng)常有香客問我慌闭,道長别威,這世上最難降的妖魔是什么躯舔? 我笑而不...
    開封第一講書人閱讀 58,223評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮省古,結(jié)果婚禮上粥庄,老公的妹妹穿的比我還像新娘。我一直安慰自己豺妓,他們只是感情好惜互,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,245評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著琳拭,像睡著了一般训堆。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上臀栈,一...
    開封第一講書人閱讀 51,208評論 1 299
  • 那天,我揣著相機(jī)與錄音挠乳,去河邊找鬼权薯。 笑死,一個(gè)胖子當(dāng)著我的面吹牛睡扬,可吹牛的內(nèi)容都是我干的盟蚣。 我是一名探鬼主播,決...
    沈念sama閱讀 40,091評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼卖怜,長吁一口氣:“原來是場噩夢啊……” “哼屎开!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起马靠,我...
    開封第一講書人閱讀 38,929評論 0 274
  • 序言:老撾萬榮一對情侶失蹤奄抽,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后甩鳄,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體逞度,經(jīng)...
    沈念sama閱讀 45,346評論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,570評論 2 333
  • 正文 我和宋清朗相戀三年妙啃,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了档泽。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,739評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡揖赴,死狀恐怖馆匿,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情燥滑,我是刑警寧澤渐北,帶...
    沈念sama閱讀 35,437評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站铭拧,受9級特大地震影響腔稀,放射性物質(zhì)發(fā)生泄漏盆昙。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,037評論 3 326
  • 文/蒙蒙 一焊虏、第九天 我趴在偏房一處隱蔽的房頂上張望淡喜。 院中可真熱鬧,春花似錦诵闭、人聲如沸炼团。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽瘟芝。三九已至,卻和暖如春褥琐,著一層夾襖步出監(jiān)牢的瞬間锌俱,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評論 1 269
  • 我被黑心中介騙來泰國打工敌呈, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留贸宏,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,760評論 2 369
  • 正文 我出身青樓磕洪,卻偏偏與公主長得像吭练,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子析显,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,647評論 2 354

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

  • 1,javascript 基礎(chǔ)知識 Array對象 Array對象屬性 Arrray對象方法 Date對象 Dat...
    Yuann閱讀 903評論 0 1
  • 第一章 錯(cuò)誤處理: 錯(cuò)誤: 程序運(yùn)行過程中鲫咽,導(dǎo)致程序無法正常執(zhí)行的現(xiàn)象(即bug) 現(xiàn)象: 程序一旦出錯(cuò),默認(rèn)會報(bào)...
    fastwe閱讀 1,114評論 0 1
  • 一谷异、 JS面向?qū)ο缶幊?1分尸、 面向?qū)ο蠼榻B 什么是對象? Everything is object (萬物皆對象)...
    寵辱不驚丶?xì)q月靜好閱讀 830評論 0 2
  • 繼承 一歹嘹、混入式繼承 二寓落、原型繼承 利用原型中的成員可以被和其相關(guān)的對象共享這一特性,可以實(shí)現(xiàn)繼承荞下,這種實(shí)現(xiàn)繼承的...
    magic_pill閱讀 1,062評論 0 3
  • 最近幣圈還是在熊市伶选,各大社群,除了主流交易所那幾個(gè)人氣不錯(cuò)外尖昏,其余的社群基本上都冷得不行仰税。目前我還接著在...
    三朵金花_fcb1閱讀 320評論 0 0