JavaScript知識點總結(未完待續)

1、變量

1. 數據類型

  • 基本數據類型html

    • Stringgit

    • Number程序員

    • Booleangithub

    • null面試

      特殊: typeof null === 'Object' //truesegmentfault

    • Undefinedapi

    • Symbol 符號(ES6新增)數組

  • 引用數據類型瀏覽器

    • Object
      • Function
      • Array
      • Date
      • RegExp
  • 基本數據類型和引用數據類型的區別(存儲位置不一樣)bash

    • 原始數據類型直接存儲在棧(stack)中的簡單數據段,佔據空間小、大小固定,屬於被頻繁使用數據,因此放入棧中存儲;
    • 引用數據類型存儲在堆(heap)中的對象,佔據空間大、大小不固定,若是存儲在棧中,將會影響程序運行的性能;引用數據類型在棧中存儲了指針,該指針指向堆中該實體的起始地址。當解釋器尋找引用值時,會首先檢索其在棧中的地址,取得地址後從堆中得到實體

數據封裝類對象:ObjectArrayBooleanNumberString

其餘對象:FunctionArgumentsMathDateRegExpError

2. 類型判斷

2.1 null

typeof null === 'Object' //true

null是惟一一個用typeof檢測會返回object基本類型值(注意‘基本’兩字)

緣由:不一樣的對象在底層都表示爲二進制 在JavaScript中二進制前三位爲0的話都會被判斷爲object類型 null的二進制表示全是0,天然前三位也是0 因此 typeof null === 「object」

2.2 引用類型的判斷

參考連接:深刻理解JS的類型、值、類型轉換

2.2.1 typeof

  • typeof 除了能判斷基本類型、Object外,還能判斷function類型

2.2.2 instanceof

  • 判斷對象用 instanceof,其內部機制是經過原型鏈來判斷的

  • instanceof原理:判斷實例對象的__proto__屬性,和構造函數的prototype屬性,是否爲同一個引用(是否指向同一個地址)

  • 注意1:雖說,實例是由構造函數 new 出來的,可是實例的__proto__屬性引用的是構造函數的prototype。也就是說,實例的__proto__屬性與構造函數自己無關。

    注意2:在原型鏈上,原型的上面可能還會有原型,以此類推往上走,繼續找__proto__屬性。這條鏈上若是能找到, instanceof 的返回結果也是 true。

咱們也能夠試着實現一下 instanceof
function instanceof(left, right) {
    // 得到類型的原型
    let prototype = right.prototype
    // 得到對象的原型
    left = left.__proto__
    // 判斷對象的類型是否等於類型的原型
    while (true) {
    	if (left === null)
    		return false
    	if (prototype === left)
    		return true
    	left = left.__proto__
    }
}
複製代碼
  • 分析一個問題

問題:已知A繼承了B,B繼承了C。怎麼判斷 a 是由A直接生成的實例,仍是B直接生成的實例呢?仍是C直接生成的實例呢?

分析:這就要用到原型的constructor屬性了。

  • foo.__proto__.constructor === Foo的結果爲true,可是 foo.__proto__.constructor === Object的結果爲false。

因此,用 consturctor判斷就比用 instanceof判斷,更爲嚴謹。

  • 若是咱們想得到一個變量的正確類型,能夠經過 Object.prototype.toString.call(xx)

3. 類型轉換

2.3.1 顯示類型轉換

  • 轉換成字符串 String()

    toString() 能夠被顯式調用,或者在須要字符串化時自動調用

    null 轉換爲 "null",undefined 轉換爲 "undefined",true 轉換爲 "true"。 數字的字符串化則遵循通用規則 極小和極大的 數字使用指數形式:

    // 1.07 連續乘以七個 1000
    var a = 1.07 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000;
    // 七個1000一共21位數字 
    a.toString(); // "1.07e21"
    複製代碼

    數組的默認 toString() 方法通過了從新定義,將全部單元字符串化之後再用 "," 鏈接起來

    var a = [1,2,3];
     a.toString(); // "1,2,3"
    複製代碼
  • 轉換成數字 Number()

    其中 true 轉換爲 1,false 轉換爲 0。undefined 轉換爲 NaN,null 轉換爲 0。 處理失敗 時返回 NaN(處理數字常量失敗時會產生語法錯誤)

  • 轉換成布爾值 Boolean()

    • undefined
    • null
    • false
    • +0、-0 和 NaN
    • ""
    複製代碼

    除了上面之外的,都爲true

