構造函數->原型
每一個函數都有一個 prototype 屬性,指向實例的原型
原型:每個JavaScript對象(null除外)在建立的時候就會與之關聯另外一個對象,這個對象就是咱們所說的原型
實例->原型
每個JavaScript對象(除了 null )都具備的一個屬性,叫__proto__,這個屬性會指向該對象的原型
ES5的方法,能夠得到對象的原型
(Object.getPrototypeOf(person) === Person.prototype person是實例,Person是構造函數
當讀取實例的屬性時,若是找不到,就會查找與對象關聯的原型中的屬性,若是還查不到,就去找原型的原型,一直找到最頂層爲止。
原型->構造函數
每一個原型都有一個 constructor 屬性指向關聯的構造函數
實例-> 構造函數
person.constructor === Person 從原型上面繼承
Null表明什麼
null 表示「沒有對象」,即該處不該該有值。前端
obj.__proto__ :非標準的方法訪問原型,並不存在於 Person.prototype 中,並非原型上的屬性,它是來自於 Object.prototype,返回了Object.getPrototypeOf(obj)
繼承:
繼承意味着複製操做, 默認並不會複製對象的屬性,在兩個對象之間建立一個關聯,經過委託訪問另外一個對象的屬性和函數,因此與其叫繼承,委託的說法反而更準確些。chrome
做用域:定義變量的區域,肯定當前執行代碼對變量的訪問權限
Js: 詞法做用域(lexical scoping),也就是靜態做用域,函數的做用域基於函數建立的位置
靜態做用域:做用域在函數定義
動態做用域:做用域是在函數調用數組
var scope = "global scope"; function checkscope(){ var scope = "local scope"; function f(){ return scope; } return f(); } checkscope(); var scope = "global scope"; function checkscope(){ var scope = "local scope"; function f(){ return scope; } return f; } checkscope()(); 思考:結果同樣,有什麼不一樣
當執行一段代碼的時候,會進行一個「準備工做
這個「一段一段」中的「段」到底是怎麼劃分的呢
Js引擎遇到一段怎樣的代碼時纔會作「準備工做」呢?
執行到一個函數的時候,就會進行準備工做,就叫作"執行上下文(execution context)"函數
Js 的可執行代碼(executable code)的類型
全局代碼、函數代碼、eval代碼this
執行上下文棧(Execution context stack,ECS
管理建立執行上下文 ECStack = []
Js解釋執行代碼,最早遇到全局代碼,so初始化首先就會向執行上下文棧壓入一個全局執行上下文, globalContext ,只有當整個應用程序結束的時候,ECStack 纔會被清空,因此程序結束以前ECStack 最底部永遠有個 globalContext
當執行一個函數的時候,就會建立一個執行上下文,而且壓入執行上下文棧,當函數執行完畢的時候,就會將函數的執行上下文從棧中彈出spa
函數執行結束以後,若是沒有顯示地返回值,默認是undefined,chrome中會把函數執行的結果打印出來prototype
執行上下文,都有三個重要屬性code
變量對象:數據做用域,存儲了在上下文中定義的變量和函數聲明對象
不一樣上下文變量對象不一樣:全局上下文下的變量對象和函數上下文下的變量對象blog
全局上下文中的變量對象:全局對象
全局對象:
預約義的對象 訪問全部其餘全部預約義的對象、函數和屬性 頂層 JavaScript 代碼中,能夠用關鍵字 this 引用全局對象, 全局對象是做用域鏈的頭 全局對象是由 Object 構造函數實例化的一個對象
全局上下文中的變量對象:活動對象(activation object, AO)
活動對象和變量對象實際上是一個東西
它們其實都是同一個對象,只是處於執行上下文的不一樣生命週期
變量對象:規範上的實現,不可在 JavaScript 環境中訪問
活動對象:只有到當進入一個執行上下文中,這個執行上下文的變量對象纔會被激活而只有被激活的變量對象也就是活動對象上的各類屬性才能被訪問
活動對象是在進入函數上下文時刻被建立的,它經過函數的 arguments 屬性初始化。arguments 屬性值是 Arguments 對象
JS 引擎在分析代碼的時候,分爲兩個階段:編譯階段和執行階段
當進入執行上下文時,這時候尚未執行代碼,
變量對象會包括:
函數的全部形參 (若是是函數上下文)
函數聲明
變量聲明
在代碼執行階段,會順序執行代碼,根據代碼,修改變量對象的值
補充:
Arguments對象 - 調用函數時,會爲其建立一個Arguments對象,並自動初始化局部變量arguments,指代該Arguments對象。全部做爲參數傳入的值都會成爲Arguments對象的數組元素
執行上下文的生命週期
在這個階段中,執行上下文會分別建立變量對象,創建做用域鏈,以及肯定this的指向。
建立完成以後,就會開始執行代碼,這個時候,會完成變量賦值,函數引用,以及執行其餘代碼
AO 其實是包含了 VO 的
也就是說 AO 的確是在進入到執行階段的時候被激活,可是激活的除了 VO 以外,還包括函數執行時傳入的參數和 arguments 這個特殊對象。
AO = VO + function parameters + arguments
在進入執行上下文時,首先會處理函數聲明,其次會處理變量聲明,若是變量名稱跟已經聲明的形式參數或函數相同,則變量聲明不會干擾已經存在的這類屬性。
在進入執行上下文階段,只會將有 `var,function修飾的變量或方法添加到變量對象中。在進行執行階段前一刻,foo和bar方法的它們的VO中均沒有a屬性。在執行階段,執行到 a= 1時,纔將a變量添加到全局的變量對象中而不是在進入執行上下文階段。因此foo方法中會報錯,bar方法會打印 1。
function foo() { console.log(a); a = 1; } foo(); // Uncaught ReferenceError: a is not defined function bar() { a = 1; console.log(a); } bar(); // 1 WT: let/const 在esc中的表現
當查找變量的時候,會先從當前上下文的變量對象中查找,若是沒有找到,就會從父級(詞法層面上的父級)執行上下文的變量對象中查找,一直找到全局上下文的變量對象,也就是全局對象。這樣由多個執行上下文的變量對象構成的鏈表就叫作做用域鏈
函數建立:
函數的做用域在函數定義的時候就決定了
函數有一個內部屬性 [[scope]],當函數建立的時候,就會保存全部父變量對象到其中
[[scope]] 就是全部父變量對象的層級鏈([[scope]] 並不表明完整的做用域鏈!)
function foo() { function bar() { ... } } 建立時 foo.[[scope]] = [ globalContext.VO ]; bar.[[scope]] = [ fooContext.AO, globalContext.VO ];
函數激活:
當函數激活時,進入函數上下文,建立 VO/AO 後,將活動對象添加到做用鏈的前端
這時候執行上下文的做用域鏈,咱們命名爲 Scope:
Scope = [AO].concat([[Scope]]);
e.g.:
var scope = "global scope"; function checkscope(){ var scope2 = 'local scope'; return scope2; } checkscope();
執行過程以下:
1.checkscope 函數被建立,保存做用域鏈到 內部屬性[[scope]]
checkscope.[[scope]] = [ globalContext.VO ];
2.執行 checkscope 函數,建立 checkscope 函數執行上下文,checkscope 函數執行上下文被壓入執行上下文棧
ECStack = [ checkscopeContext, globalContext ];
3.checkscope 函數並不馬上執行,開始作準備工做,第一步:複製函數[[scope]]屬性建立做用域鏈
checkscopeContext = { Scope: checkscope.[[scope]], }
4.第二步:用 arguments 建立活動對象,隨後初始化活動對象,加入形參、函數聲明、變量聲明
checkscopeContext = { AO: { arguments: { length: 0 }, scope2: undefined }, Scope: checkscope.[[scope]], }
5.第三步:將活動對象壓入 checkscope 做用域鏈頂端
checkscopeContext = { AO: { arguments: { length: 0 }, scope2: undefined }, Scope: [AO, [[Scope]]] }
6.準備工做作完,開始執行函數,隨着函數的執行,修改 AO 的屬性值
checkscopeContext = { AO: { arguments: { length: 0 }, scope2: 'local scope' }, Scope: [AO, [[Scope]]] }
7.查找到 scope2 的值,返回後函數執行完畢,函數上下文從執行上下文棧中彈出
ECStack = [ globalContext ];
補充:
每個函數都有本身的執行環境,函數執行的時候,會建立函數執行上下文
在源代碼中當你定義(書寫)一個函數的時候(並未調用),js引擎也能根據你函數書寫的位置,函數嵌套的位置,給你生成一個[[scope]],做爲該函數的屬性存在(這個屬性屬於函數的)。即便函數不調用,因此說基於詞法做用域(靜態做用域)。而後進入函數執行階段,生成執行上下文,執行上下文你能夠宏觀的當作一個對象,(包含vo,scope,this),此時,執行上下文裏的scope和以前屬於函數的那個[[scope]]不是同一個,執行上下文裏的scope,是在以前函數的[[scope]]的基礎上,又新增一個當前的AO對象構成的。函數定義時候的[[scope]]和函數執行時候的scope,前者做爲函數的屬性,後者做爲函數執行上下文的屬性。