本文為深入理解javascript原型和閉包系列的摘要筆記
1.一切都是對(duì)象
function show(x) {
// 值類型,不是對(duì)象
console.log(typeof x); // undefined
console.log(typeof 10); // number
console.log(typeof 'abc'); // string
console.log(typeof true); // boolean
// 引用類型,是對(duì)象:函數(shù)、數(shù)組、對(duì)象、null、new Number(10)都是對(duì)象
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();
對(duì)象:若干屬性的集合。
- java或者C#中的對(duì)象都是new一個(gè)class出來的,而且里面有字段、屬性、方法,規(guī)定的非常嚴(yán)格。
- javascript中,數(shù)組是對(duì)象,函數(shù)是對(duì)象,對(duì)象還是對(duì)象。對(duì)象里面的一切都是屬性,只有屬性,沒有方法。
- javascript中,方法也是一種屬性。因?yàn)樗膶傩员硎緸殒I值對(duì)的形式。
var fn = function () {
alert(100);
};
fn.a = 10;
fn.b = function () {
alert(123);
};
fn.c = {
name: "王福朋",
year: 1988
};
2.函數(shù)和對(duì)象的關(guān)系
對(duì)象都是通過函數(shù)創(chuàng)建的
函數(shù)是對(duì)象的一種
var fn = function () { };
console.log(fn instanceof Object); // true
對(duì)象可以通過函數(shù)來創(chuàng)建
function Fn() {
this.name = '王福朋';
this.year = 1988;
}
var fn1 = new Fn();
對(duì)象都是通過函數(shù)創(chuàng)建的
//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.prototype原型
- 每個(gè)函數(shù)都有一個(gè)屬性叫做prototype;
- 這個(gè)prototype的屬性值是一個(gè)對(duì)象(屬性的集合)
- 默認(rèn)的只有一個(gè)叫做constructor的屬性,指向這個(gè)函數(shù)本身
如下圖,左側(cè)是一個(gè)函數(shù),右側(cè)的方框就是它的原型:
可以在自己自定義的方法的prototype中新增自己的屬性
function Fn() { }
Fn.prototype.name = '王福朋';
Fn.prototype.getYear = function () {
return 1988;
};
var fn = new Fn();
console.log(fn.name);
console.log(fn.getYear());
- 即,F(xiàn)n是一個(gè)函數(shù),fn對(duì)象是從Fn函數(shù)new出來的,這樣fn對(duì)象就可以調(diào)用
Fn.prototype
中的屬性。 - 每個(gè)對(duì)象都有一個(gè)隱藏的屬性:
__proto__
,這個(gè)屬性引用了創(chuàng)建這個(gè)對(duì)象的函數(shù)的prototype
。 fn.__proto__ === Fn.prototype
-
__proto__
成為“隱式原型”
4.隱式原型
每個(gè)對(duì)象都有一個(gè)proto屬性,指向創(chuàng)建該對(duì)象的函數(shù)的prototype
var obj = {}
- obj對(duì)象的隱式原型:obj這個(gè)對(duì)象本質(zhì)上是被Object函數(shù)創(chuàng)建的,因此
obj.__proto__=== Object.prototype
,如下圖:
即,每個(gè)對(duì)象都有一個(gè)__proto__屬性,指向創(chuàng)建該對(duì)象的函數(shù)的prototype
- Object prototype的隱式原型:
Object prototype
也是一個(gè)對(duì)象,它的__proto__
指向哪里?null,如下圖:
- 函數(shù)的隱式原型:函數(shù)是通過
new Functoin()
創(chuàng)建的:
5.instanceof
A instanceof B
Instanceof
的判斷規(guī)則是:沿著A的proto這條線來找,同時(shí)沿著B的prototype這條線來找(B線到此為止),如果兩條線能找到同一個(gè)引用,即同一個(gè)對(duì)象,那么就返回true。如果找到終點(diǎn)還未重合,則返回false。
function Foo() {}
var f1 = new Foo();
console.log(f1 instanceof Foo);// true
console.log(f1 instanceof Object);// true

