深入理解javascript原型和閉包

王福朋 - 博客園 —— 《 深入理解javascript原型和閉包

目錄:深入理解javascript原型和閉包[目錄]

1. 一切都是對象

原文鏈接:http://www.cnblogs.com/wangfupeng1988/p/3977987.html

本文要點1

一切(引用類型)都是對象涕滋,對象是屬性的集合磷支。

1. javascript 數(shù)據(jù)類型

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

引用類型(是對象):函數(shù)疮跑,數(shù)組以清,對象,null, new Number(10)

值類型和引用類型的類型判斷方式:

  • 值類型的類型判斷用 typeof
  • 引用類型的類型判斷使用 instanceof
var fn = function () {};
console.log(fn instanceof Object);  // true

2. javascript 對象

定義:若干屬性的集合(只有屬性贮竟,沒有方法丽焊,方法也是一種屬性)

var obj = {
    a: 10,
    b: function (x) {
        alert(this.a + x)
    },
    c: {
        name: 'tanya',
        year: 1975
    }
}

函數(shù)也是一種對象(可以定義屬性):

var fn = function () {
    alert(100);
};

fn.a = 10;
fn.b = function () {
    alert(123);
};
fn.c = {
    name: 'tanya',
    year: 1975
}

數(shù)組也是一種對象(本身就存在 length 屬性):

var arr = [1, 2, 3];
console.log(arr.length); // 3

arr.a = 'tanya';
arr.b = function () {
    alert(123);
}
arr.c = {
    name: 'tanya',
    year: 1975
}

for (var item in arr) {
    console.log(item) // 0 1 a b c
}

2. 函數(shù)和對象的關系

原文鏈接:http://www.cnblogs.com/wangfupeng1988/p/3978035.html

本文要點2

對象是通過函數(shù)創(chuàng)建的,而函數(shù)又是一種對象咕别。

對象是通過函數(shù)創(chuàng)建的

function Fn () {
    this.name = 'tanya';
    this.age = 1975;
}

var fn = new Fn(); // 說 fn 是對象技健,因為它有屬性
console.log(fn.name)  // tanya
console.log(fn.age)   // 1975

通過字面量創(chuàng)建對象或數(shù)組:

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;

其中 Object 和 Array 是函數(shù):

console.log(typeof Object);  // function
console.log(typeof Array);   // function

3. prototype 原型

原文鏈接:http://www.cnblogs.com/wangfupeng1988/p/3978131.html

什么是原型?

函數(shù)都有一個 prototype 屬性惰拱,它是一個對象雌贱,

函數(shù)的實例對象的 __proto__ 屬性指向該函數(shù)的 prototype 屬性,

那我們稱該對象為函數(shù)實例對象的原型

本文要點3

理解原型概念弓颈。

1. prototype 和 constructor 屬性

函數(shù)有一個默認屬性 prototype帽芽,屬性值是一個對象删掀,該對象默認只有一個 constructor 屬性翔冀,指向這個函數(shù)本身。

3.prototype-圖1.png

如上圖:SuperType 是一個函數(shù)披泪,右側的方框就是它的原型纤子。

Object 原型里面有幾個其他屬性:

3.prototype-圖2.png

2. 在 prototype 上添加屬性

function Fn() {}
Fn.prototype.name = 'tanya'
Fn.prototype.getYear = function () {
    return 1975;
};
3.prototype-圖3.png

4. 隱式原型

原文鏈接:http://www.cnblogs.com/wangfupeng1988/p/3979290.html

每個函數(shù)都有一個 prototype 屬性,即原型款票。每個對象都有一個 __proto__控硼,可稱為隱式原型。

本文要點4

理解隱式原型 __proto__艾少。

1. __proto__ 屬性

關于 __proto__ 屬性說明:是一個隱藏屬性卡乾,低版本瀏覽器不支持該屬性。

var obj = {};
console.log(obj.__proto__);
console.log(Object.prototype);
console.log(obj.__proto__ === Object.prototype);

以上代碼說明:每個對象都有一個 __proto__屬性缚够,指向創(chuàng)建該對象(這里是 obj)的函數(shù)(這里是 Object)的 prototype幔妨。

4.__proto__-圖1.png

2. Object.prototype__proto__

自定義函數(shù)的 prototype:本質和 var obj = {} 是一樣的,都是被 Object 創(chuàng)建的谍椅,所以它的 __proto__ 指向的就是 Object.prototype误堡。

