摘要:本文是本人閱讀學(xué)習(xí)深入理解原型和閉包時所作的總結(jié)和筆記,當然也引用了很多原文,感興趣的朋友也可以直接去看原文。即這里的稱為隱式原型。注意,構(gòu)造函數(shù)的函數(shù)名第一個字母大寫規(guī)則約定。但實際上,上述情況是一種理想的情況。
本文是本人閱讀學(xué)習(xí)深入理解JavaScript原型和閉包時所作的總結(jié)和筆記,當然也引用了很多原文,感興趣的朋友也可以直接去看原文。
1、一切都是對象先說結(jié)論,一切引用類型都是對象,對象是屬性的集合。
首先我們對不同變量使用typeof()看看都有哪些輸出的類型。
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
在以上代碼中,undefined, number, string, boolean屬于值類型,不是對象。
而其他的幾種類型 - 包括函數(shù)、數(shù)組、對象、null、new Number(10)都是對象,它們屬于引用類型。
在JavaScript中,數(shù)組是對象,函數(shù)是對象,對象還是對象。對象里面的一切都是屬性,只有屬性,沒有方法,或者說方法也是一種屬性。屬性表示為鍵值對的形式。
JavaScript中的對象可以任意的擴展屬性,定義屬性的方法通常有兩種。
var obj = { a = 10, b: function(x) { console.log(this.a + x) }, c: { name: "Steven", year: 1988 } }
上面這段代碼中,obj是一個自定義的對象,其中a、b、c是它的屬性,而屬性c的本身又是一個對象,它又有name、year兩個屬性。
函數(shù)和數(shù)組不能用上面的方法定義屬性,下面以函數(shù)為例:
var fn = function () { alert(100); }; fn.a = 10; fn.b = function () { alert(123); }; fn.c = { name: "Steven", year: 1988 };
在jQuery源碼中,變量jQuery或者$其實是一個函數(shù),我們可以用typeof()驗證一下:
console.log(typeof ($)); // function console.log($.trim(" ABC "));
很明顯,這就是在$或者jQuery函數(shù)上加了一個trim屬性,屬性值是函數(shù),作用是截取前后空格。
2、函數(shù)和對象的關(guān)系上文已經(jīng)說到,函數(shù)也是一種對象,我們可以用instanceof驗證一下:
var fn = function () { }; console.log(fn instanceof Object); // true
但是函數(shù)和對象的關(guān)系卻有一點復(fù)雜,請看下面這個例子:
function Fn() { this.name = "嚴新晨"; this.year = 1990; } var fn_1 = new Fn();
由上面這個例子可以得出,對象是可以通過函數(shù)創(chuàng)建的。
但其實,對象都是通過函數(shù)創(chuàng)建的。
var obj = { a: 10, b: 20 }; var arr = [5, "x", true];
上面這種方式,其實是一個語法糖,而這段代碼的本質(zhì)是:
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
由此可以得出,對象都是通過函數(shù)創(chuàng)建的。
3、prototype原型每個函數(shù)都有一個默認屬性 - prototype。
這個prototype的屬性值是一個對象,這個對象有一個默認屬性 - constructor,這個屬性指向這個函數(shù)本身。
而原型作為一個對象,除了constructor之外,當然可以有其他屬性,以函數(shù)Object為例,在瀏覽器調(diào)試窗口輸入Object.prototype會得到以下返回值:
Object ... constructor hasOwnProperty isPrototypeOfs toLocalString toString valueOf ...
同時,我們還可以為這個原型增添自定義方法或?qū)傩?/p>
function Fn(){} Fn.prototype.name = "Steven" Fn.prototype.getYear = function(){ return 1988; } var fn = new Fn(); console.log(fn.name); console.log(fn.getYear());
在上例中,Fn是一個函數(shù),fn對象是從Fn函數(shù)中new出來的,這樣fn對象就可以調(diào)用Fn.prototype中的屬性。
每個對象都有一個隱藏的屬性 - __proto__,這個屬性引用了創(chuàng)建這個對象的函數(shù)的prototype。即:fn.__proto__ === Fn.prototype
這里的__proto__稱為“隱式原型”。
4、隱式原型每個函數(shù)function都有一個prototype,即原型。
每個對象都有一個__proto__,可稱為隱式原型。
var obj = {} console.log(obj.__proto__ === Object.prototype) // true
每個對象都有一個__proto__屬性,指向創(chuàng)建該對象的函數(shù)的prototype
function Foo(){} Foo.__proto__ === Function.prototype // true Object.__proto__ === Function.prototype // true Function.__proto__ === Function.prototype // true Function.prototype.__proto__ === Object.prototype // true
注意,Object.prototype.__proto__是一個特例,它指向的是null
5、instanceof由于typeof在判斷引用類型時,返回值只有object或function,這時我們可以用到instanceof。
function Foo(){} var f = new Foo() console.log(f instanceof Foo) // true console.log(f instanceof Object) // true
用法:A instanceof B,變量A是一個待判斷的對象,變量B通常是一個函數(shù)。
判斷規(guī)則:沿著A.__proto__和B.prototype查找,如果能找到同一個引用,即同一個對象,則返回true。
由以上判定規(guī)則,我們可以解釋許多奇怪的判定結(jié)果,例如:
Object instanceof Function // true Function instanceof Object // true Function instanceof Function // true
instanceof表示的是一種繼承關(guān)系 - 原型鏈
6、繼承JavaScript中的繼承通過原型鏈來體現(xiàn)。
function Foo(){} var f = new Foo() f.a = 10 Foo.prototype.a = 100 Foo.prototype.b = 200 console.log(f.a) // 10 console.log(f.b) // 200
上例中,f是Foo函數(shù)new出來的對象,f.a是對象f的基本屬性,因為f.__proto__ === Foo.prototype,所以f.b是從Foo.prototype中繼承而來的。
在JavaScript中,訪問一個對象的屬性時,先在基本屬性中查找,如果沒有,再沿著__proto__這條鏈向上找,這就是原型鏈
通過hasOwnProperty,我們可以判斷出一個屬性到底是基本屬性,還是從原型中繼承而來的。
function Foo(){} var f_1 = new Foo() f.a = 10 Foo.prototype.a = 100 Foo.prototype.b = 200 var item for(item in f){ console.log(item) // a b } for(item in f){ if(f.hasOwnProperty(item){ console.log(item) // a }) }
hasOwnProperty方法是從Object.prototype中繼承而來的
每個函數(shù)都有apply、call方法,都有length、arguments等屬性,這些都是從Function.prototype中繼承而來的
由于Function.prototype.__proto__指向Object.prototype,所以函數(shù)也會有hasOwnProperty方法
7、原型的靈活性首先,對象屬性可以隨時改動
其次,如果繼承的方法不合適,可以隨時修改
var obj = { a: 10, b: 20 } console.log(obj.toString()) // [object Object] var arr = [1, 2, true] console.log(arr.toString()) // 1, 2, true
從上例中可以看出,Object和Array的toString()方法是不一樣的,肯定是Array.prototype.toString()作了修改。
同理,我們也可以自己定義一個函數(shù)并修改toString()方法。
function Foo(){} var f = new Foo() Foo.prototype.toString = function(){ return "嚴新晨" } console.log(f.toString) // 嚴新晨
最后,如果缺少需要的方法,也可以自己創(chuàng)建
如果要添加內(nèi)置方法的原型屬性,最好做一步判斷,如果該屬性不存在,則添加。如果本來就存在,就沒必要再添加了。
8、簡述 - 執(zhí)行上下文 - 上執(zhí)行上下文,也叫執(zhí)行上下文環(huán)境
console.log(a) // 報錯,a is not undefined
console.log(a) // undefined var a;
console.log(a) // undefined var a = 10;
console.log(this) // Window {...}
console.log(f_1) // function f_1({}) function f_1(){} // 函數(shù)聲明 console.log(f_2) // undefined var f_2 = function(){} // 函數(shù)表達式
在js代碼執(zhí)行前,瀏覽器會先進行一些準備工作:
變量、函數(shù)表達式 - 變量聲明,默認賦值為undefined;
this - 賦值;
函數(shù)聲明 - 賦值;
這三種數(shù)據(jù)的準備工作我們稱之為“執(zhí)行上下文”或者“執(zhí)行上下文環(huán)境”。
JavaScript在執(zhí)行一個代碼段之前,都會進行這些“準備工作”來生成執(zhí)行上下文。這個“代碼段”其實分三種情況 - 全局代碼,函數(shù)體,eval代碼。
首先,全局代碼,寫在標簽里的代碼段
其次,eval接收的是一段文本形式的代碼(不推薦使用)
最后,函數(shù)體代碼段是函數(shù)在創(chuàng)建時,本質(zhì)上是new Function(…)得來的,其中需要傳入一個文本形式的參數(shù)作為函數(shù)體
var fn = new Function("x", "console.log(x+5)")9、簡述 - 執(zhí)行上下文 - 下
function fn(x){ console.log(arguments) console.log(x) } fn(10)
以上代碼展示了在函數(shù)體的語句執(zhí)行之前,arguments變量和函數(shù)的參數(shù)都已經(jīng)被賦值。從這里可以看出,函數(shù)每被調(diào)用一次,都會產(chǎn)生一個新的執(zhí)行上下文環(huán)境。因為不同的調(diào)用可能就會有不同的參數(shù)。
函數(shù)在定義的時候(不是調(diào)用的時候),就已經(jīng)確定了函數(shù)體內(nèi)部自由變量的作用域。
var a = 10 function fn(){ console.log(a) } function bar(fn){ var a = 20 fn() // 10 } bar(fn)
總結(jié)一下上下文環(huán)境的數(shù)據(jù)內(nèi)容
普通變量 - 聲明
函數(shù)表達式 - 聲明
函數(shù)聲明 - 賦值
this - 賦值
如果代碼段是函數(shù)體,則需加上
參數(shù) - 賦值
arguments - 賦值
自由變量的取值作用域 - 賦值
所以通俗來講,執(zhí)行上下文環(huán)境就是在執(zhí)行代碼之前,把將要用到的所有的變量都事先拿出來,有的直接賦值了,有的先用undefined占個空。
10、thisthis的取值,通常分4種情況
先強調(diào)一點,在函數(shù)中this到底取何值,是在函數(shù)真正被調(diào)用執(zhí)行的時候確定的,函數(shù)定義的時候確定不了,因為this的取值是執(zhí)行上下文環(huán)境的一部分,每次調(diào)用函數(shù),都會產(chǎn)生一個新的執(zhí)行上下文環(huán)境。
情況1:構(gòu)造函數(shù)
所謂構(gòu)造函數(shù)就是用來new對象的函數(shù)。
注意,構(gòu)造函數(shù)的函數(shù)名第一個字母大寫(規(guī)則約定)。例如:Object、Array、Function等。
function Foo(){ this.name = "Steven" this.year = 1988 console.log(this) // Foo {name: "Steven", year: 1988} } var f = new Foo(); console.log(f.name) // Steven console.log(f.year) // 1988
如果函數(shù)作為構(gòu)造函數(shù)調(diào)用,那么其中的this就代表它即將new出來的對象。
如果直接調(diào)用Foo函數(shù),而不是new Foo(),情況就完全不同。
function Foo(){ this.name = "Steven" this.year = 1988 console.log(this) // Window {...} } Foo()
情況2:函數(shù)作為對象的一個屬性
如果函數(shù)作為對象的一個屬性,并且作為對象的一個屬性被調(diào)用時,函數(shù)中的this指向該對象。
var obj = { x: 10, fn: function(){ console.log(this) // Object {x: 10, fn: function} console.log(this.x) // 10 } } obj.fn()
如果函數(shù)fn是對象obj的一個屬性,但是不作為obj的一個屬性被調(diào)用
var obj = { x: 10, fn: function(){ console.log(this) // Window {...} console.log(this.x) // undefined } } var f = obj.fn f()
情況3:函數(shù)用call或者apply調(diào)用
當一個函數(shù)被call和apply調(diào)用時,this的值就取傳入的對象的值。
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永遠是Window
普通函數(shù)在調(diào)用時,其中的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 foo(){ console.log(this) // Window console.log(this.x) // undefined } foo() } } obj.fn()
函數(shù)foo雖然是在obj.fn內(nèi)部定義的,但是它仍然是一個普通的函數(shù),this仍然指向window。
補充:情況5 - 在構(gòu)造函數(shù)的prototype中
function Fn() { this.name = "Steven"; this.year = 1988; } Fn.prototype.getName = function () { console.log(this.name); // 這里的this指向f1對象 } var f1 = new Fn(); f1.getName(); // Steven11、執(zhí)行上下文棧
上文說過,執(zhí)行全局代碼時,會產(chǎn)生一個全局上下文環(huán)境,每次調(diào)用函數(shù)都又會產(chǎn)生函數(shù)上下文環(huán)境。當函數(shù)調(diào)用完成時,函數(shù)上下文環(huán)境以及其中的數(shù)據(jù)都會被消除,再重新回到全局上下文環(huán)境。處于活動狀態(tài)的執(zhí)行上下文環(huán)境始終只有一個。其實這是一個壓棧出棧的過程 - 執(zhí)行上下文棧。
以下面代碼為例:
var a = 10, // 1、進入全局上下文環(huán)境 fn, bar = function(x){ var x = 5 fn(x+b) // 3、進入fn()函數(shù)上下文環(huán)境 } fn = function(y){ var c = 5 console.log(y+c) } bar() // 2、進入bar()函數(shù)上下文環(huán)境
執(zhí)行代碼前,首次創(chuàng)建全局上下文環(huán)境
a === undefined fn === undefined bar === undefined this === window
代碼執(zhí)行時,全局上下文環(huán)境中的各個變量被賦值
a === 10 fn === function bar === function this === window
調(diào)用bar()函數(shù)時,會創(chuàng)建一個新的函數(shù)上下文環(huán)境
b === undefined x === 5 arguments === [5] this === window
以上是一段簡短代碼的執(zhí)行上下文環(huán)境的變化過程,一個完整的閉環(huán)。
但實際上,上述情況是一種理想的情況。而有一種很常見的情況,無法做到這樣干凈利落的說銷毀就銷毀,那就是閉包。
12、簡述 - 作用域JavaScript沒有塊級作用域。所謂的“塊”就是“{}”中的語句,比如:if(){}或者for(){}之類的。
所以,編寫代碼時不要在“塊”里聲明變量。
重點來了:JavaScript除了全局作用域之外,只有函數(shù)可以創(chuàng)建的作用域
所以,在聲明變量時,全局代碼要在代碼前端聲明,函數(shù)中要在函數(shù)體一開始就聲明好。除了這兩個地方,其他地方都不要出現(xiàn)變量聲明。而且建議用“單var”形式。
作用域有上下級的關(guān)系,上下級關(guān)系的確定就看函數(shù)是在哪個作用域下創(chuàng)建的
作用域最大的用處就是隔離變量,不同作用域下同名變量不會有沖突
13、作用域和上下文環(huán)境在上文中已經(jīng)說過,除了全局作用域之外,每個函數(shù)都會創(chuàng)建自己的作用域。作用域在函數(shù)定義時就已經(jīng)確定了,而不是在函數(shù)調(diào)用時確定。
var a = 10, b = 20; // 全局作用域:a=10,b=20 function fn(x){ var a = 100, c = 300; // fn(10):a=100,c=300,x=10 function bar(x){ var a = 1000, d = 4000; // bar(100):a=1000,d=4000,x=100 // bar(100):a=1000,d=4000,x=200 } bar(100); bar(200); } fn(10)
作用域只是一個“地盤”,一個抽象的概念,其中沒有變量。
要通過作用域?qū)?yīng)的執(zhí)行上下文環(huán)境來獲取變量的值。
**同一個作用域下,不同的調(diào)用會產(chǎn)生不同的執(zhí)行上下文環(huán)境,繼而產(chǎn)生不同的變量的值。
所以,作用域中變量的值是在執(zhí)行過程中產(chǎn)生的確定的,而作用域是在函數(shù)創(chuàng)建時就確定了。
所以,如果要查找一個作用域下某個變量的值,就需要找到這個作用域?qū)?yīng)的執(zhí)行上下文環(huán)境,再在其中尋找變量的值。
上文中有一種常見情況我們并沒有討論,那就是跨作用域取值的情況。
先說明一個概念 - 自由變量
var a = 10 function fn(){ var b = 20 return a + b // 這里的a就是一個自由變量 }
很多人對此解釋為a是從父作用域取值的,這種說法基本正確,但有些時候會產(chǎn)生歧義。
var x = 10; function fn(){ console.log(x) // 10 } function show(f){ var x = 20; (function(){ f() // 10,而不是20 })() } show(fn);
所以,更準確的說法是,我們要到創(chuàng)建這個函數(shù)的作用域中去取值。
var a = 10; function fn() { var b = 20; function bar() { console.log(a + b); // 創(chuàng)建函數(shù)bar()時,b=20 // 通過作用域鏈查找到a=10 } return bar; } var x = fn(), b = 200; x(); // 3015、閉包
先回顧下前面章節(jié)講到的兩個重點:
自由變量跨作用域取值時,要去創(chuàng)建函數(shù)的作用域取值,而不是調(diào)用函數(shù)的作用域;
當一個函數(shù)被調(diào)用完成之后,其執(zhí)行上下文環(huán)境將被銷毀,其中的變量也會被同時銷毀。
“閉包”這個詞的概念很不好解釋,但我們只需記住兩種情況即可:函數(shù)作為返回值和函數(shù)作為參數(shù)傳遞。
首先,函數(shù)作為返回值,先看個例子
// 1、全局作用域,max=100,其他變量undefined function fn() { // 2、fn()作用域,max=10(調(diào)用結(jié)束后銷毀),其他變量undefined var max = 10; return function bar(x) { // 3、bar()作用域,max=10,x=15(調(diào)用結(jié)束后銷毀) if (x > max) { console.log(x); } } } var f1 = fn(), // bar()作為返回值賦值給f1 max = 100; f1(15); // 15
然后,函數(shù)作為參數(shù)傳遞,再看個例子
// 全局作用域,max=10 var max = 10, fn = function (x) { if (x > max) { console.log(x) // 15 } }; (function (f) { var max = 100; f(15); // max=10而不是100 })(fn);
先回顧下前面章節(jié)講到的兩個重點:
自由變量跨作用域取值時,要去創(chuàng)建函數(shù)的作用域取值,而不是調(diào)用函數(shù)的作用域;
當一個函數(shù)被調(diào)用完成之后,其執(zhí)行上下文環(huán)境將被銷毀,其中的變量也會被同時銷毀。
16、完結(jié) - 這章沒干貨我也就不寫了o(╯□╰)o 17、補充 - this - 我直接寫在10、this那一篇里了就不贅述了 18、補充 - 上下文環(huán)境和作用域的關(guān)系本篇主要是解釋一下上下文環(huán)境和作用域并不是一回事。
上下文環(huán)境 - 可以理解為一個看不見摸不著的對象(有若干個屬性),在調(diào)用函數(shù)時創(chuàng)建,用來保存調(diào)用函數(shù)時的各個變量。
作用域 - 除了全局作用域,只有創(chuàng)建函數(shù)才會創(chuàng)建作用域,無論你是否調(diào)用函數(shù),函數(shù)只要創(chuàng)建了就有一個獨立的作用域。
兩者 - 一個作用域可能包含若干個上下文環(huán)境,也可能從來沒有過上下文環(huán)境(函數(shù)從未被調(diào)用),還可能函數(shù)調(diào)用完畢后上下文環(huán)境被銷毀了等多種情況。
以下面代碼為例:
// 全局作用域中x=100 var x = 100; function fn(x) { // fn(x)作用域 // 調(diào)用f1()時的上下文環(huán)境中,x=5 // 調(diào)用f2()時的上下文環(huán)境中,x=10 return function () { // 匿名function作用域 console.log(x); } } var f1 = fn(5), f2 = fn(10); f1(); // 5 f2(); // 10
所謂上下文環(huán)境就是調(diào)用函數(shù)時創(chuàng)建的一個臨時作用域,根據(jù)調(diào)用情況不同里面的變量會發(fā)生變化;而作用域是隨著函數(shù)的創(chuàng)建而創(chuàng)建,里面的變量可能會在調(diào)用時被上下文環(huán)境中相同變量覆蓋。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/81366.html
摘要:理解的函數(shù)基礎(chǔ)要搞好深入淺出原型使用原型模型,雖然這經(jīng)常被當作缺點提及,但是只要善于運用,其實基于原型的繼承模型比傳統(tǒng)的類繼承還要強大。中文指南基本操作指南二繼續(xù)熟悉的幾對方法,包括,,。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。 怎樣使用 this 因為本人屬于偽前端,因此文中只看懂了 8 成左右,希望能夠給大家?guī)韼椭?...(據(jù)說是阿里的前端妹子寫的) this 的值到底...
摘要:深入理解原型和閉包王福朋博客園深入理解原型和閉包一切都是對象原文鏈接本文要點一切引用類型都是對象,對象是屬性的集合。每個對象都有一個,可稱為隱式原型。另外注意,構(gòu)造函數(shù)的函數(shù)名第一個字母大寫規(guī)則約定。 深入理解javascript原型和閉包 王福朋 - 博客園 —— 《 深入理解javascript原型和閉包》 1. 一切都是對象 原文鏈接:http://www.cnblogs.com...
摘要:也就是說,所有的函數(shù)和構(gòu)造函數(shù)都是由生成,包括本身。如果只考慮構(gòu)造函數(shù)和及其關(guān)聯(lián)的原型對象,在不解決懸念的情況下,圖形是這樣的可以看到,每一個構(gòu)造函數(shù)和它關(guān)聯(lián)的原型對象構(gòu)成一個環(huán),而且每一個構(gòu)造函數(shù)的屬性無所指。 前言 JavaScript 是我接觸到的第二門編程語言,第一門是 C 語言。然后才是 C++、Java 還有其它一些什么。所以我對 JavaScript 是非常有感情的,畢...
摘要:情況構(gòu)造函數(shù)所謂構(gòu)造函數(shù)就是用來對象的函數(shù)。另外注意,構(gòu)造函數(shù)的函數(shù)名第一個字母大寫規(guī)則約定。閉包但是你只需要知道應(yīng)用的兩種情況即可函數(shù)作為返回值,函數(shù)作為參數(shù)傳遞。如上代碼,函數(shù)作為返回值,賦值給變量。這就是需要理解閉包的核心內(nèi)容。 原文鏈接http://www.cnblogs.com/wangfupeng1988/p/3977924.html 對象是屬性的集合。 function ...
摘要:從最開始的到封裝后的都在試圖解決異步編程過程中的問題。為了讓編程更美好,我們就需要引入來降低異步編程的復(fù)雜性。異步編程入門的全稱是前端經(jīng)典面試題從輸入到頁面加載發(fā)生了什么這是一篇開發(fā)的科普類文章,涉及到優(yōu)化等多個方面。 TypeScript 入門教程 從 JavaScript 程序員的角度總結(jié)思考,循序漸進的理解 TypeScript。 網(wǎng)絡(luò)基礎(chǔ)知識之 HTTP 協(xié)議 詳細介紹 HTT...
閱讀 3672·2021-09-22 15:28
閱讀 1296·2021-09-03 10:35
閱讀 878·2021-09-02 15:21
閱讀 3474·2019-08-30 15:53
閱讀 3496·2019-08-29 17:25
閱讀 569·2019-08-29 13:22
閱讀 1555·2019-08-28 18:15
閱讀 2287·2019-08-26 13:57