instanceof
表示的就是一種繼承關(guān)系,或者原型鏈的結(jié)構(gòu)
6.繼承
-
定義
- javascript中的繼承是通過原型鏈來體現(xiàn)的
- 訪問一個(gè)對(duì)象的屬性時(shí),先在基本屬性中查找,如果沒有,再沿著proto這條鏈向上找,這就是原型鏈
例:
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.a);// 200
上圖中,訪問f1.b時(shí),f1的基本屬性中沒有b,于是沿著proto找到了Foo.prototype.b。
-
對(duì)象的繼承
區(qū)分一個(gè)屬性到底是基本的還是從原型中找到的:hasOwnProperty
例:
f1.hasOwnProperty(item)
hasOwnProperty
方法來自Object.prototype
,如下圖:
-
函數(shù)的繼承
- 每個(gè)函數(shù)都有
call
,apply
方法,都有length
,arguments
,caller
等屬性 - 函數(shù)由
Function
函數(shù)創(chuàng)建,因此繼承的Function.prototype
中的方法。
函數(shù)的原型鏈 -
Function.prototype
繼承自Object.prototype
的方法,因此有hasOwnProperty
方法
7.原型的靈活性
- 對(duì)象屬性可以隨時(shí)改動(dòng)
- 如果繼承的方法不合適,可以做出修改
function Foo() {}
var f1 = new Foo();
Foo.prototype.toString = function(){
return 'jyoketsu';
}
console.log(f1.toString());// jyoketsu
- 如果感覺當(dāng)前缺少你要用的方法,可以自己去創(chuàng)建
例如在json2.js源碼中,為Date、String、Number、Boolean方法添加一個(gè)toJSON的屬性:
創(chuàng)建新方法
8.簡(jiǎn)述【執(zhí)行上下文】上
- javascript在執(zhí)行一個(gè)
代碼段
之前,都會(huì)進(jìn)行準(zhǔn)備工作
來生成執(zhí)行上下文
; -
代碼段
分為三種情況:全局代碼,函數(shù)體,eval代碼; -
準(zhǔn)備工作
中完成的數(shù)據(jù)準(zhǔn)備稱之為執(zhí)行上下文
或者執(zhí)行上下文環(huán)境
- 變量、函數(shù)表達(dá)式——變量聲明,默認(rèn)賦值為undefined
- this——賦值
- 函數(shù)聲明——賦值
例:
變量:
this:
函數(shù)聲明、函數(shù)表達(dá)式:
9.簡(jiǎn)述【執(zhí)行上下文】下
- 執(zhí)行上下文環(huán)境:在執(zhí)行代碼之前,把將要用到的所有的變量都事先拿出來,有的直接賦值了,有的先用undefined占個(gè)空
- 函數(shù)每被調(diào)用一次,都會(huì)產(chǎn)生一個(gè)新的執(zhí)行上下文環(huán)境
- 函數(shù)在定義的時(shí)候(不是調(diào)用的時(shí)候),就已經(jīng)確定了函數(shù)體內(nèi)部自由變量的作用域
var a = 10;
function fn(){
console.log(a);// a是自由變量
// 函數(shù)創(chuàng)建時(shí),就確定了a要取值的作用域
}
function bar(f){
var a = 20;
f(); //打印"10"而不是"20"
}
bar(fn);
function fn(x){
console.log(arguments);// [10]
console.log(x); // 10
}
fn(10);
上下文環(huán)境的數(shù)據(jù)內(nèi)容:
內(nèi)容類型 | 準(zhǔn)備動(dòng)作 |
---|---|
全局代碼的上下文環(huán)境數(shù)據(jù)內(nèi)容為: | |
普通變量(包括函數(shù)表達(dá)式)如: var a = 10
|
聲明(默認(rèn)賦值為undefined ) |
函數(shù)聲明,如: function fn() { }
|
賦值 |
this | 賦值 |
如果代碼段是函數(shù)體,那么在此基礎(chǔ)上需要附加: | |
參數(shù) | 賦值 |
arguments | 賦值 |
自由變量的取值作用域 | 賦值 |
10.this
-
this
指向調(diào)用該函數(shù)的對(duì)象 - 被誰直接調(diào)用,這個(gè)
this
就指向誰。沒有的話就是window
- 在函數(shù)中this到底取何值,是在函數(shù)真正被調(diào)用執(zhí)行的時(shí)候確定的,函數(shù)定義的時(shí)候確定不了。(因?yàn)閠his的取值是執(zhí)行上下文環(huán)境的一部分,每次調(diào)用函數(shù),都會(huì)產(chǎn)生一個(gè)新的執(zhí)行上下文環(huán)境)
在函數(shù)中this取何值
- 情況1:函數(shù)作為構(gòu)造函數(shù)
function Foo(){
this.name = 'jyoketsu';
this.year = 1991;
console.log(this);// Foo {name:"jyoketsu",year:1991}
}
var f1 = new Foo();
console.log(f1.name);// jyoketsu
console.log(f1.year);// 1991
以上代碼中,如果函數(shù)作為構(gòu)造函數(shù)用,那么其中的this
就代表它即將new
出來的對(duì)象。
如果直接調(diào)用Foo函數(shù),this
是window
function Foo(){
this.name = 'jyoketsu';
this.year = 1991;
console.log(this);// window {...}
}
Foo();
- 情況2:函數(shù)作為對(duì)象的一個(gè)屬性
如果函數(shù)作為對(duì)象的一個(gè)屬性時(shí),并且作為對(duì)象的一個(gè)屬性被調(diào)用時(shí),函數(shù)中的this指向該對(duì)象。
var obj = {
x:10,
fn:function(){
console.log(this); // Object {x:10,fn:function}
console.log(this.x); // 10
}
};
obj.fn();
var obj = {
x:10,
fn:function(){
console.log(this); // Window {...}
console.log(this.x); // undefined
}
};
var fn1 = obj.fn;
fn1();
- 情況3:函數(shù)用call或者apply調(diào)用
當(dāng)一個(gè)函數(shù)被call和apply調(diào)用時(shí),this的值就取傳入的對(duì)象的值。
var obj = {
x:10
}
var fn = function(){
console.log(this); // Object {x:10}
console.log(this.x); // 10
}
fn.call(obj);
- 情況4:全局 & 調(diào)用普通函數(shù)
在全局環(huán)境下,this永遠(yuǎn)是window
console.log(this === window); // true
普通函數(shù)在調(diào)用時(shí),其中的this也都是window:
var x = 10;
var fn = function (){
console.log(this); // Window {...}
console.log(this.x); // 10
}
fn();
注意:
var obj = {
x:10,
fn:function(){
function f(){
console.log(this); // Window {...}
console.log(this.x); // undefined
}
f();
}
}
obj.fn();
函數(shù)f雖然是在obj.fn內(nèi)部定義的,但是它仍然是一個(gè)普通的函數(shù),this仍然指向window
11.執(zhí)行上下文棧
??執(zhí)行全局代碼時(shí),會(huì)產(chǎn)生一個(gè)執(zhí)行上下文環(huán)境,每次調(diào)用函數(shù)都又會(huì)產(chǎn)生執(zhí)行上下文環(huán)境。當(dāng)函數(shù)調(diào)用完成時(shí),這個(gè)上下文環(huán)境以及其中的數(shù)據(jù)都會(huì)被消除,再重新回到全局上下文環(huán)境。處于活動(dòng)狀態(tài)的執(zhí)行上下文環(huán)境只有一個(gè)。
??其實(shí)這是一個(gè)壓棧出棧的過程——執(zhí)行上下文棧。如下圖:
例:
12.作用域
- javascript沒有塊級(jí)作用域”。所謂“塊”,就是大括號(hào)“{}”中間的語句。例如if語句
- javascript除了全局作用域之外,只有函數(shù)可以創(chuàng)建的作用域
- 作用域在函數(shù)定義時(shí)就已經(jīng)確定了。而不是在函數(shù)調(diào)用時(shí)確定。
作用域是一個(gè)很抽象的概念,類似于一個(gè)“地盤”:
jQuery源碼的最外層是一個(gè)自動(dòng)執(zhí)行的匿名函數(shù):
13.作用域和上下文環(huán)境
- 作用域只是一個(gè)“地盤”,一個(gè)抽象的概念,其中沒有變量。
- 要通過作用域?qū)?yīng)的執(zhí)行上下文環(huán)境來獲取變量的值。
- 作用域中變量的值是在執(zhí)行過程中產(chǎn)生的確定的,而作用域卻是在函數(shù)創(chuàng)建時(shí)就確定了。
- 如果要查找一個(gè)作用域下某個(gè)變量的值,就需要找到這個(gè)作用域?qū)?yīng)的執(zhí)行上下文環(huán)境,再在其中尋找變量的值。
14.從【自由變量】到【作用域鏈】
- 自由變量:在A作用域中使用的變量x,卻沒有在A作用域中聲明(即在其他作用域中聲明的),對(duì)于A作用域來說,x就是一個(gè)自由變量。
- 自由變量的取值:
- 1.先在當(dāng)前作用域查找a,如果有則獲取并結(jié)束。如果沒有則繼續(xù);
- 2.如果當(dāng)前作用域是全局作用域,則證明a未定義,結(jié)束;否則繼續(xù);
- 3.(不是全局作用域,那就是函數(shù)作用域)將創(chuàng)建該函數(shù)的作用域作為當(dāng)前作用域;
- 4.跳轉(zhuǎn)到第一步。
這個(gè)一步一步“跨”的路線,我們稱之為——
作用域鏈
。
15.閉包
閉包應(yīng)用的兩種情況:
- 函數(shù)作為返回值
- 函數(shù)作為參數(shù)傳遞
- 函數(shù)作為返回值
function fn(){
var max = 10;
return function bar(x){
if(x > max){
console.log(x);
}
}
}
var f1 = fn();
f1(15);
如上代碼,bar函數(shù)作為返回值,賦值給f1變量。執(zhí)行f1(15)時(shí),用到了fn作用域下的max變量的值。
- 函數(shù)作為參數(shù)被傳遞
var max = 10;
var fn = function(x){
if(x > max){
console.log(x);
}
};
(function(f){
var max = 100;
f(15);
})(fn);
如上代碼中,fn函數(shù)作為一個(gè)參數(shù)被傳遞進(jìn)入另一個(gè)函數(shù),賦值給f參數(shù)。執(zhí)行f(15)時(shí),max變量的取值是10,而不是100。
- 當(dāng)一個(gè)函數(shù)被調(diào)用完成之后,其執(zhí)行上下文環(huán)境將被銷毀,其中的變量也會(huì)被同時(shí)銷毀。
- 閉包的情況下,函數(shù)調(diào)用完成之后,其執(zhí)行上下文環(huán)境不會(huì)接著被銷毀。
fn()調(diào)用完成。按理說應(yīng)該銷毀掉fn()的執(zhí)行上下文環(huán)境,但是這里不能這么做。因?yàn)閳?zhí)行fn()時(shí),返回的是一個(gè)函數(shù)。函數(shù)的特別之處在于可以創(chuàng)建一個(gè)獨(dú)立的作用域。而正巧合的是,返回的這個(gè)函數(shù)體中,還有一個(gè)自由變量max要引用fn作用域下的fn()上下文環(huán)境中的max。因此,這個(gè)max不能被銷毀,銷毀了之后bar函數(shù)中的max就找不到值了。