4.__proto__-圖2.png

Object.prototype 是一個特例——它的 __proto__ 指向的是 null。

3. 函數(shù)的 __proto__

函數(shù)也是一種對象雏吭,所以函數(shù)也有 __proto__ 屬性锁施。至于函數(shù)的 __proto__ 是什么,還是要看函數(shù)是被誰創(chuàng)建的。

function fn(x, y) {
    return x + y;
}
console.log(fn(10, 20));  // 30

// 等價于

var fn = new Function('x', 'y', 'return x + y;');
console.log(fn(5, 6));  // 11

函數(shù)是被 Function 創(chuàng)建的悉抵。

上面有說過:對象的 __proto__ 指向的是創(chuàng)建它的函數(shù)的 prototype肩狂,則會有 Object.__proto__ === Function.prototype

解釋一下:這里 Object 是一個函數(shù)姥饰,被 Function 所創(chuàng)建婚温,函數(shù)也是一種對象,所以 Object 有 __proto__ 屬性媳否,指向創(chuàng)建它(Object)的函數(shù)(Function)的原型

4.__proto__-圖3.png

上圖中栅螟,自定義函數(shù)的 Foo.__proto__ 指向 Function.prototypeObject.__proto__ 指向 Function.prototype篱竭,還有一個Function.__proto__ 指向 Function.prototype力图。

如何理解 Function.__proto__ 指向 Function.prototype

Function 是一個函數(shù)掺逼,函數(shù)是一種對象吃媒,對象有 __proto__ 屬性。 既然是函數(shù)吕喘,那么是被 Function 創(chuàng)建的赘那,也就是 Function 是被自身創(chuàng)建的。對象的 __proto__ 指向創(chuàng)建它的函數(shù)的 prototype氯质,所以 Function__proto__ 指向了自身的 prototype募舟。

5. instanceof

原文鏈接:http://www.cnblogs.com/wangfupeng1988/p/3979290.html

本文要點5

instanceof 判定規(guī)則。

instanceof 判定

語法是 A instanceof B闻察,A是對象拱礁,B一般是一個函數(shù)。

instanceof 判斷規(guī)則:沿著 A 的 __proto__ 這條線來找辕漂,如果能找到與 B.prototype 相等的同一個引用呢灶,即同一個對象,就返回 true钉嘹。如果找到終點(Object.prototype.__proto__)還未重合鸯乃,則返回 false。

代碼實現(xiàn):

function instance_of(L, R) { // L 表示左表達式跋涣,R 表示右表達式
    var O = R.prototype; // 取 R 的顯示原型
    L = L.__proto__; // 取 L 的隱式原型
    while (true) {
        if (L === null){
            return false;
        }
        if (O === L) { // 這里重點:當 O 嚴格等于 L 時缨睡,返回 true
            return true;
        }
        L = L.__proto__;
    }
}
5.instanceof-圖1.png

根據(jù)上圖及 instanceof 判定規(guī)則理解:

console.log(Object instanceof Function);  // true
console.log(Function instanceof Object);  // true
console.log(Function instanceof Function);  // true

6. “繼承”

原文鏈接:http://www.cnblogs.com/wangfupeng1988/p/3979985.html

本文要點6

  1. 理解原型鏈概念
  2. 知道屬性查找方式

1. 原型鏈

javascript 中的繼承是通過原型鏈來體現(xiàn)的。

當訪問一個對象的屬性時仆潮,先在基本屬性(自身屬性)中查找宏蛉,如果沒有,再沿著 __proto__ 這條鏈向上找性置,直到 Object.prototype.__proto__拾并,如果還沒找到就返回 undefined,這條在原型上查找的鏈稱為原型鏈。

function Foo() {}
var f1 = new Foo();

f1.a = 10;

Foo.prototype.a = 100;
Foo.prototype.b = 200;

console.log(f1.a);  // 10
console.log(f1.b);  // 200
6.繼承-圖1.png

2. hasOwnProperty

通過 hasOwnProperty 方法可以判斷屬性是基本的(自身)還是從原型中找到的嗅义。

function Foo() {}
var f1 = new Foo();

f1.a = 10;

Foo.prototype.a = 100;
Foo.prototype.b = 200;

for (var item1 in f1) {
    if (f1.hasOwnProperty(item1)) { // 只打印自身屬性
        console.log(item1);  // a
    }
}

for (var item2 in f1) {
    console.log(item2); // a b
}

