JavaScript高級內容筆記:原型鏈、繼承、執行上下文、做用域鏈、閉包

最近在系統的學習JS深層次內容,並稍微整理了一下,做爲備忘和後期複習,這裏分享給你們,但願對你們有所幫助。若有錯誤請留言指正,tks。javascript

瞭解這些問題,我先一步步來看,先從稍微淺顯內容提及,而後引出這些概念。html

本文只用實例驗證結果,並作簡要說明,給你們增長些印象,由於單獨一項拿出來都須要大篇幅講解。java

1.值類型 & 引用類型

function show(x) {
    console.log(typeof(x)); // undefined
    console.log(typeof(10)); // number
    console.log(typeof('abc')); // string
    console.log(typeof(true)); // boolean

    console.log(typeof(function() {})); //function

    console.log(typeof([1, 'a', true])); //object
    console.log(typeof({
        a: 10,
        b: 20
    })); //object
    console.log(typeof(null)); //object
    console.log(typeof(new Number(10))); //object
}
show();

 

 

undefined, number, string, boolean 屬於簡單的值類型,不是對象;
函數、數組、對象、null、new Number(10)都是對象;
判斷一個變量是否是對象很是簡單。值類型的類型判斷用typeof,引用類型的類型判斷用instanceof。chrome

2. 語法糖(糖衣語法)

var obj = { a: 10, b: 20 };
var arr = [5, 'x', true];

 語法糖實際上是一種「快捷方式」,上面的代碼是一種縮寫,完整的寫法是:數組

var obj = new Object();
obj.a=10;
obj.b=20;
 
var arr= new Array();
arr[0]=5;
arr[1]='x';
arr[2]=true;

  

3. 對象都是經過函數建立的,而函數又是一種對象

方法一:閉包

var fn = function() {
    alert(100);
};
fn.a = 10;
fn.b = function() {
    alert(123);
};
fn.c = {
    name: "典橙貿易",
    year: 2016
};

fn是函數,a/b/c是fn建立的對象。app

方法二:函數

var obj = { a: 10, b: 20 };
var arr = [5, 'x', true];
 
其本質是:
var obj = new Object();
obj.a = 10;
obj.b = 20;
 
var arr = new Array();
arr[0] = 5;
arr[1] = 'x';
arr[2] = true;
 
而
console.log(typeof (Object)); // function
console.log(typeof (Array)); // function
 

 

4. 構造函數

只有構造函數纔有prototype屬性。
一般咱們自定義的函數都屬於構造函數,因此都有此屬性。
JS運行時環境內置的函數有些不是構造函數,好比alert和Math.sqrt等,就沒有此屬性。
注:構造函數是指有一個內部屬性[[Construct]],經過new能夠建立對象的那些函數。學習

5. prototype 與 __proto__

每一個函數function都有一個prototype,即原型。
每一個對象都有一個__proto__,即隱式原型。
只有構造函數纔有prototype屬性。
對象的__proto__指向的是建立它的函數的prototype。
函數的prototype都是被Object建立的,每一個自定義函數建立之初,都會有一個prototype(它是如何被建立的,不得而知!)。
javascript_object_layout
例如:this

function Foo(){};
var foo = new Foo();
Foo.__proto__ === Function.prototype // 任何函數都是由Function建立的,因此任何函數的__proto__都指向Function的prototype
foo.__proto__ === Foo.prototype // 對象的__proto__指向的是建立它的函數的prototype
Foo.prototype.__proto__ === Object.prototype // 若是是自定義的函數,它的prototype.__proto__ 指向 Object.prototype,由於自定義函數的prototype本質上就是和語法糖 var obj = {} 是同樣的,都是被Object建立。
Object.prototype.__proto__ === null // Object.prototype也是個對象,它的__proto__是null(這是個特例,切記切記)
 
foo.prototype == undefined //只有函數纔有prototype屬性,foo是對象
Object.prototype === (new Object()).__proto__ // 如上所述,Object自己是一個函數,(new Object())是對象
Function.prototype.__proto__ === Object.prototype // Function是一個函數,它的prototype是被Object建立,因此它的Prototype.__proto__指向建立Object.prototype
Function.__proto__ === Function.prototype //Function也是一個函數,函數是一種對象,也有__proto__屬性。既然是函數,那麼它必定是被Function建立。因此——Function是被自身建立的
Object.__proto__ === Function.prototype //Object是個函數,也是對象,它的__proto__指向的是建立它的函數的prototype

參考:

http://www.cnblogs.com/wangfupeng1988/p/3978131.html
http://www.cnblogs.com/wangfupeng1988/p/3979290.html

6. instanceof原理

