本文為深入理解javascript原型和閉包系列的摘要筆記
1.一切都是對象
function show(x) {
// 值類型蜀备,不是對象
console.log(typeof x); // undefined
console.log(typeof 10); // number
console.log(typeof 'abc'); // string
console.log(typeof true); // boolean
// 引用類型,是對象:函數、數組睬魂、對象、null镀赌、new Number(10)都是對象
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();
對象:若干屬性的集合氯哮。
- java或者C#中的對象都是new一個class出來的,而且里面有字段佩脊、屬性蛙粘、方法垫卤,規(guī)定的非常嚴格。
- javascript中出牧,數組是對象穴肘,函數是對象,對象還是對象舔痕。對象里面的一切都是屬性评抚,只有屬性,沒有方法伯复。
- javascript中慨代,方法也是一種屬性。因為它的屬性表示為鍵值對的形式啸如。
var fn = function () {
alert(100);
};
fn.a = 10;
fn.b = function () {
alert(123);
};
fn.c = {
name: "王福朋",
year: 1988
};
2.函數和對象的關系
對象都是通過函數創(chuàng)建的
函數是對象的一種
var fn = function () { };
console.log(fn instanceof Object); // true
對象可以通過函數來創(chuàng)建
function Fn() {
this.name = '王福朋';
this.year = 1988;
}
var fn1 = new Fn();
對象都是通過函數創(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原型
- 每個函數都有一個屬性叫做prototype侍匙;
- 這個prototype的屬性值是一個對象(屬性的集合)
- 默認的只有一個叫做constructor的屬性,指向這個函數本身
如下圖叮雳,左側是一個函數想暗,右側的方框就是它的原型:
可以在自己自定義的方法的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是一個函數帘不,fn對象是從Fn函數new出來的说莫,這樣fn對象就可以調用
Fn.prototype
中的屬性。 - 每個對象都有一個隱藏的屬性:
__proto__
寞焙,這個屬性引用了創(chuàng)建這個對象的函數的prototype
储狭。 fn.__proto__ === Fn.prototype
-
__proto__
成為“隱式原型”
4.隱式原型
每個對象都有一個proto屬性,指向創(chuàng)建該對象的函數的prototype
var obj = {}
- obj對象的隱式原型:obj這個對象本質上是被Object函數創(chuàng)建的捣郊,因此
obj.__proto__=== Object.prototype
,如下圖:
即辽狈,每個對象都有一個__proto__屬性,指向創(chuàng)建該對象的函數的prototype
- Object prototype的隱式原型:
Object prototype
也是一個對象模她,它的__proto__
指向哪里稻艰?null,如下圖:
- 函數的隱式原型:函數是通過
new Functoin()
創(chuàng)建的:
5.instanceof
A instanceof B
Instanceof
的判斷規(guī)則是:沿著A的proto這條線來找,同時沿著B的prototype這條線來找(B線到此為止)侈净,如果兩條線能找到同一個引用尊勿,即同一個對象,那么就返回true畜侦。如果找到終點還未重合元扔,則返回false。
function Foo() {}
var f1 = new Foo();
console.log(f1 instanceof Foo);// true
console.log(f1 instanceof Object);// true
instanceof
表示的就是一種繼承關系旋膳,或者原型鏈的結構
6.繼承
-
定義
- javascript中的繼承是通過原型鏈來體現(xiàn)的
- 訪問一個對象的屬性時澎语,先在基本屬性中查找,如果沒有,再沿著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時,f1的基本屬性中沒有b减俏,于是沿著proto找到了Foo.prototype.b召烂。
-
對象的繼承
區(qū)分一個屬性到底是基本的還是從原型中找到的:hasOwnProperty
例:
f1.hasOwnProperty(item)
hasOwnProperty
方法來自Object.prototype
,如下圖:
-
函數的繼承
- 每個函數都有
call
娃承,apply
方法奏夫,都有length
,arguments
历筝,caller
等屬性 - 函數由
Function
函數創(chuàng)建酗昼,因此繼承的Function.prototype
中的方法。
-
Function.prototype
繼承自Object.prototype
的方法梳猪,因此有hasOwnProperty
方法
7.原型的靈活性
- 對象屬性可以隨時改動
- 如果繼承的方法不合適麻削,可以做出修改
function Foo() {}
var f1 = new Foo();
Foo.prototype.toString = function(){
return 'jyoketsu';
}
console.log(f1.toString());// jyoketsu
- 如果感覺當前缺少你要用的方法,可以自己去創(chuàng)建
例如在json2.js源碼中舔示,為Date碟婆、String电抚、Number惕稻、Boolean方法添加一個toJSON的屬性:
8.簡述【執(zhí)行上下文】上
- javascript在執(zhí)行一個
代碼段
之前,都會進行準備工作
來生成執(zhí)行上下文
; -
代碼段
分為三種情況:全局代碼蝙叛,函數體俺祠,eval代碼; -
準備工作
中完成的數據準備稱之為執(zhí)行上下文
或者執(zhí)行上下文環(huán)境
- 變量、函數表達式——變量聲明借帘,默認賦值為undefined
- this——賦值
- 函數聲明——賦值
例:
變量:
this:
函數聲明蜘渣、函數表達式:
9.簡述【執(zhí)行上下文】下
- 執(zhí)行上下文環(huán)境:在執(zhí)行代碼之前肺然,把將要用到的所有的變量都事先拿出來蔫缸,有的直接賦值了,有的先用undefined占個空
- 函數每被調用一次际起,都會產生一個新的執(zhí)行上下文環(huán)境
- 函數在定義的時候(不是調用的時候)拾碌,就已經確定了函數體內部自由變量的作用域
var a = 10;
function fn(){
console.log(a);// a是自由變量
// 函數創(chuàng)建時,就確定了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)境的數據內容:
內容類型 | 準備動作 |
---|---|
全局代碼的上下文環(huán)境數據內容為: | |
普通變量(包括函數表達式)如: var a = 10
|
聲明(默認賦值為undefined ) |
函數聲明街望,如: function fn() { }
|
賦值 |
this | 賦值 |
如果代碼段是函數體校翔,那么在此基礎上需要附加: | |
參數 | 賦值 |
arguments | 賦值 |
自由變量的取值作用域 | 賦值 |
10.this
-
this
指向調用該函數的對象 - 被誰直接調用,這個
this
就指向誰灾前。沒有的話就是window
- 在函數中this到底取何值防症,是在函數真正被調用執(zhí)行的時候確定的,函數定義的時候確定不了。(因為this的取值是執(zhí)行上下文環(huán)境的一部分蔫敲,每次調用函數饲嗽,都會產生一個新的執(zhí)行上下文環(huán)境)
在函數中this取何值
- 情況1:函數作為構造函數
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
以上代碼中,如果函數作為構造函數用奈嘿,那么其中的this
就代表它即將new
出來的對象喝噪。
如果直接調用Foo函數,this
是window
function Foo(){
this.name = 'jyoketsu';
this.year = 1991;
console.log(this);// window {...}
}
Foo();
- 情況2:函數作為對象的一個屬性
如果函數作為對象的一個屬性時指么,并且作為對象的一個屬性被調用時酝惧,函數中的this指向該對象。
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:函數用call或者apply調用
當一個函數被call和apply調用時伯诬,this的值就取傳入的對象的值晚唇。
var obj = {
x:10
}
var fn = function(){
console.log(this); // Object {x:10}
console.log(this.x); // 10
}
fn.call(obj);
- 情況4:全局 & 調用普通函數
在全局環(huán)境下,this永遠是window
console.log(this === window); // true
普通函數在調用時盗似,其中的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();
函數f雖然是在obj.fn內部定義的哩陕,但是它仍然是一個普通的函數,this仍然指向window
11.執(zhí)行上下文棧
??執(zhí)行全局代碼時赫舒,會產生一個執(zhí)行上下文環(huán)境悍及,每次調用函數都又會產生執(zhí)行上下文環(huán)境。當函數調用完成時接癌,這個上下文環(huán)境以及其中的數據都會被消除心赶,再重新回到全局上下文環(huán)境。處于活動狀態(tài)的執(zhí)行上下文環(huán)境只有一個缺猛。
??其實這是一個壓棧出棧的過程——執(zhí)行上下文棧缨叫。如下圖:
例:
12.作用域
- javascript沒有塊級作用域”屉更。所謂“塊”脚翘,就是大括號“{}”中間的語句。例如if語句
- javascript除了全局作用域之外厕吉,只有函數可以創(chuàng)建的作用域
- 作用域在函數定義時就已經確定了有咨。而不是在函數調用時確定琐簇。
作用域是一個很抽象的概念,類似于一個“地盤”:
jQuery源碼的最外層是一個自動執(zhí)行的匿名函數:
13.作用域和上下文環(huán)境
- 作用域只是一個“地盤”座享,一個抽象的概念婉商,其中沒有變量。
- 要通過作用域對應的執(zhí)行上下文環(huán)境來獲取變量的值征讲。
- 作用域中變量的值是在執(zhí)行過程中產生的確定的据某,而作用域卻是在函數創(chuàng)建時就確定了。
- 如果要查找一個作用域下某個變量的值诗箍,就需要找到這個作用域對應的執(zhí)行上下文環(huán)境癣籽,再在其中尋找變量的值挽唉。
14.從【自由變量】到【作用域鏈】
- 自由變量:在A作用域中使用的變量x,卻沒有在A作用域中聲明(即在其他作用域中聲明的)筷狼,對于A作用域來說瓶籽,x就是一個自由變量。
- 自由變量的取值:
- 1.先在當前作用域查找a埂材,如果有則獲取并結束塑顺。如果沒有則繼續(xù);
- 2.如果當前作用域是全局作用域俏险,則證明a未定義严拒,結束;否則繼續(xù)竖独;
- 3.(不是全局作用域裤唠,那就是函數作用域)將創(chuàng)建該函數的作用域作為當前作用域;
- 4.跳轉到第一步莹痢。
這個一步一步“跨”的路線种蘸,我們稱之為——
作用域鏈
。
15.閉包
閉包應用的兩種情況:
- 函數作為返回值
- 函數作為參數傳遞
- 函數作為返回值
function fn(){
var max = 10;
return function bar(x){
if(x > max){
console.log(x);
}
}
}
var f1 = fn();
f1(15);
如上代碼竞膳,bar函數作為返回值航瞭,賦值給f1變量。執(zhí)行f1(15)時坦辟,用到了fn作用域下的max變量的值刊侯。
- 函數作為參數被傳遞
var max = 10;
var fn = function(x){
if(x > max){
console.log(x);
}
};
(function(f){
var max = 100;
f(15);
})(fn);
如上代碼中,fn函數作為一個參數被傳遞進入另一個函數长窄,賦值給f參數滔吠。執(zhí)行f(15)時,max變量的取值是10挠日,而不是100。
- 當一個函數被調用完成之后翰舌,其執(zhí)行上下文環(huán)境將被銷毀嚣潜,其中的變量也會被同時銷毀。
- 閉包的情況下椅贱,函數調用完成之后懂算,其執(zhí)行上下文環(huán)境不會接著被銷毀。
fn()調用完成庇麦。按理說應該銷毀掉fn()的執(zhí)行上下文環(huán)境计技,但是這里不能這么做。因為執(zhí)行fn()時山橄,返回的是一個函數垮媒。函數的特別之處在于可以創(chuàng)建一個獨立的作用域。而正巧合的是,返回的這個函數體中睡雇,還有一個自由變量max要引用fn作用域下的fn()上下文環(huán)境中的max萌衬。因此,這個max不能被銷毀它抱,銷毀了之后bar函數中的max就找不到值了秕豫。