那么 hasOwnProperty 有是在哪來的呢屏歹?它是在 Objec.prototype 中定義的。

6.繼承-圖2.png

對象的原型鏈是沿著 __proto__ 這條線走的之碗,因此在查找 f1.hasOwnProperty 屬性時蝙眶,就會順著原型鏈一直查找到 Object.prototype。

每個函數(shù)都有 call, bind 方法褪那,都有 length, arguments, caller 等屬性幽纷。這也是“繼承”的。函數(shù)是由 Function 函數(shù)創(chuàng)建博敬,__proto__ 屬性指向 Function.prototype友浸,因此繼承 Function.prototype 中的方法。

7. 原型靈活性

原文鏈接:http://www.cnblogs.com/wangfupeng1988/p/3980065.html

偶不想表偏窝。

8. 簡述【執(zhí)行上下文】上

原文鏈接:http://www.cnblogs.com/wangfupeng1988/p/3986420.html

本文要點8

理解全局執(zhí)行上下文環(huán)境收恢。

全局執(zhí)行上下文環(huán)境

在 javascript 代碼執(zhí)行之前,瀏覽器會做一些“準備工作”祭往,其中包括對變量的聲明伦意,而不是賦值。變量賦值是在賦值語句執(zhí)行的時候進行的硼补。

  • 變量驮肉、函數(shù)表達式——變量聲明,默認賦值為undefined括勺;
  • this——賦值缆八;
  • 函數(shù)聲明——賦值;

這三種數(shù)據(jù)的準備情況我們稱之為“執(zhí)行上下文”或者“執(zhí)行上下文環(huán)境”疾捍。

其實,javascript 在執(zhí)行一個“代碼段”之前栏妖,都會進行這些“準備工作”來生成執(zhí)行上下文乱豆。這個“代碼段”分為三種情況——全局代碼、函數(shù)體吊趾、eval代碼宛裕。

為什么“代碼段”分為這三種?

代碼段就是一段文本形式的代碼论泛。首先揩尸,全局代碼是一種,本來就是手寫文本到 <script> 標簽里面的屁奏。

<script>
    // 代碼段
</script>

其次岩榆,eval 代碼接受的也是一段文本形式的代碼。

eval('alert(123)')

最后,函數(shù)體是因為函數(shù)在創(chuàng)建時勇边,本質上是 new Function(...) 得到的犹撒,其中需要傳入一個文本形式的參數(shù)作為函數(shù)體。

function fn(x) {
    console.log(x + 5);
}

var fn = new Function('x', 'console.log(x + 5)');

9. 簡述【執(zhí)行上下文】下

原文鏈接:http://www.cnblogs.com/wangfupeng1988/p/3987563.html

本文要點9

  1. 理解執(zhí)行上下文環(huán)境
  2. 知道什么是自由變量
  3. 上下文的準備工作及區(qū)別(全局與函數(shù))

1. 函數(shù)體執(zhí)行上下文環(huán)境

function fn(x) {
    console.log(arguments);  // [10]
    console.log(x);  // 10
}
fn(10);

在函數(shù)體的語句執(zhí)行之前粒褒,arguments 變量和函數(shù)參數(shù)都已經被賦值识颊。函數(shù)每調用一次都會產生一個新的執(zhí)行上下文環(huán)境,因為不同的調用可能會有不同的參數(shù)奕坟。

2. 自由變量

自由變量:當前作用域內使用了外部作用域的變量(使用了不是在當前作用域內定義的變量)祥款。

函數(shù)在定義的時候(不是調用的時候),就已經確定了函數(shù)體內部自由變量的作用域月杉。

var a = 10;
function fn() {
    console.log(a); // a 是自由變量镰踏,函數(shù)創(chuàng)建時,就確定了 a 要取值的作用域
}

function bar(f) {
    var a = 20;
    f(); // 打印 10, 而不是 20
}
bar(fn);

3. 上下文環(huán)境

全局代碼的上下文環(huán)境數(shù)據(jù)內容為:

準備內容 初始化
普通變量(包括函數(shù)表達式)沙合,如: var a = 10; 聲明(默認賦值為 undefined)
函數(shù)聲明奠伪, 如: function fn() { } 賦值
this 賦值

如果代碼段是函數(shù)體,那么需要在此(全局準備內容)基礎上附加:

準備內容 初始化
參數(shù) 賦值
arguments 賦值
自由變量的取值作用域 賦值