Instanceof運算符的第一個變量是一個對象,暫時稱爲A;第二個變量通常是一個函數,暫時稱爲B。
Instanceof的判斷規則是:
  沿着A的__proto__這條線來找,若是B的prototype在這條線上,那麼就返回true,不然返回false。

 

如:

function Foo(){}
var f1 = new Foo();
console.log(f1 instanceof Foo);    // true
console.log(f1 instanceof Object); // true

分析方法:
  f1的__proto__路線A:

(1) f1.__proto__ === Foo.prototype
(2) Foo.prototype.__proto__ === Object.prototype
(3) Object.prototype.__proto__ === null //到終點

結論:
  f1 instanceof Foo :A線路(1)中出現了Foo.prototype
  f1 instanceof Object :A線路(2)中出現了Object.prototype


參考:

http://www.cnblogs.com/wangfupeng1988/p/3979533.html

7. 原型鏈

訪問一個對象的屬性時,先在基本屬性中查找,若是沒有,再沿着__proto__這條鏈向上找,這就是原型鏈。
例如:

function Foo(){}
var f1 = new Foo();
f1.a = 2;
f1.b = 3;
Foo.prototype.b = 6;
Foo.prototype.c = 7;
 
console.log(f1.a); // 2,a是f1的基本屬性,直接訪問沒問題
console.log(f1.c); // 7,f1的基本屬性中沒有c,沿着原型鏈向上,f1.__proto__是Foo.prototype,而Foo.prototype有c,能夠訪問。
console.log(f1.b); // 3,b是f1的基本屬性,直接訪問沒問題。可Foo.prototype還有一個b,可是不會顯示,這種狀況被稱爲「屬性遮蔽」。

 

判斷屬性是基本屬性(也叫實例屬性)仍是原型鏈上的屬性(也叫原型屬性)使用hasOwnProperty,通常在for…in..循環中。for…in…會輸出實例屬性和原型屬性。
屬性列表參考實例屬性 vs 原型的屬性 vs 靜態屬性
如:

var item;
for(item in f1){
    console.log(item); // a,b,c
}
 
for(item in f1){
    f1.hasOwnProperty(item) && console.log(item); // a,b
}

 

8. 繼承

在原型鏈的基礎上,很容易理解繼承。例如在原型鏈的實例中,f1.hasOwnProperty就是繼承自Object.prototype。
過程以下:

  • f1基本屬性中沒有hasOwnProperty,沿原型鏈向上找:f1.__proto__是Foo.prototype;
  • Foo.prototype中也沒有hasOwnProperty,繼續沿原型鏈向上找:Foo.prototype.__proto__是Object.prototype;
  • Object.prototype中有hasOwnProperty,查找結束,容許訪問hasOwnProperty。

因爲全部的對象的原型鏈都會找到Object.prototype,所以全部的對象都會有Object.prototype的方法。這就是所謂的「繼承」。
另外,每一個函數都有的call,apply方法,和length,arguments,caller等屬性也都是繼承下來的。

9. 執行上下文/執行上下文環境

定義1:js在執行一段代碼以前,會聲明提早,賦值或賦初始值(即undefined)。因此同一代碼段中,能夠先訪問,再定義,這就是「執行上下文」。
定義2:在執行代碼以前,把將要用到的全部的變量都事先拿出來,有的直接賦值了,有的先用undefined佔個空。

分4種狀況:

第1種狀況:變量、函數表達式——只提早聲明,不提早賦值,默認賦值爲undefined;

console.log(a); // undefined
var a=10;
console.log(fn1); // undefined
var fn1 = function(){};

第2種狀況:this關鍵字——提早聲明並賦值

console.log(this); // Window {external: Object, chrome: Object, document: document, __ctrip: Object, __SERVERDATE__: Object…}

 

第3種狀況:函數聲明——提早聲明並賦值

console.log(fn2); // function fn2(){}
function fn2(){};

 

第4種狀況:函數內部會建立本身獨有的上下文環境

函數每被調用一次,都會產生一個新的執行上下文環境。由於不一樣的調用可能就會有不一樣的參數。
另外,每一個函數在定義的時候(不是調用的時候),就已經肯定了函數體內部自由變量的做用域。
如:

var a = 10;

function fn() {
    console.log(a); // a是自由變量,函數建立時,就肯定了a要取值的做用域
}

function bar(f) {
    var a = 20;
    f(); // 10,到建立fn的地方找變量a的做用域,而不是調用它的當前做用域。
}
bar(fn);

 

 

10. 執行上下文

如【執行上下文/執行上下文環境】所述,js代碼中,會有無數的函數調用和嵌套調用,會產生無數的上下文環境,這麼多上下文環境如何管理,以及如何銷燬而釋放內存呢?這就須要【執行上下文棧】的參與了。

