您好,登錄后才能下訂單哦!
這篇文章主要講解了“JavaScript中的對象有哪些”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“JavaScript中的對象有哪些”吧!
ECMAScript, 是一門高級抽象面向對象的語言,用以處理Objects. 當然,也有原生類型,但是必要時,也需要轉換成object.
Object是一個屬性的集合,并且都擁有一個單獨的原型對象[prototype object]. 這個原型對象[prototype object]可以是一個object或者null值。
讓我們舉一個Object的例子。首先我們要清楚,一個Object的prototype是一個內部的[[prototype]]屬性的引用。不過一般來說,我們會使用____ 下劃線來代替雙括號,例如__prototype__
看這段代碼
var foo = { x: 10, y: 20};
如上結構有兩個顯式的屬性[explicit own properties]和一個自帶隱式的 __proto__ 屬性[implicit
__proto__ property],也就是foo的prototype.
這個property有什么用處呢?我們來看一個原型鏈[prototype chain]的例子。
Prototype對象也是對象類型的,也會有自己的prototypes。如果這個prototype仍然存在一個非空的prototype,那么這樣一直搜尋下去,就形成了一個原型鏈[prototype chain]。
原型鏈是由一系列用來繼承和共享屬性的對象組成有限的對象鏈。
考慮這樣的一個情況。
有兩個對象,只有一小部分的方法或者屬性不同,其余的都是一樣的。很明顯,在一個好的設計模式中,我們會需要重用那部分相同的,而不是在每個對象中重復定義那些相同的方法或者屬性。在基于類[class-based]的系統中,這些重用部分被稱為類的繼承
– 相同的部分放入class A,然后class B和class C從A繼承,并且擁有各自的獨特的東西。
ECMAScript沒有類的概念。但是,重用[reuse]這個理念沒什么不同(某些方面,甚至比class-更加靈活),可以由prototype chain原型鏈來實現。這種繼承被稱為delegation
based inheritance-基于繼承的委托,或者更通俗一些,叫做原型繼承。
上述的Classes A,B,C,在ECMAScript中可以創建三個對象:a,b,c. 由a來負責b和c相同的部分,而b和c則負責各自不同的獨特的東西。
var a = { x: 10, calculate: function (z) {return this.x + this.y + z } };var b = { y: 20, __proto__: a};var c = { y: 30, __proto__: a};// call the inherited methodb.calculate(30); // 60c.calculate(40); // 80
這樣看上去是不是很簡單啦。b和c可以使用a中定義的calculate方法,這就是有原型鏈來[prototype chain]實現的。
我們來看一下原理:如果在對象b中找不到calculate方法(也就是對象b中沒有這個calculate屬性), 那么就會沿著原型鏈開始找。如果這個calculate方法在b的prototype中沒有找到,那么就會沿著原型鏈找到a的prototype,一直遍歷完整個原型鏈。記住,一旦找到,就返回第一個找到的屬性或者方法。因此,第一個找到的屬性成為繼承屬性。如果遍歷完整個原型鏈,仍然沒有找到,那么就會返回undefined.
注意一點,this這個值在一個繼承機制中,仍然是指向它原本屬于的對象,而不是從原型鏈上找到它時,它所屬于的對象。例如,以上的例子,this.y是從b和c中獲取的,而不是a。當然,你也發現了this.x是從a取的,因為是通過原型鏈機制找到的。
如果一個對象的prototype沒有顯示的聲明過或者說定義過,那么__prototype__的默認值就是object.prototype, 而object.prototype也會有一個__prototype__, 這個就是原型鏈的終點了,被設置為null.
下面的圖示就是表示了上述a,b,c的關系
我們再考慮一種情況。有時候,我們需要對象使用相同的或者相似的結構(例如相同的屬性集),但是不同的值。在這種情況下,我們可以考慮使用構造函數[constructor function],是一種特定結構構造的對象。
除了可以構造特定對象,構造函數[constructor]還有一個有用的功能 – 為一個新建對象創建一個原型對象。這個原型對象放置在構造函數的原型[ConstrutorFunction.prototype]中。
我們用構造函數重寫一下上述的例子。
// a constructor functionfunction Foo(y) { // which may create objects // by specified pattern: they have after // creation own "y" property this.y = y; }// also "Foo.prototype" stores reference// to the prototype of newly created objects,// so we may use it to define shared/inherited// properties or methods, so the same as in// previous example we have:// inherited property "x"Foo.prototype.x = 10;// and inherited method "calculate"Foo.prototype.calculate = function (z) { return this.x + this.y + z; };// now create our "b" and "c"// objects using "pattern" Foovar b = new Foo(20);var c = new Foo(30);// call the inherited methodb.calculate(30); // 60c.calculate(40); // 80// let's show that we reference// properties we expectconsole.log( b.__proto__ === Foo.prototype, // true c.__proto__ === Foo.prototype, // true // also "Foo.prototype" automatically creates // a special property "constructor", which is a // reference to the constructor function itself; // instances "b" and "c" may found it via // delegation and use to check their constructor b.constructor === Foo, // true c.constructor === Foo, // true Foo.prototype.constructor === Foo // true b.calculate === b.__proto__.calculate, // true b.__proto__.calculate === Foo.prototype.calculate // true);
上述的代碼是如下的關系
上述的圖示可以看出來,每一個object都有一個prototype. 構造函數Foo也擁有自己的__proto__, 也就是Function.prototype, 而Function.prototype的__proto__指向了Object.prototype. 因此,Foo.prototype只是一個顯式的屬性,也就是b和c的__proto__屬性。
針對這個內容更完整和詳細的可以在第七章ES3系列中找到。有兩個部分: Chapter 7.1. OOP. The general theory(各種OOP的原理和它們與ECMAScript的對比),還有Chapter
7.2. OOP. ECMAScript implementation(闡述如何將OOP的概念引入到ECMAScript中)
現在,我們已經了解了基本的object原理,那么我們接下去來看看ECMAScript里面的程序執行環境[runtime program execution]. 這就是通常稱為的“執行上下文堆棧”[execution
context stack]。每一個元素都可以抽象的理解為object。你也許發現了,沒錯,在ECMAScript中,幾乎處處都能看到object的身影。
在ECMASscript中的代碼有三種類型:global, function和eval。
每一種代碼的執行都需要依賴自身的上下文。當然global的上下文可能涵蓋了很多的function和eval的實例。函數的每一次調用,都會進入函數執行中的上下文,并且來計算函數中變量等的值。eval函數的每一次執行,也會進入eval執行中的上下文,判斷應該從何處獲取變量的值。
注意,一個function可能產生無限的上下文環境,因為一個函數的調用(甚至遞歸)都產生了一個新的上下文環境。
function foo(bar) {}// call the same function,// generate three different// contexts in each call, with// different context state (e.g. value// of the "bar" argument)foo(10);foo(20);foo(30);
一個執行上下文可以激活另一個上下文,就好比一個函數調用了另一個函數(或者全局的上下文調用了一個全局函數),然后一層一層調用下去。邏輯上來說,這種實現方式是棧,我們可以稱之為上下文堆棧。
例如A上下文激活了B的上下文,那么A稱為caller,B稱為callee. 一個callee同時也可能是另一個callee的caller。(例如一個全局上下文中的function又一次調用了它的內部函數。)
當一個caller激活了一個callee,那么這個caller就會暫停它自身的執行,然后將控制權交給這個callee. 于是這個callee被放入堆棧,稱為進行中的上下文[running/active
execution context]. 當這個callee的上下文結束之后,會把控制權再次交給它的caller,然后caller會在剛才暫停的地方繼續執行。在這個caller結束之后,會繼續觸發其他的上下文。一個callee可以用return或者例外退出(exit with an exception)結束它自身的上下文.
所有的ECMAScript的程序執行都可以看做是一個執行上下文堆棧[execution context (EC) stack]。堆棧的頂部就是處于激活狀態的上下文。
當一段程序開始時,會先進入全局執行上下文環境[global execution context], 這個也是堆棧中最底部的元素。此全局程序會開始初始化,初始化生成必要的對象[objects]和函數[functions].
在此全局上下文執行的過程中,它可能會激活一些方法(當然是已經初始化過的),然后進入他們的上下文環境,然后將新的元素加入堆棧。在這些初始化都結束之后,這個系統會等待一些事件(例如用戶的鼠標點擊等),會觸發一些方法,然后進入一個新的上下文環境。
在這個圖示中,一些方法的上下文稱為EC1,全局的上下文稱為 Global EC,我們看一下堆棧形式,演示了從全局上下文進入和推出EC1的過程。
這就是ECMAScript的執行系統如何管理代碼執行的。
關于上下文環境的更多內容,可以在 Chapter 1. Execution context中找到。
就像我們剛剛講過的,堆棧中每一個執行的上下文可以看做一個對象[object]。接街區,我們將看看它的結構和執行它的代碼時狀態類型(或者說屬性)。
一個執行的上下文可以抽象的理解為object。每一個執行的上下文都有一系列的屬性(我們稱為上下文狀態),他們用來追蹤關聯代碼的執行進度。這個圖示就是一個context的結構
當然除了這三個必要的屬性,一個上下文環境也會根據情況依賴其他的屬性(狀態)。
讓我們仔細來看看這三個屬性
一個VO[variable object]是關聯上下文執行環境的一系列數據。它是與上下文關系密切的一個特殊對象,存儲了在上下文中定義的變量和函數聲明。
注意:函數表達式[function expression](而不是函數聲明[function
declarations])是不包含在VO[variable object]里面的。
Variable Object是一個抽象的概念,某個方面來講,它表示使用不同的object.以下簡稱VO. 例如,在global上下文中,variable object也是全局對象[global object]。(這就是我們可以通過全局對象的屬性來指向全局變量)。
讓我們看看下面例子中的全局執行上下文情況。
var foo = 10;function bar() {} // function declaration, FD(function baz() {}); // function expression, FEconsole.log( this.foo == foo, // true window.bar == bar // true);console.log(baz); // ReferenceError, "baz" is not defined
如下圖所示
可以看到,函數baz是一個函數表達式,所以它不在VO里面,所以會有一個ReferenceError的提示,因為此時是在全局環境下讀取,也就是在函數表達式外部讀取的。
注意,和其他的語言不同(例如c/c++),在ECMAScript中,只有函數functions會新建一個新的作用域。在一個函數中定義的變量和內部函數在外部是無法獲取的,同時也不會影響全局的變量。
使用eval的話,我們通常會進入一個新的eval上下文環境。Eval會使用global VO,也可以使用caller的VO。
那么函數和它們的VO的情況呢,在一個函數的上下文中,一個VO代表了激活變量[activation object],以下簡稱AO.
當一個函數被調用激活之后,這個稱為AO(activation object)的特殊對象就被新建了.它涵蓋了普通的參數和一個特殊的arguments對象(這是一個參數的映射表,并且有索引屬性)。我們也可以認為:AO實際上就是函數上下文的VO.
例如,函數的VO實際上就是簡單的VO,但是除了這些變量和函數定義,還有參數和arguments對象,這些就成為AO.
考慮下面的情況
function foo(x, y) { var z = 30; function bar() {} // FD (function baz() {}); // FE}foo(10, 20);
那么AO的圖示如下所示:
同樣道理,function expression不在AO的行列。
對于這個AO的詳細內容可以通過Chapter 2. Variable object找到
我們接下去將第三個主要的對象。眾所周知,在ECMAScript中,我們會用到內部函數[inner functions],在這些內部函數中,我們可能會引用它的父函數變量,或者全局的變量。我們把這些變量對象成為上下文作用域對象[scope
object of the context]. 類似于上面討論的原型鏈[prototype chain],我們在這里稱為作用域鏈[scope
chain]。
作用域鏈就是在代碼上下文中,搜索標識符的過程中遇到的一系列對象
這個原理和原型鏈很類似,如果這個變量在自己的作用域中沒有,那么它會尋找父級的,直到最頂層。
標示符[Identifiers]可以理解為變量名稱、函數聲明和普通參數。例如,當一個函數在自身函數體內需要引用一個變量,但是這個變量并沒有在函數內部聲明(或者也不是某個參數名),那么這個變量就可以稱為自由變量[free
variable].那么我們搜尋這些自由變量就需要用到作用域鏈。
在一般情況下,一個作用域鏈包括父級變量VO、函數自身的VO和AO。不過,有些情況下也會包含其它的對象,例如在執行期間,動態加入作用域鏈中的—例如with或者catch語句。
當處理一個變量,作用域鏈是會從AO開始搜索,然后一直搜索到頂端。和原型鏈非常類似。
var x = 10; (function foo() { var y = 20; (function bar() {var z = 30;// "x" and "y" are "free variables"// and are found in the next (after// bar's activation object) object// of the bar's scope chainconsole.log(x + y + z); })(); })();
我們假設作用域鏈的對象聯動是通過一個叫做__parent__的屬性,它是指向作用域鏈的下一個對象。這可以在Rhino Code中測試一下這種流程,這種技術也確實在ES5環境中實現了(有一個稱為outer鏈接).當然也可以用一個簡單的數據來模擬這個模型。使用__parent__的概念,我們可以把上面的代碼演示成如下的情況。(因此,父級變量是被存在函數的[[Scope]]屬性中的)
在代碼執行過程中,如果使用with或者catch語句就會改變作用域鏈。而這些對象都是一些簡單對象,他們也會有原型鏈。這樣的話,作用域鏈會從兩個維度來搜尋。
首先在原本的作用域鏈
每一個鏈接點的作用域的鏈(如果這個鏈接點是有prototype的話)
我們再看下面這個例子
Object.prototype.x = 10;var w = 20;var y = 30;// in SpiderMonkey global object// i.e. variable object of the global// context inherits from "Object.prototype",// so we may refer "not defined global// variable x", which is found in// the prototype chainconsole.log(x); // 10(function foo() { // "foo" local variables var w = 40; var x = 100; // "x" is found in the // "Object.prototype", because // {z: 50} inherits from it with ({z: 50}) {console.log(w, x, y , z); // 40, 10, 30, 50 } // after "with" object is removed // from the scope chain, "x" is // again found in the AO of "foo" context; // variable "w" is also local console.log(x, w); // 100, 40 // and that's how we may refer // shadowed global "w" variable in // the browser host environment console.log(window.w); // 20})();
我們就會有如下結構圖示。注意,在我們去搜尋__parent__之前,首先會去__proto__的鏈接中。
注意,不是所有的全局對象都是由Object.prototype繼承而來的。上述圖示的情況可以在SpiderMonkey中測試。
我們會搜尋作用域鏈來尋找需要的變量。剛才也有提過,在一個上下文結束之后,它的狀態和自身會銷毀掉。同時,這個內部函數會返回到父級函數中。也許,這個返回函數稍后會再激活另一個上下文。如何保持一個自由變量仍然處于被激活中呢?理論上來說,有一個概念可以解決這個問題,稱為閉包[Closure].它也是和作用域鏈有直接關系的。
在ECMAScript中,function[函數]是基本等級[first-class]的對象。這就意味著,函數可以作為另一個函數的參數(在這種情況下,稱之為”funargs”, 也就是”functional
arguments”的縮寫).接收funargs的函數稱為高級函數[higher-order function], 或者類似于數學中的運算符[operator].
同樣,函數也可以作為另一個函數的返回。把函數作為返回值的函數稱為以函數為值的函數[function valued functions](或者functions with functional
value)
關于上述的funargs和functional value,會引發一個叫做”Funarg problem”(或者”A problem of a functional argument)”。為了解決這個問題,我們引入了“閉包”。我們下面來詳細討論這兩個問題(在ECMAScript中的要解決這兩個問題,需要用到函數的一個[[Scope]]的屬性)。
首先”funarg problem”的一個類型就是自下而上[”upward funarg problem”]. 當一個函數作為另一個函數的返回值時,并且使用了自由變量[free
variable]的時候會發生。即便它的父級上下文環境已經結束了,它可以引用父級的變量,。這個內部函數在創建時就會將父級的作用域鏈保存在自己的作用域[[Scope]]中。當函數運行時,上下文環境的作用域量是由活躍變量[activation
object]和它[[Scope]]屬性組合而成。
Scope chain = Activation object + [[Scope]]
請再次注意這個很重要的點 – 在函數創建期間[creation moment],函數會將父級的作用域鏈保存起來,因為隨后調用這個函數的時候使用的已經保存的作用域鏈來搜尋變量。
請看下面的函數
function foo() { var x = 10; return function bar() {console.log(x); }; }// "foo" returns also a function// and this returned function uses// free variable "x"var returnedFunction = foo();// global variable "x"var x = 20;// execution of the returned functionreturnedFunction(); // 10, but not 20
這種形式的作用域稱為靜態作用域[static/lexical scope]。上面的x變量就是在函數bar的[[Scope]]中搜尋到的。理論上來說,也會有動態作用域[dynamic
scope], 也就是上述的x被解釋為20,而不是10. 但是EMCAScript不使用動態作用域。
“funarg problem”的另一個類型就是自上而下[”downward funarg problem”].在這種情況下,父級的上下會存在,但是在判斷一個變量值的時候會有多義性。也就是,這個變量究竟應該使用哪個作用域。是在函數創建時的作用域呢,還是在執行時的作用域呢?為了避免這種多義性,可以采用閉包,也就是使用靜態作用域。
請看下面的例子
// global "x"var x = 10;// global functionfunction foo() { console.log(x); } (function (funArg) { // local "x" var x = 20; // there is no ambiguity, // because we use global "x", // which was statically saved in // [[Scope]] of the "foo" function, // but not the "x" of the caller's scope, // which activates the "funArg" funArg(); // 10, but not 20})(foo); // pass "down" foo as a "funarg"
從上述的情況,我們似乎可以斷定,在語言中,使用靜態作用域是閉包的一個強制性要求。不過,在某些語言中,會提供動態和靜態作用域的結合,可以允許開發員選擇哪一種作用域。但是在ECMAScript中,只采用了靜態作用域。所以ECMAScript完全支持使用[[Scope]]的屬性。我們可以給閉包得出如下定義
閉包是一系列代碼塊(在ECMAScript中是函數),并且靜態保存所有父級的作用域。通過這些保存的作用域來搜尋到函數中的自由變量。
注意,其實每一個函數都在創建期間保存[[Scope]],所以理論上來說,在ECMAScript中所有的函數都是閉包。
還有一個很重要的點,幾個函數可能含有相同的父級作用域(這是一個很普遍的情況,例如有好幾個內部或者全局的函數)。在這種情況下,在[[Scope]]中存在的變量是會共享的。一個閉包中變量的變化,也會影響另一個閉包的。
function baz() { var x = 1; return {foo: function foo() { return ++x; },bar: function bar() { return --x; } }; }var closures = baz();console.log( closures.foo(), // 2 closures.bar() // 1);
上述代碼可以用這張圖來表示
如果有這個特性的話,在一個循環中創建多個函數就會有關聯了。在循環中創建函數,我們可能會遇到意想不到的情況,因為所有的函數都會共享同一個變量了。所有的函數都有同一個[[Scope]], 并且都指向了最后一次賦值的count。
var data = [];for (var k = 0; k < 3; k++) { data[k] = function () {alert(k); }; }data[0](); // 3, but not 0data[1](); // 3, but not 1data[2](); // 3, but not 2
有幾種方法可以解決種鴿問題。一種就是在作用域鏈中提供一個新增的對象,例如增加一個函數。
var data = [];for (var k = 0; k < 3; k++) { data[k] = (function (x) {return function () { alert(x); }; })(k); // pass "k" value}// now it is correctdata[0](); // 0data[1](); // 1data[2](); // 2
如果對閉包和它的應用有興趣的,可以去看看 Chapter 6. Closures.
如果對作用域鏈想要有更深入鏈接的,可以去看看 Chapter 4. Scope chain。
接下去我們討論下一個章節,也就是上下文環境的最后一個屬性– this.
任何對象在上下文環境中都可以使用this。我需要澄清一個誤區,在一些描述中總是認為this是一個變量對象的屬性。請記住
this是執行上下文環境的一個屬性,而不是某個變量對象的屬性
這個特點很重要,因為和變量不同,this是沒有一個類似搜尋變量的過程。當你在代碼中使用了this,這個 this的值就直接從執行的上下文中獲取了,而不會從作用域鏈中搜尋。this的值只取決中進入上下文時的情況。
順便說一句,和ECMAScript不同,Python有一個self的參數,和this的情況差不多,但是可以在執行過程中被改變。在ECMAScript中,是不可以給this賦值的,因為,還是那句話,this不是變量。
在global context(全局上下文)中,this的值就是指全局這個對象,這就意味著,this值就是這個變量本身。
var x = 10;console.log( x, // 10 this.x, // 10 window.x // 10);
在函數上下文[function context]中,this會可能會根據每次的函數調用而成為不同的值.this會由每一次caller提供,caller是通過調用表達式[call
expression]產生的(也就是這個函數如何被激活調用的)。例如,下面的例子中foo就是一個callee,在全局上下文中被激活。下面的例子就表明了不同的caller引起this的不同。
// the code of the "foo" function// never changes, but the "this" value// differs in every activationfunction foo() { alert(this); }// caller activates "foo" (callee) and// provides "this" for the calleefoo(); // global objectfoo.prototype.constructor(); // foo.prototypevar bar = { baz: foo};bar.baz(); // bar(bar.baz)(); // also bar(bar.baz = bar.baz)(); // but here is global object(bar.baz, bar.baz)(); // also global object(false || bar.baz)(); // also global objectvar otherFoo = bar.baz;otherFoo(); // again global object
要更深入的了解問什么每次函數調用的時候,this都會改變,請看Chapter 3. This。
感謝各位的閱讀,以上就是“JavaScript中的對象有哪些”的內容了,經過本文的學習后,相信大家對JavaScript中的對象有哪些這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。