通俗執(zhí)行上下文環(huán)境定義:在執(zhí)行代碼之前首懈,把將要用到的所有的變量都事先拿出來绊率,有的直接賦值了,有的先用 undefined 占個空究履。

10. this

原文鏈接:http://www.cnblogs.com/wangfupeng1988/p/3988422.html

新增參考:http://mp.weixin.qq.com/s/xMLZLQzb2CBvkB7HkiNzSA

本文要點10

  1. 掌握 this 的幾種指向問題滤否。
  2. 函數(shù)中 this 取值是在函數(shù)被調用的時候確定的,而不是在定義時最仑。

1. 構造函數(shù)

所謂構造函數(shù)就是用來 new 對象的函數(shù)藐俺。嚴格來說,所有函數(shù)都可以 new 一個對象泥彤,但是有些函數(shù)的定義不是為了作為構造函數(shù)欲芹。另外注意,構造函數(shù)的函數(shù)名第一個字母大寫(規(guī)則約定)吟吝。例如:Object, Array, Function 等菱父。

function Foo() {
    this.name = 'tanya';
    this.year = 1975;

    console.log(this);  // Foo { name: 'tanya', year: 1975 }
}

var f1 = new Foo();
console.log(f1.name);  // tanya
console.log(f1.year);  // 1975

以上代碼中,如果函數(shù)作為構造函數(shù)用剑逃,那么其中 this 就代表它即將 new 出來的對象浙宜, 這里 this 表示 f1。

2. 函數(shù)作為對象的一個屬性

如果函數(shù)作為對象的一個屬性時蛹磺,并且作為對象的一個屬性被調用時粟瞬,函數(shù)中的 this 指向該對象。

var obj = {
    x: 10,
    fn: function() {
        console.log(this);    //  Object { x: 10, fn: function }
        console.log(this.x);  // 10
    }
}

obj.fn();

如果 fn 函數(shù)不是作為 obj 的一個屬性被調用萤捆,會是什么結果裙品?

var obj = {
    x: 10,
    fn: function() {
        console.log(this);    //  Window {top: Window, ...}
        console.log(this.x);  // undefined
    }
}

var fn1 = obj.fn;
fn1();

如上代碼俗批,如果 fn 函數(shù)被賦值到了另一個變量中,并沒有作為 obj 的一個屬性被調用清酥,那么 this 的值就是 window扶镀,this.x 為 undefined。

3. 函數(shù)用 call 或者 apply 調用

當一個函數(shù)被 call 或 apply 調用時焰轻,this 的值就取傳入的對象的值臭觉。

var obj = {
    x: 10
};

var fn = function() {
    console.log(this);  // Object {x: 10}
    console.log(this.x);
}

fn.call(obj);

4. 全局 & 普通函數(shù)調用(直接調用)

在全局環(huán)境下,this 永遠是 window:

console.log(this === window);  // true

普通函數(shù)在調用時辱志,其中 this 也是 window:

var x = 10;

var fn = function() {
    console.log(this);    // Window {top: Window ...}
    console.log(this.x);  // 10
}

fn();

注意下面的情況:

var obj = {
    x: 10,
    fn: function() {
        function f() {
            console.log(this);    // Window {top: Window ...}
            console.log(this.x);  // 10
        }
        f();
    }
};

obj.fn();

雖然函數(shù) f 是在 obj.fn 內部定義的蝠筑,但它仍然是一個普通函數(shù),this 指向 window揩懒。

5. bind() 對直接調用的影響(新增)

Function.prototype.bind() 的作用是將當前函數(shù)與指定對象綁定什乙,并返回一個新函數(shù),這個函數(shù)無論以什么樣的方式調用已球,其 this 始終指向綁定的對象臣镣。

var obj = {};
function test() {
    console.log(this === obj);
}

var testObj = test.bind(obj);
test(); // false
testObj(); // true

6. 箭頭函數(shù)中的 this(新增)

箭頭函數(shù)沒有自己的 this 綁定。箭頭函數(shù)中使用的 this智亮,指的是直接包含它的那個函數(shù)或函數(shù)表達式中的 this忆某。

var obj = {
    test: function () {
        var arrow = () => {
            console.log(this === obj);
        }

        arrow();
    }
}

obj.test(); // true
// 這里 arrow 函數(shù)中的 this 指的是 test 函數(shù)中的 this,
// 而 test 函數(shù)中的 this 指的是 obj

