王福朋 - 博客園 —— 《 深入理解javascript原型和閉包》
1. 一切都是對象
本文要點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ù)和對象的關系
本文要點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 原型
什么是原型?
函數(shù)都有一個 prototype 屬性惰拱,它是一個對象雌贱,
函數(shù)的實例對象的 __proto__
屬性指向該函數(shù)的 prototype 屬性,
那我們稱該對象為函數(shù)實例對象的原型
本文要點3
理解原型概念弓颈。
1. prototype 和 constructor 屬性
函數(shù)有一個默認屬性 prototype
帽芽,屬性值是一個對象删掀,該對象默認只有一個 constructor
屬性翔冀,指向這個函數(shù)本身。
如上圖:SuperType 是一個函數(shù)披泪,右側的方框就是它的原型纤子。
Object 原型里面有幾個其他屬性:
2. 在 prototype 上添加屬性
function Fn() {}
Fn.prototype.name = 'tanya'
Fn.prototype.getYear = function () {
return 1975;
};
4. 隱式原型
每個函數(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幔妨。
2. Object.prototype
的 __proto__
自定義函數(shù)的 prototype:本質和 var obj = {}
是一樣的,都是被 Object
創(chuàng)建的谍椅,所以它的 __proto__
指向的就是 Object.prototype
误堡。
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)的原型
上圖中栅螟,自定義函數(shù)的 Foo.__proto__
指向 Function.prototype
,Object.__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
本文要點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__;
}
}
根據(jù)上圖及 instanceof 判定規(guī)則理解:
console.log(Object instanceof Function); // true
console.log(Function instanceof Object); // true
console.log(Function instanceof Function); // true
6. “繼承”
本文要點6
- 理解原型鏈概念
- 知道屬性查找方式
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
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 中定義的。
對象的原型鏈是沿著 __proto__
這條線走的之碗,因此在查找 f1.hasOwnProperty
屬性時蝙眶,就會順著原型鏈一直查找到 Object.prototype。
每個函數(shù)都有 call, bind 方法褪那,都有 length, arguments, caller 等屬性幽纷。這也是“繼承”的。函數(shù)是由 Function 函數(shù)創(chuàng)建博敬,__proto__
屬性指向 Function.prototype友浸,因此繼承 Function.prototype 中的方法。
7. 原型靈活性
偶不想表偏窝。
8. 簡述【執(zhí)行上下文】上
本文要點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í)行上下文】下
本文要點9
- 理解執(zhí)行上下文環(huán)境
- 知道什么是自由變量
- 上下文的準備工作及區(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
本文要點10
- 掌握 this 的幾種指向問題滤否。
- 函數(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í)行上下文棧
本文要點11
- 理解執(zhí)行上下文棧概念
- 了解壓棧削锰、出棧過程。
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)
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):
第三步:執(zhí)行到 B窄驹,又調用了 fn 函數(shù)朝卒,在執(zhí)行函數(shù)體語句之前,會創(chuàng)建 fn 函數(shù)的執(zhí)行上下文環(huán)境乐埠,并壓棧抗斤,設置為活動狀態(tài):
第四步:待 B 執(zhí)行完畢,即 fn 函數(shù)執(zhí)行完畢后丈咐,此次調用 fn 所生成的上下文環(huán)境出棧瑞眼,并且被銷毀(已經用完了,就要及時銷毀扯罐,釋放內存)负拟。
第五步:同理,待 A 執(zhí)行完畢歹河,即 bar 函數(shù)執(zhí)行完畢后掩浙,調用 bar 函數(shù)所生成的上下文環(huán)境出棧,并且被銷毀(已經用完了秸歧,就要及時銷毀厨姚,釋放內存)。
12. 簡介【作用域】
本文要點12
理解作用域键菱。
作用域
作用域是個很抽象的概念谬墙,類似于一個“地盤”。
上圖中经备,全局代碼和 fn 拭抬、bar 兩個函數(shù)都會形成一個作用域。
在作用域中存在上下級關系侵蒙,上下級關系的確定就看函數(shù)是在哪個作用域下創(chuàng)建的造虎。例如:fn 作用域下創(chuàng)建了 bar 函數(shù),那么 “fn 作用域” 就是 “bar 作用域” 的上級纷闺。
作用域最大的用處就是隔離變量算凿,不同作用域下同名變量不會有沖突份蝴。例如以上代碼中,三個作用域下都聲明了“a” 這個變量氓轰,但是他們不會有沖突婚夫。各自作用域下,用各自的“a”署鸡。
13. 【作用域】和【上下文環(huán)境】
本文要點13
理解作用域和上下文環(huán)境案糙。
作用域結合上下文環(huán)境
除了全局作用域外,每個函數(shù)都會創(chuàng)建自己的作用域储玫,作用域在函數(shù)定義時就已經確定了侍筛,而不是在函數(shù)調用時確定的。
下面按照程序執(zhí)行的順序撒穷,一步一步加上上下文環(huán)境:
第一步匣椰,在加載程序時,就已經確定了全局上下文環(huán)境端礼,并隨著程序的執(zhí)行而對變量賦值:
第二步禽笑,程序執(zhí)行到 A ,調用 fn(10) 蛤奥,此時生成此次調用 fn 函數(shù)時的上下文環(huán)境佳镜,壓棧,并將此上下文環(huán)境設置為活動狀態(tài)凡桥。
第三步蟀伸,執(zhí)行到 B 時,調用 bar(100) 缅刽,生成此次調用的上下文環(huán)境啊掏,壓棧,并設置為活動狀態(tài)衰猛。
第四步迟蜜,執(zhí)行完 B ,bar(100) 調用完成啡省。則 bar(100) 上下文環(huán)境被銷毀娜睛。接著執(zhí)行 C,調用 bar(200)卦睹,則又生成 bar(200 )的上下文環(huán)境畦戒,壓棧,設置為活動狀態(tài)结序。
第五步兢交,執(zhí)行完 C ,則 bar(200) 調用結束笼痹,其上下文環(huán)境被銷毀配喳。此時會回到 fn(10) 上下文環(huán)境,變?yōu)榛顒訝顟B(tài)凳干。
第六步晴裹,執(zhí)行完 A,fn(10) 執(zhí)行完成之后救赐,fn(10) 上下文環(huán)境被銷毀涧团,全局上下文環(huán)境又回到活動狀態(tài)。
最后把以上過程連起來看看:
作用域只是一個“地盤”经磅,一個抽象的概念泌绣,其中沒有變量,要通過作用域對應的執(zhí)行上下文環(huán)境來獲取變量的值预厌。同一個作用域下阿迈,不同的調用會產生不同的執(zhí)行上下文環(huán)境,繼而產生不同變量的值轧叽。所以苗沧,作用域中變量的值是在執(zhí)行過程中產生的確定的,而作用域卻是在函數(shù)創(chuàng)建時就確定了炭晒。
所以待逞,如果要找一個作用域下某個變量的值,就需要找到這個作用域對應的執(zhí)行上下文環(huán)境网严,再在其中尋找變量的值识樱。
14. 從【自由變量】到【作用域鏈】
本文要點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ù)的作用域作為當前作用域;
第四步汇竭,跳轉到第一步葱蝗。
示例代碼:
以上代碼中: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. 閉包產生的條件
- 函數(shù)嵌套
- 內部函數(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()
說明:紅色框中,收起部分為 object: { max: undefined }
那么瘟忱,閉包到底是什么奥额?
閉包是包含被內部函數(shù)引用的在外部函數(shù)中定義的數(shù)據(jù)的對象(可以是變量或者函數(shù))。簡單點說是:包含被引用變量(函數(shù))的對象
2. 常見的閉包
- 將函數(shù)作為另一個函數(shù)的返回值(函數(shù)不必為了被稱為閉包而返回访诱,看看閉包產生的條件)
- 將函數(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
從中我們可以看出閉包的作用:
- 使函數(shù)內部的變量
max
在函數(shù)fn
執(zhí)行完后垫挨,讓然存活在內存中(其他變量已經釋放) - 讓函數(shù)
fn
外部可以讀取到函數(shù)內部的數(shù)據(jù)max
(變量或者函數(shù))
也可以看出閉包的缺點:
內存泄漏(Memory Leak)是指程序中己動態(tài)分配的堆內存由于某種原因程序未釋放或無法釋放,造成系統(tǒng)內存的浪費触菜,導致程序運行速度減慢甚至系統(tǒng)崩潰等嚴重后果九榔。
——百度百科
- 函數(shù)
fn
執(zhí)行完之后,被函數(shù)bar
引用的變量沒有釋放,占用內存時間會變長 - 可能造成內存泄漏(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();
注意圖中 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();
從上圖中可以看到攻旦,閉包所在作用域平行于引用變量(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
從代碼輸出可以看到,閉包并不是屬于某一個內部函數(shù)牢屋,也恰好印證了上面說的。
16. 總結
文章說明
感謝王福朋
內容絕大部分來自王福朋 - 博客園 —— 《 深入理解javascript原型和閉包
》槽袄,我只是重畫了大部分的圖和對少量內容進行了補充(比如:this 章節(jié)的“新增”烙无、閉包部分)
文章初衷
希望在一篇文章中進行概括說明這些內容(并不是說分開不好),只是這樣讀起來更加順暢遍尺,找起來比較方便(離線也可以哦截酷,已上傳到 Github上,歡迎 star乾戏、fork)迂苛;
自己也可以針對相關內容進行擴充(不用看到一些額外的相關知識就收藏一個網(wǎng)址_,你也可以把這篇文章轉到你的賬號下鼓择,進行擴充三幻,說明來源即可);
文章反饋
如有錯誤呐能,歡迎指出念搬;
如有疑問,歡迎討論摆出;
文章后續(xù)
如果看到相關的新的知識朗徊,會添加到對應主題下面(以“新增”標明);
如果你看到這里沒有提到的相關內容偎漫,也可以給我鏈接爷恳,我會進行補充,并貼上你的大名象踊;
撒花温亲,待續(xù),期待你們的加入……