深入理解JavaScript原型與閉包-學習筆記

本文為深入理解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的屬性,指向這個函數本身

如下圖叮雳,左側是一個函數想暗,右側的方框就是它的原型:


SuperType()的prototype

Object()的prototype

可以在自己自定義的方法的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,如下圖:
Object.prototype的隱式原型
  • 函數的隱式原型:函數是通過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

Ojbect Layout
Ojbect Layout

instanceof表示的就是一種繼承關系旋膳,或者原型鏈的結構


6.繼承

  1. 定義

  • 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召烂。

  1. 對象的繼承

區(qū)分一個屬性到底是基本的還是從原型中找到的:hasOwnProperty
例:

f1.hasOwnProperty(item)

hasOwnProperty方法來自Object.prototype,如下圖:

對象的原型鏈

  1. 函數的繼承

  • 每個函數都有call娃承,apply方法奏夫,都有lengtharguments历筝,caller等屬性
  • 函數由Function函數創(chuàng)建酗昼,因此繼承的Function.prototype中的方法。
    函數的原型鏈
  • Function.prototype繼承自Object.prototype的方法梳猪,因此有hasOwnProperty方法

7.原型的靈活性

  1. 對象屬性可以隨時改動
  2. 如果繼承的方法不合適麻削,可以做出修改
function Foo() {}
var f1 = new Foo();

Foo.prototype.toString = function(){
    return 'jyoketsu';
}
console.log(f1.toString());// jyoketsu
  1. 如果感覺當前缺少你要用的方法,可以自己去創(chuàng)建
    例如在json2.js源碼中舔示,為Date碟婆、String电抚、Number惕稻、Boolean方法添加一個toJSON的屬性:
    創(chuàng)建新方法

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

  • javascript在執(zhí)行一個代碼段之前,都會進行準備工作來生成執(zhí)行上下文;
  • 代碼段分為三種情況:全局代碼蝙叛,函數體俺祠,eval代碼;
  • 準備工作中完成的數據準備稱之為執(zhí)行上下文或者執(zhí)行上下文環(huán)境
    • 變量、函數表達式——變量聲明借帘,默認賦值為undefined
    • this——賦值
    • 函數聲明——賦值

例:

變量:
執(zhí)行上下文-變量
this:
執(zhí)行上下文-this
函數聲明蜘渣、函數表達式:
執(zhí)行上下文-函數聲明、函數表達式

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函數,thiswindow

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í)行上下文棧缨叫。如下圖:

執(zhí)行上下文棧示意圖

例:


例-執(zhí)行上下文棧示意圖

12.作用域

  • javascript沒有塊級作用域”屉更。所謂“塊”脚翘,就是大括號“{}”中間的語句。例如if語句
  • javascript除了全局作用域之外厕吉,只有函數可以創(chuàng)建的作用域
  • 作用域在函數定義時就已經確定了有咨。而不是在函數調用時確定琐簇。

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

作用域

jQuery源碼的最外層是一個自動執(zhí)行的匿名函數:

jQuery作用域

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

  • 作用域只是一個“地盤”座享,一個抽象的概念婉商,其中沒有變量。
  • 要通過作用域對應的執(zhí)行上下文環(huán)境來獲取變量的值征讲。
  • 作用域中變量的值是在執(zhí)行過程中產生的確定的据某,而作用域卻是在函數創(chuàng)建時就確定了。
  • 如果要查找一個作用域下某個變量的值诗箍,就需要找到這個作用域對應的執(zhí)行上下文環(huán)境癣籽,再在其中尋找變量的值挽唉。
作用域

作用域與執(zhí)行上下文

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

  • 自由變量:在A作用域中使用的變量x,卻沒有在A作用域中聲明(即在其他作用域中聲明的)筷狼,對于A作用域來說瓶籽,x就是一個自由變量。
  • 自由變量的取值:
    • 1.先在當前作用域查找a埂材,如果有則獲取并結束塑顺。如果沒有則繼續(xù);
    • 2.如果當前作用域是全局作用域俏险,則證明a未定義严拒,結束;否則繼續(xù)竖独;
    • 3.(不是全局作用域裤唠,那就是函數作用域)將創(chuàng)建該函數的作用域作為當前作用域;
    • 4.跳轉到第一步莹痢。

作用域鏈

這個一步一步“跨”的路線种蘸,我們稱之為——作用域鏈


15.閉包

閉包應用的兩種情況:

  • 函數作為返回值
  • 函數作為參數傳遞
  1. 函數作為返回值
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變量的值刊侯。

  1. 函數作為參數被傳遞
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就找不到值了秕豫。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市观蓄,隨后出現(xiàn)的幾起案子混移,更是在濱河造成了極大的恐慌,老刑警劉巖侮穿,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件沫屡,死亡現(xiàn)場離奇詭異,居然都是意外死亡撮珠,警方通過查閱死者的電腦和手機沮脖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來芯急,“玉大人勺届,你說我怎么就攤上這事∪⑺#” “怎么了免姿?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長榕酒。 經常有香客問我胚膊,道長,這世上最難降的妖魔是什么想鹰? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任紊婉,我火速辦了婚禮,結果婚禮上辑舷,老公的妹妹穿的比我還像新娘喻犁。我一直安慰自己,他們只是感情好何缓,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布肢础。 她就那樣靜靜地躺著,像睡著了一般碌廓。 火紅的嫁衣襯著肌膚如雪传轰。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天谷婆,我揣著相機與錄音慨蛙,去河邊找鬼辽聊。 笑死,一個胖子當著我的面吹牛股淡,可吹牛的內容都是我干的身隐。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼唯灵,長吁一口氣:“原來是場噩夢啊……” “哼贾铝!你這毒婦竟也來了?” 一聲冷哼從身側響起埠帕,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤垢揩,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后敛瓷,有當地人在樹林里發(fā)現(xiàn)了一具尸體叁巨,經...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年呐籽,在試婚紗的時候發(fā)現(xiàn)自己被綠了锋勺。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡狡蝶,死狀恐怖庶橱,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情贪惹,我是刑警寧澤苏章,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站奏瞬,受9級特大地震影響枫绅,放射性物質發(fā)生泄漏。R本人自食惡果不足惜硼端,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一并淋、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧显蝌,春花似錦预伺、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽脏嚷。三九已至骆撇,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間父叙,已是汗流浹背神郊。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工肴裙, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人涌乳。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓蜻懦,卻偏偏與公主長得像,于是被迫代替她去往敵國和親夕晓。 傳聞我的和親對象是個殘疾皇子宛乃,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內容