另外需要注意的是阔蛉,箭頭函數(shù)不能用 new 調用弃舒,不能 bind() 到某個對象(雖然 bind() 方法調用沒問題,但是不會產生預期效果)状原。不管在什么情況下使用箭頭函數(shù)聋呢,它本身是沒有綁定 this 的,它用的是直接外層函數(shù)(即包含它的最近的一層函數(shù)或函數(shù)表達式)綁定的 this颠区。

11. 執(zhí)行上下文棧

原文鏈接:http://www.cnblogs.com/wangfupeng1988/p/3989357.html

本文要點11

  1. 理解執(zhí)行上下文棧概念
  2. 了解壓棧削锰、出棧過程。

1. 執(zhí)行上下文棧概念

執(zhí)行全局代碼時瓦呼,會產生一個執(zhí)行上下文環(huán)境喂窟,每次調用函數(shù)都又會產生執(zhí)行上下文環(huán)境。當函數(shù)調用完成時央串,這個上下文環(huán)境以及其中的數(shù)據(jù)都會被清除,再重新回到全局上下文環(huán)境碗啄。處于活動狀態(tài)的執(zhí)行上下文環(huán)境只有一個质和。

可以把這看成是一個壓棧出棧的過程,俗稱執(zhí)行上下文棧稚字。

藍色背景表示活動狀態(tài)

白色背景表示非活動狀態(tài)

11.執(zhí)行上下文棧-圖1.png

2. 壓棧饲宿、出棧過程

var a = 10,                         // 1厦酬、進入全局上下文環(huán)境
    fn,
    bar = function(x) {
        var b = 5;
        fn(x+b);// 用 B 表示        // 3、進入 fn 函數(shù)上下文環(huán)境
    }

fn = function(y) {
    var c = 5;
    console.log(y + c);
}
// 用 A 表示
bar(10);                           // 2瘫想、進入 bar 函數(shù)上下文環(huán)境

第一步:在代碼執(zhí)行之前仗阅,首先創(chuàng)建全局上下文環(huán)境:

全局上下文環(huán)境(全局代碼執(zhí)行前)

變量 賦值
a undefined
fn undefined
bar undefined
this window

然后是代碼執(zhí)行,執(zhí)行到 A 之前国夜,全局上下文環(huán)境中的變量都在執(zhí)行過程中被賦值:

全局上下文環(huán)境變?yōu)?/p>

變量 賦值
a 10
fn function
bar function
this window

第二步:執(zhí)行到 A 之后减噪,調用 bar 函數(shù)。

跳轉到 bar 函數(shù)內部车吹,執(zhí)行函數(shù)體語句之前筹裕,會創(chuàng)建一個新的執(zhí)行上下文環(huán)境:

bar 函數(shù)執(zhí)行上下文環(huán)境

變量 賦值
b undefined
x 10
arguments [10]]
this window

并將這個執(zhí)行上下文環(huán)境壓棧,設置為活動狀態(tài):

11.執(zhí)行上下文棧-圖2.png

第三步:執(zhí)行到 B窄驹,又調用了 fn 函數(shù)朝卒,在執(zhí)行函數(shù)體語句之前,會創(chuàng)建 fn 函數(shù)的執(zhí)行上下文環(huán)境乐埠,并壓棧抗斤,設置為活動狀態(tài):

11.執(zhí)行上下文棧-圖3.png

第四步:待 B 執(zhí)行完畢,即 fn 函數(shù)執(zhí)行完畢后丈咐,此次調用 fn 所生成的上下文環(huán)境出棧瑞眼,并且被銷毀(已經用完了,就要及時銷毀扯罐,釋放內存)负拟。

11.執(zhí)行上下文棧-圖4.png

第五步:同理,待 A 執(zhí)行完畢歹河,即 bar 函數(shù)執(zhí)行完畢后掩浙,調用 bar 函數(shù)所生成的上下文環(huán)境出棧,并且被銷毀(已經用完了秸歧,就要及時銷毀厨姚,釋放內存)。

11.執(zhí)行上下文棧-圖5.png

12. 簡介【作用域】

原文鏈接:http://www.cnblogs.com/wangfupeng1988/p/3991151.html

參考:https://github.com/mqyqingfeng/Blog/issues/3

本文要點12

理解作用域键菱。

作用域

作用域是個很抽象的概念谬墙,類似于一個“地盤”。

12.作用域-圖1.png