執行上下文棧:執行全局代碼時,會產生一個全局上下文環境,並將該上下文環境壓入棧,每次調用函數都又會產生執行上下文環境,並將該函數上下文環境也壓入棧。當函數調用完成時,該函數上下文環境出棧,並消除該函數上下文環境以及其中的數據,再從新回到全局上下文環境。處於活動狀態的執行上下文環境只有一個。其實這是一個壓棧出棧的過程。
執行上下文棧
如:

var a = 10, // 1. 進入全局上下文環境
fn,
bar = function(x) {
    var b = 5;
    fn(x + b); // 3. 進入fn函數上下文環境,併入棧,設爲活動狀態。該函數執行完畢後,bar函數上下文出棧,並及時銷燬,釋放內存
};
 
fn = function(y) {
    var c = 5;
    console.log(y + c);
}
 
bar(10); // 2. 進入bar函數上下文環境,併入棧,設爲活動狀態。該函數執行完畢後,bar函數上下文出棧,並及時銷燬,釋放內存
 

 

參考:http://www.cnblogs.com/wangfupeng1988/p/3989357.html

11. this

  this在執行上下文中有着很是重要的做用,這裏應該說一下。

  瞭解this,須要記住一點:在函數中this到底取何值,是在函數真正被調用執行的時候肯定的,函數定義的時候肯定不了。

  大體分爲如下4種狀況:

狀況1:構造函數,this就表明new出來的對象。

function Foo(){
    this.name = '典橙貿易';
    this.year = 2016;
    console.log(this); // Foo {name: "典橙貿易", year: 2016}
}
var f1 = new Foo(); // 所謂構造函數就是用來new對象的函數。

 

另外,若是在原型鏈中使用this(Foo.prototype中使用this),this仍然表明new出來的對象。

function Foo(){
    this.name = '典橙貿易2016';
}
Foo.prototype.getName = function(){
    console.log(this); // Foo {name: "典橙貿易2016"}
}
var f1 = new Foo();
f1.getName();

 

狀況2:函數做爲對象的一個屬性,而且被對象直接調用時,this指向該對象。

var obj = {
    name:'典橙貿易',
    fn:function(){
        console.log(this); // Object {name: "典橙貿易"}
        console.log(this.name); // 典橙貿易
    }
}
obj.fn(); // 直接調用
 
// 若是不是直接調用,this就指向window
var obj = {
    name: '典橙貿易',
    fn: function() {
        console.log(this); // Window {external: Object, chrome: Object, document: document, __ctrip: Object, __SERVERDATE__: Object…}
        console.log(this.name); // undefined
    }
}
var fn1 = obj.fn;
fn1();

狀況3:函數用call或者apply調用,this就是傳入的對象的值

var obj ={
    name:'典橙貿易'
}
var fn = function(){
    console.log(this); // Object {name: "典橙貿易"}
    console.log(this.name); // 典橙貿易
}
fn.call(obj);
// 或者
// fn.apply(obj);

 

狀況4:全局 & 調用普通函數,this是window

全局:

console.log(this===window); // true
 
調用普通函數:
var name ="典橙貿易";
var fn = function() {
    console.log(this); // Window {external: Object, chrome: Object,     document: document, __ctrip: Object, __SERVERDATE__: Object…}
    console.log(this.name); // 典橙貿易
}
fn();

 

注意:

var obj = {
    name: '典橙貿易',
    fn: function() {

        function f() {
            console.log(this); // Window {external: Object, chrome: Object,     document: document, __ctrip: Object, __SERVERDATE__: Object…}
            console.log(this.name); // undefined
        }
        f(); // 函數f雖然是在obj.fn內部定義的,可是它仍然是一個普通的函數,this仍然指向window。

    }
}
obj.fn();

 

12. 做用域

知識點1:javascript中沒有塊級做用域。

var i = 2;
if(i>1){
    var name ='典橙貿易';
}
console.log(name); // 典橙貿易
for循環的{}也是相似。

 

知識點2:javascript除了全局做用域以外,只有函數能夠建立本身的做用域,稱爲函數做用域。

如經典的問題:

<ul id = "list">
    <li> we </li>
    <li> sdf </li>
    <li> cx </li>
    <li> h </li>
    <li> z </li>
</ul>

 

var list = document.getElementById('list');
var e = list.getElementsByTagName('li');
var i = 0;

錯誤寫法:
for (; i < e.length; i++) {
  e[i].onclick = function() {
    console.log(i);
  }
}