2.3.2 隱式類型轉換

  • 字符串和數字之間的隱式轉換

    一個坑

    [] + {}; // "[object Object]" {} + []; // 0

    console.log([] + {}); //[object Object]

    console.log({} + []); //[object Object]

    第一行代碼中,{} 出如今 + 運算符表達式中,所以它被看成一個值(空對象)來處理。 第二行代碼中,{} 被看成一個獨立的空代碼塊(不執行任何操做)。代碼塊結尾不須要分號,因此這裏不存在語法上的問題。最後 + [] 將 [] 顯式強制類型轉換(參見第 4 章) 爲 0。

    第四行代碼中,{} 其實應該當成一個代碼塊,而不是一個 Object,當你在console.log使用的時候,{} 被當成了一個 Object

  • 隱式強制類型轉換爲布爾值

    下面的狀況會發生 布爾值隱式強制類型轉換。

    • (1)if (..)語句中的條件判斷表達式。
    • (2)for ( .. ; .. ; .. )語句中的條件判斷表達式(第二個)。
    • (3) while (..) 和 do..while(..) 循環中的條件判斷表達式。
    • (4)? :中的條件判斷表達式。
    • (5) 邏輯運算符 ||(邏輯或)和 &&(邏輯與)左邊的操做數(做爲條件判斷表達式)。
  • || 與 &&

  • == 與 ===

    == 容許在相等比較中進行強制類型轉換,而 === 不容許

    [] == ![] //true
    複製代碼

    參考連接:爲何[] ==![]

2、函數

1. 函數調用

4種方式,每種方式的不一樣在於this的初始化

通常而言,在Javascript中,this指向函數執行時的當前對象。

1.1. 做爲一個函數調用

function myFunction(a, b) {
    return a * b;
}
myFunction(10, 2);  // myFunction(10, 2) 返回 20
window.myFunction(10, 2); //myFunction() 和 window.myFunction() 是同樣的
複製代碼

函數做爲全局對象調用,會使 this 的值成爲全局對象。 使用 window 對象做爲一個變量容易形成程序崩潰。

1.2 函數做爲方法調用

var myObject = {
    firstName:"John",
    lastName: "Doe",
    fullName: function () {
        return this.firstName + " " + this.lastName;
    }
}
myObject.fullName();         // 返回 "John Doe"
複製代碼

fullName 方法是一個函數。函數屬於對象。 myObject 是函數的全部者。this對象,擁有 JavaScript 代碼。實例中 this 的值爲 myObject 對象。

函數做爲對象方法調用,會使得 this 的值成爲對象自己。

1.3 使用構造函數調用

// 構造函數:
function myFunction(arg1, arg2) {
    this.firstName = arg1;
    this.lastName  = arg2;
}
 
// This creates a new object
var x = new myFunction("John","Doe");
x.firstName;                             // 返回 "John"
複製代碼

構造函數中 this 關鍵字沒有任何的值。 this 的值在函數調用實例化對象(new object)時建立。

1.4 使用函數的方法調用

call()apply() 是預約義的函數方法。 兩個方法可用於調用函數,兩個方法的第一個參數必須是對象自己。

function myFunction(a, b) {
    return a * b;
}
myObject = myFunction.call(myObject, 10, 2);     // 返回 20
複製代碼
function myFunction(a, b) {
    return a * b;
}
myArray = [10, 2];
myObject = myFunction.apply(myObject, myArray);  // 返回 20
複製代碼

經過 call() 或 apply() 方法你能夠設置 this 的值, 且做爲已存在對象的新方法調用。