上圖中经备,全局代碼和 fn 拭抬、bar 兩個函數(shù)都會形成一個作用域。

在作用域中存在上下級關系侵蒙,上下級關系的確定就看函數(shù)是在哪個作用域下創(chuàng)建的造虎。例如:fn 作用域下創(chuàng)建了 bar 函數(shù),那么 “fn 作用域” 就是 “bar 作用域” 的上級纷闺。

作用域最大的用處就是隔離變量算凿,不同作用域下同名變量不會有沖突份蝴。例如以上代碼中,三個作用域下都聲明了“a” 這個變量氓轰,但是他們不會有沖突婚夫。各自作用域下,用各自的“a”署鸡。

13. 【作用域】和【上下文環(huán)境】

原文鏈接:http://www.cnblogs.com/wangfupeng1988/p/3991995.html

本文要點13

理解作用域和上下文環(huán)境案糙。

作用域結合上下文環(huán)境

13.作用域和上下文環(huán)境-圖1.png

除了全局作用域外,每個函數(shù)都會創(chuàng)建自己的作用域储玫,作用域在函數(shù)定義時就已經確定了侍筛,而不是在函數(shù)調用時確定的。

下面按照程序執(zhí)行的順序撒穷,一步一步加上上下文環(huán)境:

第一步匣椰,在加載程序時,就已經確定了全局上下文環(huán)境端礼,并隨著程序的執(zhí)行而對變量賦值:

13.作用域和上下文環(huán)境-圖2.png

第二步禽笑,程序執(zhí)行到 A ,調用 fn(10) 蛤奥,此時生成此次調用 fn 函數(shù)時的上下文環(huán)境佳镜,壓棧,并將此上下文環(huán)境設置為活動狀態(tài)凡桥。

13.作用域和上下文環(huán)境-圖3.png

第三步蟀伸,執(zhí)行到 B 時,調用 bar(100) 缅刽,生成此次調用的上下文環(huán)境啊掏,壓棧,并設置為活動狀態(tài)衰猛。

13.作用域和上下文環(huán)境-圖4.png

第四步迟蜜,執(zhí)行完 B ,bar(100) 調用完成啡省。則 bar(100) 上下文環(huán)境被銷毀娜睛。接著執(zhí)行 C,調用 bar(200)卦睹,則又生成 bar(200 )的上下文環(huán)境畦戒,壓棧,設置為活動狀態(tài)结序。

13.作用域和上下文環(huán)境-圖5.png

第五步兢交,執(zhí)行完 C ,則 bar(200) 調用結束笼痹,其上下文環(huán)境被銷毀配喳。此時會回到 fn(10) 上下文環(huán)境,變?yōu)榛顒訝顟B(tài)凳干。

13.作用域和上下文環(huán)境-圖6.png

第六步晴裹,執(zhí)行完 A,fn(10) 執(zhí)行完成之后救赐,fn(10) 上下文環(huán)境被銷毀涧团,全局上下文環(huán)境又回到活動狀態(tài)。

13.作用域和上下文環(huán)境-圖7.png

最后把以上過程連起來看看:

13.作用域和上下文環(huán)境-圖8.png

作用域只是一個“地盤”经磅,一個抽象的概念泌绣,其中沒有變量,要通過作用域對應的執(zhí)行上下文環(huán)境來獲取變量的值预厌。同一個作用域下阿迈,不同的調用會產生不同的執(zhí)行上下文環(huán)境,繼而產生不同變量的值轧叽。所以苗沧,作用域中變量的值是在執(zhí)行過程中產生的確定的,而作用域卻是在函數(shù)創(chuàng)建時就確定了炭晒。

所以待逞,如果要找一個作用域下某個變量的值,就需要找到這個作用域對應的執(zhí)行上下文環(huán)境网严,再在其中尋找變量的值识樱。

14. 從【自由變量】到【作用域鏈】

原文鏈接:http://www.cnblogs.com/wangfupeng1988/p/3992795.html

本文要點14

理解作用域鏈。

1. 自由變量

什么是自由變量震束?

在 A 作用域中使用變量 x怜庸,卻沒有在 A 作用域中聲明(在其他作用域中聲明的),對于 A 作用域來說驴一,x就是一個自由變量休雌。

如下:

var x = 10;

function fn() {
    var b = 20;
    console.log(x + b); // x 在這里就是一個自由變量
}