正確寫法:
for (; i < e.length; i++) {
  (function(a) {
    e[i].onclick = function() {
      console.log(a);
    }
  })(i);
}
等效於:
for (; i < 5; i++) {
  var Fn = function(a) {
    e[i].onclick = function() {
      console.log(a);
    }
  }
  Fn(i);
}

錯誤的緣由:
  e[i].onclick 每次循環,只是賦值給onclick,而for循環是沒有塊級做用域的,因此i的值會不斷累加,直到最大值5,故每次循環都會輸出5。
正確的緣由:
  因爲函數能夠建立本身的做用域,並且各個做用域間不會相互影響,因此每次循環Fn(i)都會建立一個特有的函數做用域提供給相應的onclick,而每一個做用於中的a變量也不會相互影響。

知識點3:做用域最大的用處就是隔離變量,不一樣做用域下同名變量不會有衝突。

{
    var a = 1,
        b = 2;

    function fn1() {
        var a = 100,
            b = 200;

        function fn2() {
            var a = 1000,
                b = 2000;
        }
    }
} 

三個做用域下都聲明瞭「a和b」變量,可是他們不會有衝突。各自的做用域下,用各自的「a和b」。

 

13. 自由變量及其取值規則

在A做用域中使用的變量x,卻沒有在A做用域中聲明(即在其餘做用域中聲明的),對於A做用域來講,x就是一個自由變量。
如:

var x=1;
function fn(){
    var b=2;
    console.log(x+b); // 這裏的x就是自由變量
}

自由變量取值規則:要到建立 自由變量所在函數 的那個做用域中取值–是【建立】,而不是【調用】,也不是【父做用域】。

如:

var x = 10;
function fn() {
    console.log(x); // fn建立了自由的函數做用域,因此不管什麼地方調用它,都會輸出10
}
function show(f) {
    var x = 20;
    (function() {
        console.log(x); // 要到x所在的匿名函數的做用域中找x,故輸出20
        f(); // 要到建立fn函數的做用域中找x,故這裏輸出10,而不是20
    })();
}
show(fn);

  

14. 做用域鏈

在自由變量的基礎上理解做用域鏈更加容易。
在做用域Fn中使用一個變量A,若是Fn中沒有定義A,則到建立Fn的那個做用域Fm中找,若是仍沒有找到,則繼續到建立Fm的做用域中找……依次類推,直至找到變量A或者到全局做用域中仍未找到 爲止,這種跨越多個做用域的線路,就叫「做用域鏈」。
代碼以下:

{
    // var A = 4; // 第4步,這是全局做用域,找到則返回4,若是到這裏仍未找到,就結束,報錯"A is not defined"

    function Fw() {
        // var A = 3; // 第3步,在做用域Fw中找,找到則返回3,不然到建立Fw的全局做用域中找

        function Fm() {
            // var A = 2; // 第2步,在做用域Fm中找,找到則返回2,不然到建立Fm的Fw中找

            function Fn() {
                // var A = 1; // 第1步,在當前做用域找,找到則返回1,不然到建立Fn的Fm中找
                console.log(A);
            }
            return Fn();
        }
        return Fm();
    }
    Fw();
}

注意:

  這裏說的建立Fn的那個做用域,而不是調用Fn的那個做用域,也不是「父做用域」。詳情參考【自由變量及其取值規則 > 自由變量取值規則】中的實例。

15. 閉包

要想理解閉包,前面的【做用域、自由變量、做用域鏈】三部分是基礎。
閉包的兩種形式:函數做爲返回值函數做爲參數被傳遞

第一,函數做爲返回值

function fn() {
    var max = 10;
    return function bar(x) {
        if (x > max) { // max是自由變量,取值規則,參考【自由變量及其取值規則】
            console.log(x);
        }
    }
}
var f1 = fn();
f1(15); // 15

第二,函數做爲參數被傳遞

var max = 10,
    fn = function(x) {
        if (x > max) { // max是自由變量,取值規則,參考【自由變量及其取值規則】
            console.log(x);
        }
    };
(function(f) {
    var max = 100;
    f(15); // 15
})(fn)

另外,
在【執行上下文棧】中說到,每一個函數執行完畢,都會銷燬其函數上下文環境,並清空數據。可是閉包函數執行完後,上下文環境不會被銷燬。由於閉包中函數會返回或者做爲參數被傳遞,在其餘地方會被用到,一旦銷燬,調用閉包的地方就沒法再使用了。因此閉包會增長內容開銷。

 

參考:

  http://www.cnblogs.com/wangfupeng1988/p/3994065.html

  http://blog.csdn.net/yueguanghaidao/article/details/9568071

  http://blog.csdn.net/lmj623565791/article/details/25076713

相關文章
相關標籤/搜索