在 JavaScript 嚴格模式(strict mode)下, 在調用函數時第一個參數會成爲 this 的值, 即便該參數不是一個對象。

在 JavaScript 非嚴格模式(non-strict mode)下, 若是第一個參數的值是 null 或 undefined, 它將使用全局對象替代。

2. call、apply、bind的區別

  • call和apply改變了函數的this上下文後便當即執行該函數,而bind不會當即執行函數,而是將函數返回。
  • 他們第一個參數都是要改變上下文的對象,而call、bind從第二個參數開始以參數列表的形式展示,apply則是把除了改變上下文對象的參數放在一個數組裏面做爲它的第二個參數。

參考連接:詳解call、apply、bind

3. 做用域及做用域鏈

  • 做用域就是變量與函數的可訪問範圍,即做用域控制着變量與函數的可見性和生命週期

  • 內層函數可訪問外層函數局部變量

  • 外層函數不能訪問內層函數局部變量

  • 通俗地講,當聲明一個函數時,局部做用域一級一級向上包起來,就是做用域鏈。

    1.當執行函數時,老是先從函數內部找尋局部變量

    2.若是內部找不到(函數的局部做用域沒有),則會向建立函數的做用域(聲明函數的做用域)尋找,依次向上

4. 閉包

4.1 什麼是閉包?

閉包就是可以讀取其餘函數內部變量的函數。

函數 A 內部有一個函數 B,函數 B 能夠訪問到函數 A 中的變量,那麼函數 B 就是閉包。

4.2 閉包的用途

  • 能夠讀取函數內部的變量
  • 讓這些變量始終保持在內存中

經典面試題,循環中使用閉包解決 var 定義函數的問題

for (var i = 1; i <= 5; i++) {
  setTimeout(function timer() {
    console.log(i)
  }, i * 1000)
}
//咱們但願輸出的是1,2,3,4,5,但由於 setTimeout 是個異步函數,因此會先把循環所有執行完畢,這時候 i就是 6 了,因此會輸出五個6
複製代碼

解決方法:

  1. 閉包

    for (var i = 1; i <= 5; i++) {
      (function(j) {
        setTimeout(function timer() {
          console.log(j)
        }, j * 1000)
      })(i)
    }
    複製代碼
  2. 使用 setTimeout 的第三個參數,這個參數會被當成 timer 函數的參數傳入

    for (var i = 1; i <= 5; i++) {
      setTimeout(
        function timer(j) {
          console.log(j)
        },
        i * 1000,
        i
      )
    }
    複製代碼
  3. 使用let

    for (let i = 1; i <= 5; i++) {
      setTimeout(function timer() {
        console.log(i)
      }, i * 1000)
    }
    複製代碼

4.3 閉包的優缺點

  • 優勢:避免全局變量的污染
  • 缺點:因爲閉包會使得函數中的變量都被保存在內存中,內存消耗很大,因此不能濫用閉包,不然會形成網頁的性能問題,在IE中可能致使內存泄露
  • 解決方法:在退出函數以前,將不使用的局部變量所有刪除

3、對象

1. 建立對象的方式

  1. 對象字面量

    person={firstname:"Mark",lastname:"Yun",age:25,eyecolor:"black"};
    複製代碼
  2. 工廠方式(內置對象)

    var wcDog =new Object();
         wcDog.name="旺財";
         wcDog.age=3;
         wcDog.work=function(){
           alert("我是"+wcDog.name+",汪汪汪......");
         }
         wcDog.work();
    複製代碼

    方式1與方式2效果同樣,第一種寫法,person會指向Object

  3. 經過構造函數

    // 無參
    function Person(){}
    	var person=new Person();//定義一個function,若是使用new"實例化",該function能夠看做是一個Class
            person.name="Mark";
            person.age="25";
            person.work=function(){
            alert(person.name+" hello...");
    }
    person.work();
    複製代碼
    // 帶參(用this關鍵字定義構造的上下文屬性)
    function Pet(name,age,hobby){
           this.name=name;//this做用域:當前對象
           this.age=age;
           this.hobby=hobby;
           this.eat=function(){
               alert("我叫"+this.name+",我喜歡"+this.hobby+",是個程序員");
           }
    }
    var maidou =new Pet("麥兜",25,"coding");//實例化、建立對象
    maidou.eat();//調用eat方法
    複製代碼
  4. Object.create

    var p = {name:'smyhvae'};
    var obj3 = Object.create(p);  //此方法建立的對象,是用原型鏈鏈接的,obj3是實例,p是obj3的原型(name是p原型裏的屬性),構造函數是Objecet
    複製代碼