那么,在 fn 函數(shù)中肝断,取自由變量 x 的值時杈曲,要到哪個作用域中去取胸懈?——要到創(chuàng)建 fn 函數(shù)的那個作用域中取担扑。無論 fn 函數(shù)在哪里調用。

2. 作用域鏈

作用域鏈:在當前作用域中進行查找趣钱,如果沒有涌献,就在創(chuàng)建函數(shù)作用域中查找自由變量翅敌,如果沒有睦授,就去創(chuàng)建該作用域的函數(shù)所在作用域查找,直到全局作用域為止。這個在作用域中查找的路線喘批,稱之為作用域鏈缠导。

我們拿文字總結一下取自由變量時的這個“作用域鏈”過程:(假設a是自由量)

第一步辩块,現(xiàn)在當前作用域查找a靴寂,如果有則獲取并結束。如果沒有則繼續(xù)轴捎;

第二步鹤盒,如果當前作用域是全局作用域,則證明a未定義侦副,結束侦锯;否則繼續(xù);

第三步秦驯,(不是全局作用域尺碰,那就是函數(shù)作用域)將創(chuàng)建該函數(shù)的作用域作為當前作用域;

第四步汇竭,跳轉到第一步葱蝗。

示例代碼:

14.作用域和自由變量-圖2.png

以上代碼中:fn() 返回的是 bar 函數(shù) ,賦值給 x 细燎。執(zhí)行 x()两曼,即執(zhí)行 bar 函數(shù)代碼。取 b 的值時玻驻,直接在 fn 作用域取出悼凑。取 a 的值時,試圖在 fn 作用域取璧瞬,但是取不到户辫,只能轉向創(chuàng)建 fn 的那個作用域中去查找,結果找到了嗤锉。

15. 閉包

原文鏈接:http://www.cnblogs.com/wangfupeng1988/p/3992795.html

參考:https://stackoverflow.com/questions/111102/how-do-javascript-closures-work

本文要點15

  1. 知道閉包產生的條件及常見用法
  2. 理解閉包是什么

1. 閉包產生的條件

  1. 函數(shù)嵌套
  2. 內部函數(shù)引用了外部函數(shù)的數(shù)據(jù)(可以是變量或者函數(shù))

換句話說:簡單地訪問函數(shù)的詞法作用域(靜態(tài)作用域)以外的自由變量會創(chuàng)建一個閉包渔欢。

function fn () { // 外部函數(shù)
    var max = 10;

    function bar (x) { // 內部函數(shù)
        if (x > max) { // 引用了外部函數(shù)變量 max
            console.log(x)
        }
    }
}

fn()
15.閉包-圖1.png

說明:紅色框中,收起部分為 object: { max: undefined }

那么瘟忱,閉包到底是什么奥额?

閉包是包含被內部函數(shù)引用的在外部函數(shù)中定義的數(shù)據(jù)的對象(可以是變量或者函數(shù))。簡單點說是:包含被引用變量(函數(shù))的對象

2. 常見的閉包

  1. 將函數(shù)作為另一個函數(shù)的返回值(函數(shù)不必為了被稱為閉包而返回访诱,看看閉包產生的條件)
  2. 將函數(shù)作為實參傳遞給另一個函數(shù)調用

將函數(shù)作為另一個函數(shù)的返回值

function fn () {
    var max = 10;

    function bar () {
        return max++;
    }

    return bar; // 作為另一個函數(shù)的返回值
}

var bar = fn();
console.log(bar()); // 10
console.log(bar()); // 11
console.log(bar()); // 12

從中我們可以看出閉包的作用:

  1. 使函數(shù)內部的變量 max 在函數(shù) fn 執(zhí)行完后垫挨,讓然存活在內存中(其他變量已經釋放)
  2. 讓函數(shù) fn 外部可以讀取到函數(shù)內部的數(shù)據(jù) max(變量或者函數(shù))

也可以看出閉包的缺點:

內存泄漏(Memory Leak)是指程序中己動態(tài)分配的堆內存由于某種原因程序未釋放或無法釋放,造成系統(tǒng)內存的浪費触菜,導致程序運行速度減慢甚至系統(tǒng)崩潰等嚴重后果九榔。
——百度百科

  1. 函數(shù) fn 執(zhí)行完之后,被函數(shù) bar 引用的變量沒有釋放,占用內存時間會變長
  2. 可能造成內存泄漏(memory leaks)

那對應的解決方案是:

只需要在不使用函數(shù)時哲泊,置空就行了 bar = null;

