深入理解JavaScript原型與閉包-學(xué)習(xí)筆記

本文為深入理解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è)的方框就是它的原型:


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是一個(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,如下圖:

對(duì)象的隱式原型

即,每個(gè)對(duì)象都有一個(gè)__proto__屬性,指向創(chuàng)建該對(duì)象的函數(shù)的prototype

  • Object prototype的隱式原型:Object prototype也是一個(gè)對(duì)象,它的__proto__指向哪里?null,如下圖:
Object.prototype的隱式原型
  • 函數(shù)的隱式原型:函數(shù)是通過new Functoin()創(chuàng)建的:
函數(shù)的隱式原型

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

Ojbect Layout
Ojbect Layout

instanceof表示的就是一種繼承關(guān)系,或者原型鏈的結(jié)構(gòu)


6.繼承

  1. 定義

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

  1. 對(duì)象的繼承

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

f1.hasOwnProperty(item)

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

對(duì)象的原型鏈

  1. 函數(shù)的繼承

  • 每個(gè)函數(shù)都有callapply方法,都有lengthargumentscaller等屬性
  • 函數(shù)由Function函數(shù)創(chuàng)建,因此繼承的Function.prototype中的方法。
    函數(shù)的原型鏈
  • Function.prototype繼承自Object.prototype的方法,因此有hasOwnProperty方法

7.原型的靈活性

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

Foo.prototype.toString = function(){
    return 'jyoketsu';
}
console.log(f1.toString());// jyoketsu
  1. 如果感覺當(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ù)聲明——賦值

例:

變量:
執(zhí)行上下文-變量
this:
執(zhí)行上下文-this
函數(shù)聲明、函數(shù)表達(dá)式:
執(zhí)行上下文-函數(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ù),thiswindow

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

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

例:


例-執(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ù):

jQuery作用域

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)境,再在其中尋找變量的值。
作用域

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

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ù)傳遞
  1. 函數(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變量的值。

  1. 函數(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就找不到值了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,501評(píng)論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,673評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,610評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,939評(píng)論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,668評(píng)論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,004評(píng)論 1 329
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,001評(píng)論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,173評(píng)論 0 290
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,705評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,426評(píng)論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,656評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,139評(píng)論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,833評(píng)論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,247評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,580評(píng)論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,371評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,621評(píng)論 2 380

推薦閱讀更多精彩內(nèi)容