2. new

2.1 new的做用

  • new出來的實例能夠訪問到構造函數中的屬性
  • new出來的實例能夠訪問到構造函數原型鏈中的屬性(實例與構造函數經過原型鏈鏈接了起來)

2.2 new的原理

  • 建立一個新的空對象實例。
  • 將此空對象的隱式原型指向其構造函數的顯示原型。
  • 執行構造函數(傳入相應的參數,若是沒有參數就不用傳),同時 this 指向這個新實例。
  • 若是返回值是一個新對象,那麼直接返回該對象;若是無返回值或者返回一個非對象值,那麼就將步驟(1)建立的對象返回。

2.3 如何實現new

function create(Con, ...args) {
  let obj = {}
  Object.setPrototypeOf(obj, Con.prototype)
  let result = Con.apply(obj, args)
  return result instanceof Object ? result : obj
}
複製代碼
  1. 首先函數接受不定量的參數,第一個參數爲構造函數,接下來的參數被構造函數使用

  2. 而後內部建立一個空對象 obj

  3. 由於 obj 對象須要訪問到構造函數原型鏈上的屬性,因此咱們經過 setPrototypeOf 將二者聯繫起來。這段代碼等同於 obj.__proto__ = Con.prototype

  4. obj 綁定到構造函數上,而且傳入剩餘的參數

  5. 判斷構造函數返回值是否爲對象,若是爲對象就使用構造函數返回的值,不然使用 obj,這樣就實現了忽略構造函數返回的原始值

參考連接:new操做符

2.4 new建立對象和字面量建立對象有何區別?

  • 不管是function Foo()仍是let a = { b : 1 },其實都是經過new產生的
  • 使用 new Object() 的方式建立對象須要經過做用域鏈一層層找到 Object,可是你使用字面量的方式就沒這個問題
function Foo() {}
// function 就是個語法糖
// 內部等同於 new Function()
let a = { b: 1 }
// 這個字面量內部也是使用了 new Object()
複製代碼

3. 原型及原型鏈

3.1 概念

  • 每一個對象都會在其內部初始化一個屬性,就是prototype(原型)
  • 當咱們訪問一個對象的屬性時,若是這個對象內部不存在這個屬性,那麼他就會去prototype裏找這個屬性,這個prototype又會有本身的prototype,因而就這樣一直找下去,也就是咱們平時所說的原型鏈的概念
  • 任何一個實例,經過原型鏈,找到它上面的原型,該原型對象中的方法和屬性,能夠被全部的原型實例共享。

3.2 原型、構造函數、實例

  • 構造函數經過new生成實例
  • 實例的構造函數屬性(constructor)指向構造函數
  • 原型對象(Person.prototype)是 構造函數(Person)的一個實例
person1.constructor === Person
Person.prototype.constructor === Person
複製代碼
  • 實例的__proto__指向其構造函數的原型
person1.__proto__ === Person.prototype
複製代碼

4. 繼承

4.1 構造函數繼承

function Parent1() {
        this.name = 'parent1 的屬性';
    }

    function Child1() {
        Parent1.call(this);         //【重要】此處用 call 或 apply 都行:改變 this 的指向,parent的實例 --> 改成指向child的實例
        this.type = 'child1 的屬性';
    }

    console.log(new Child1);
複製代碼

這種方式,雖然改變了 this 的指向,可是,Child1 沒法繼承 Parent1 的原型。也就是說,若是我給 Parent1 的原型增長一個方法,這個方法是沒法被 Child1 繼承的。