將函數(shù)作為實參傳遞給另一個函數(shù)調用

function fn () {
    var max = 1;
    var interval;
    interval = setInterval(function () { // 將匿名函數(shù)作為 setInterval 函數(shù)的參數(shù)調用
        if (max > 100) clearInterval(interval)
        console.log(max++);
    }, 1000)
}

fn();
15.閉包-圖2.png

注意圖中 Closure (fn)剩蟀,fn 指的是查找引用變量時的作用域。

3. 閉包作用域

代碼一

function fn () { // Closure (fn) { x: 1 }
    var x = 1;
    function foo () { // Closure (foo) { y: 2 }
        var y = 2;
        console.log(x + y)
        return function bar () {
            var z = 3;
            console.log(x + y + z)
        }
    }
}

var foo = fn();
var bar = foo();
bar();
15.閉包-圖3.png

從上圖中可以看到攻旦,閉包所在作用域平行于引用變量(x, y)所在的作用域喻旷。

代碼二(繼續(xù)說明閉包所在作用域)

function fn () {
    var x = 1;
    function foo () {
        console.log(x++);
    }

    function bar () {
        console.log(x++);
    }

    return {
        foo: foo,
        bar: bar
    }
}

var o = fn();
var foo = o.foo;
var bar = o.bar;

foo(); // x: 1
bar(); // x: 2
15.閉包-圖4.png

從代碼輸出可以看到,閉包并不是屬于某一個內部函數(shù)牢屋,也恰好印證了上面說的。

16. 總結

文章說明

感謝王福朋

內容絕大部分來自王福朋 - 博客園 —— 《 深入理解javascript原型和閉包
》槽袄,我只是重畫了大部分的圖和對少量內容進行了補充(比如:this 章節(jié)的“新增”烙无、閉包部分)

文章初衷

希望在一篇文章中進行概括說明這些內容(并不是說分開不好),只是這樣讀起來更加順暢遍尺,找起來比較方便(離線也可以哦截酷,已上傳到 Github上,歡迎 star乾戏、fork)迂苛;

自己也可以針對相關內容進行擴充(不用看到一些額外的相關知識就收藏一個網(wǎng)址_,你也可以把這篇文章轉到你的賬號下鼓择,進行擴充三幻,說明來源即可);

文章反饋

如有錯誤呐能,歡迎指出念搬;

如有疑問,歡迎討論摆出;

文章后續(xù)

如果看到相關的新的知識朗徊,會添加到對應主題下面(以“新增”標明);

如果你看到這里沒有提到的相關內容偎漫,也可以給我鏈接爷恳,我會進行補充,并貼上你的大名象踊;

撒花温亲,待續(xù),期待你們的加入……

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末通危,一起剝皮案震驚了整個濱河市铸豁,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌菊碟,老刑警劉巖节芥,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡头镊,警方通過查閱死者的電腦和手機蚣驼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來相艇,“玉大人颖杏,你說我怎么就攤上這事√逞浚” “怎么了留储?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長咙轩。 經常有香客問我获讳,道長,這世上最難降的妖魔是什么活喊? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任丐膝,我火速辦了婚禮,結果婚禮上钾菊,老公的妹妹穿的比我還像新娘帅矗。我一直安慰自己,他們只是感情好煞烫,可當我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布浑此。 她就那樣靜靜地躺著,像睡著了一般红竭。 火紅的嫁衣襯著肌膚如雪尤勋。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天茵宪,我揣著相機與錄音最冰,去河邊找鬼。 笑死稀火,一個胖子當著我的面吹牛暖哨,可吹牛的內容都是我干的。 我是一名探鬼主播凰狞,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼篇裁,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了赡若?” 一聲冷哼從身側響起达布,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎逾冬,沒想到半個月后黍聂,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體躺苦,經...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年产还,在試婚紗的時候發(fā)現(xiàn)自己被綠了匹厘。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡脐区,死狀恐怖愈诚,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情牛隅,我是刑警寧澤炕柔,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站倔叼,受9級特大地震影響汗唱,放射性物質發(fā)生泄漏。R本人自食惡果不足惜丈攒,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望授霸。 院中可真熱鬧巡验,春花似錦、人聲如沸碘耳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽辛辨。三九已至捕捂,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間斗搞,已是汗流浹背指攒。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留僻焚,地道東北人允悦。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像虑啤,于是被迫代替她去往敵國和親隙弛。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,592評論 2 353