4.2 原型繼承

/* 經過原型鏈實現繼承 */
    function Parent() {
        this.name = 'Parent 的屬性';
    }

    function Child() {
        this.type = 'Child 的屬性';
    }

    Child.prototype = new Parent(); //【重要】

    console.log(new Child());
複製代碼

咱們把Parent的實例賦值給了Childprototye,從而實現繼承。此時,new Child.__proto__ === new Parent()的結果爲true

這種繼承方式,Child 能夠繼承 Parent 的原型,但有個缺點:

若是修改 child1實例的name屬性,child2實例中的name屬性也會跟着改變。形成這種缺點的緣由是:child1和child2共用原型。即:chi1d1.__proto__ === child2__proto__是嚴格相同。

4.3 組合繼承

用原型鏈實現對原型屬性和方法的繼承,用借用構造函數技術來實現實例屬性的繼承。

/* 組合方式實現繼承:構造函數、原型鏈 */
    function Parent3() {
        this.name = 'Parent 的屬性';
        this.arr = [1, 2, 3];
    }

    function Child3() {
        Parent3.call(this); //【重要1】執行 parent方法
        this.type = 'Child 的屬性';
    }
    Child3.prototype = new Parent3(); //【重要2】第二次執行parent方法

    var child = new Child3();
複製代碼

這種方式,能解決以前兩種方式的問題:既能夠繼承父類原型的內容,也不會形成原型裏屬性的修改。

這種方式的缺點是:讓父親Parent的構造方法執行了兩次。

4.4 ES6類繼承extends

extends關鍵字主要用於類聲明或者類表達式中,以建立一個類,該類是另外一個類的子類。其中constructor表示構造函數,一個類中只能有一個構造函數,有多個會報出SyntaxError錯誤,若是沒有顯式指定構造方法,則會添加默認的 constructor方法。

4、DOM事件

1.DOM事件的級別

  • DOM0

    element.onclick = function () {
    
        }
    複製代碼

​ 一是在標籤內寫onclick事件 二是在JS寫onclick=function(){}函數

  • DOM2

    //高版本瀏覽器
    	element.addEventListener('click', function () {
    
        }, false);
    	//IE8及如下版本
        element.attachEvent('onclick', function () {
    
        });
    	//兼容寫法
                /* * 參數: * element 要綁定事件的對象 * eventStr 事件的字符串(不要on) * callback 回調函數 */
    	function myBind(element , eventStr , callback){
            if(element.addEventListener){
                //大部分瀏覽器兼容的方式
                element.addEventListener(eventStr , callback , false);
            }else{
                //IE8及如下
                element.attachEvent("on"+eventStr , function(){
                    //在匿名函數 function 中調用回調函數callback
                    callback.call(element);
                });
            }
    複製代碼

    上面的第三參數中,true表示事件在捕獲階段觸發,false表示事件在冒泡階段觸發(默認)。若是不寫,則默認爲false。

  • DOM3

    element.addEventListener('keyup', function () {
    
        }, false);
    複製代碼

    DOM3中,增長了不少事件類型,好比鼠標事件、鍵盤事件等。

  • 爲什麼事件沒有DOM1的寫法呢?由於,DOM1標準制定的時候,沒有涉及與事件相關的內容。

2. 事件流

事件傳播的三個階段是:事件捕獲、事件目標、事件冒泡

  • 事件捕獲階段:事件從祖先元素往子元素查找(DOM樹結構),直到捕獲到事件目標 target。在這個過程當中,默認狀況下,事件相應的監聽函數是不會被觸發的。
  • 事件目標:當到達目標元素以後,執行目標元素該事件相應的處理函數。若是沒有綁定監聽函數,那就不執行。
  • 事件冒泡階段:事件從事件目標 target 開始,從子元素往冒泡祖先元素冒泡,直到頁面的最上一級標籤。

3. DOM事件模型(捕獲、冒泡)

3.1 事件捕獲

事件從祖先元素往子元素查找(DOM樹結構),直到捕獲到事件目標 target。

addEventListener能夠捕獲事件

element.addEventListener('click', function () {

    }, true);
複製代碼

參數爲true,表明事件在捕獲階段執行。

捕獲階段,事件依次傳遞的順序是:window --> document --> html--> body --> 父元素、子元素、目標元素。

window.addEventListener("click", function () {
        alert("捕獲 window");
    }, true);

    document.addEventListener("click", function () {
        alert("捕獲 document");
    }, true);

    document.documentElement.addEventListener("click", function () {
        alert("捕獲 html");
    }, true);  //獲取html節點

    document.body.addEventListener("click", function () {
        alert("捕獲 body");
    }, true);  //獲取body節點

    fatherBox.addEventListener("click", function () {
        alert("捕獲 father");
    }, true);

    childBox.addEventListener("click", function () {
        alert("捕獲 child");
    }, true);
複製代碼

3.2 事件冒泡

當一個元素上的事件被觸發的時候(好比說鼠標點擊了一個按鈕),一樣的事件將會在那個元素的全部祖先元素中被觸發。這一過程被稱爲事件冒泡;這個事件從原始元素開始一直冒泡到DOM樹的最上層。

通俗來說,冒泡指的是:子元素的事件被觸發時,父元素的一樣的事件也會被觸發。取消冒泡就是取消這種機制。

冒泡順序

通常的瀏覽器: (除IE6.0以外的瀏覽器)

  • div -> body -> html -> document -> window

IE6.0:

  • div -> body -> html -> document

不能冒泡的事件:

blur、focus、load、unload、onmouseenter、onmouseleave

檢查一個元素是否會冒泡:event.bubbles

阻止冒泡:

w3c的方法:(火狐、谷歌、IE11)

event.stopPropagation();
複製代碼

IE10如下:

event.cancelBubble = true
複製代碼

兼容代碼:

childBox.onclick = function(event){
    event = event || window.event;
    if(event && event.stopPropagation){
        event.stopPropagation();
    }else{
        event.cancelBubble = true;
    }
}
複製代碼

上方代碼中,咱們對childBox進行了阻止冒泡,產生的效果是:事件不會繼續傳遞到 father、grandfather、body了

4. event對象的常見應用(經常使用api方法)

  • 阻止默認事件

    event.preventDefault();
    複製代碼
    function stopDefault( event ) { 
        //阻止默認瀏覽器動做(W3C) 
        if ( event && event.preventDefault ) 
            event.preventDefault(); 
        //IE中阻止函數器默認動做的方式 
        else 
            window.event.returnValue = false; 
    }
    複製代碼

    好比,已知<a>標籤綁定了click事件,此時,若是給<a>設置了這個方法,就阻止了連接的默認跳轉

  • 阻止冒泡

    代碼見上

  • 設置事件優先級

    event.stopImmediatePropagation();
    複製代碼

    好比說,我用addEventListener給某按鈕同時註冊了事件A、事件B。此時,若是我單擊按鈕,就會依次執行事件A和事件B。如今要求:單擊按鈕時,只執行事件A,不執行事件B。該怎麼作呢?這是時候,就能夠用到stopImmediatePropagation方法了。作法是:在事件A的響應函數中加入這句話。

  • event.currentTarget   //當前所綁定的事件對象。在事件委託中,指的是【父元素】。
    
    event.target  //當前被點擊的元素。在事件委託中,指的是【子元素】。
    複製代碼

5. 自定義事件

var myEvent = new Event('clickTest');
    element.addEventListener('clickTest', function () {
        console.log('smyhvae');
    });

	//元素註冊事件
    element.dispatchEvent(myEvent); //注意,參數是寫事件對象 myEvent,不是寫 事件名 clickTest
複製代碼

6. 事件委託

事件代理/事件委託是利用事件冒泡的特性,將本應該綁定在多個元素上的事件綁定在他們的祖先元素上,尤爲在動態添加子元素的時候,能夠很是方便的提升程序性能,減少內存空間。

相關文章
相關標籤/搜索