引言javascript
最近比較忙致使這篇拖了很久啊,第二篇的做用域和閉包由於其中一部分沒搞得很清楚也很難受,決定不和本身鑽牛角尖了,本篇最後的面試題部分會包含一部分閉包的知識點以彌補上篇沒講清和講的不夠詳細的知識點。java
本篇對標犀牛書第6大章和第9大章git
爲何叫大Object,事實上JS將它單獨做爲一個基本數據類型應該就足以稱之爲大了,也夠複雜。撰寫本篇的初心仍是想搞清楚原型和原型鏈因此放在最前面,只是在看的過程當中發現和相關的知識點很成體系以及也比較重要,因此都總結記錄了一下,能夠做爲補充看。github
_proto_
、prototype
和constructor
屬性每一個JS對象(null
除外)都自動擁有一個_proto_
屬性,這個屬性是一個對象,指向該對象的原型面試
每一個JS函數(bind()
方法除外)都自動擁有一個prototype
屬性,這個屬性也是一個對象,用該函數作構造函數建立的對象將繼承這個prototype
的屬性,也就是說理論上任何一個JS函數均可以用做構造函數,而且調用構造函數須要用到prototype
屬性。瀏覽器
prototype
屬性包含一個惟一不可枚舉的屬性constructor
,這個屬性是一個函數對象,指向該函數的構造函數。閉包
看完上述兩點你可能會認爲_proto_
是否是就是對象的原型,prototype
是否是就是函數的原型呢? 事實上並非,但咱們能夠說對象的_proto_
屬性指向它的原型,構造函數的prototype
屬性指向調用構造函數建立的實例的原型。函數
這麼說有點繞,用圖片(來源:github.com/mqyqingfeng… 侵刪)表示即爲:ui
綜上所述,若是咱們有一段代碼:this
function Person(name, age){
this.name = name;
this.age = age;
}
let person = new Person('xiao hong', 18);
複製代碼
能夠獲得:
person._proto_ === Person.prototype;
Person.prototype.constructor === Person;
複製代碼
咱們知道,當執行屬性訪問表達式時,首先會將表達式的操做主題轉化爲對象,而後去對象中查找屬性,若是找不到就去找與對象的原型中的屬性,若是還找不到,就繼續查找原型的原型,直到找到最頂層爲止。
那麼原型的原型是什麼?咱們知道,_proto_
和prototype
屬性也只是普通的對象而已,既然是對象,就也有_proto_
屬性,一個普通對象的_proto_
屬性天然指向其構造函數Obejct的prototype
屬性,即Object.prototype
那Object.prototype
的原型呢?咱們能夠打印一下:
console.log(Object.prototype._proto_) // null
複製代碼
因此,當咱們向上查找屬性的時候,查到Object.prototype
就能夠中止了,由這些對象相互關聯的原型之間的關係就是原型鏈。到這裏,咱們的圖片來源:github.com/mqyqingfeng… 侵刪)也能夠更新爲:
綜上所述,若是咱們有一段代碼:
function Person(name, age){
this.name = name;
this.age = age;
}
Person.prototype = {
getName: function(){return this.name}
}
let person = new Person('xiao hong', 18);
person.toString();
複製代碼
咱們在person上找不到toString方法,就會去person的原型上找,person._proto_ === Person.prototype
,結果Person.prototype
上也沒有這個方法,就會再去原型的原型上找即Person.prototype._proto_
, 要知道Person.prototype._proto_
只是一個普通的對象,能夠被原始的new Object()
建立,因此Person.prototype._proto === Object.prototype
,所幸Object.prototype
上有toString()
方法,所以調用它,查找到此結束。
constructor
屬性不是每一個對象都有constructor
屬性,所以不是每一個對象均可以用做構造函數的prototype
屬性對象,但能夠顯示的定義constructor
屬性反向引用構造函數來修正這個問題。
constructor
屬性也是普通的對象屬性,若是找不到改屬性,也會從對象原型上繼續尋找。以下:
function Person() {
}
var person = new Person();
console.log(person.constructor === Person); // true
複製代碼
當獲取 person.constructor
時,其實 person
中並無 constructor
屬性,當不能讀取到constructor
屬性時,會從 person
的原型也就是 Person.prototype
中讀取,正好原型中有該屬性,因此:
person.constructor === Person.prototype.constructor
複製代碼
_proto_
其次是 __proto__
,絕大部分瀏覽器都支持這個非標準的方法訪問原型,然而它並不存在於Person.prototype
中,實際上,它是來自於 Object.prototype
,與其說是一個屬性,不如說是一個 getter/setter
,當使用obj.__proto
__ 時,能夠理解成返回了 Object.getPrototypeOf(obj)
。
Function._proto_ === Function.prototype
這裏並非由於Function
是對象,有_proto_
屬性,Function
又是函數,函數對象的原型指向其構造函數Function.prototype
,這樣理解雖然看上去很正確可是是不對的。引用大佬的話:
Function.prototype是引擎創造出來的對象,一開始就有了,又由於其餘的構造函數均可以經過原型鏈找到Function.prototype,Function自己也是一個構造函數,爲了避免產生混亂,就將這兩個聯繫到一塊兒了
Object.__proto__ === Function.prototype
Object
是對象的構造函數,那麼它也是一個函數,固然它的__proto__
也是指向Function.prototype
實際上原型原型鏈就是這樣,但若是你以爲上述仍是很難理解,最好再理解一系列的概念,如下內容能夠做爲做爲對原型和繼承的補充理解:
對象的三種建立方法:
例如let o = {}
,對象直接量是一個表達式,這個表達式的每次運算都會建立並初始化一個新的對象,也就是說在一個函數中使用對象直接量會函數重複調用時建立不少新對象
例如let o = new Object()
new
關鍵字作了什麼呢,根據MDN的介紹![image-20191026185858754](/Users/chenxingjian/Library/Application Support/typora-user-images/image-20191026185858754.png)其中第2點即,設置建立的新對象的原型與構造函數的prototype
屬性相關聯。
注意:new
關鍵字也不是必定建立一個新對象的,例如:new Object({})
, 根據ES5規範,若是new Object(value)
中檢測到Value
的類型爲object
就直接返回該對象而不會建立一個新對象。
優先級 | 運算類型 | 關聯性 | 例子 |
---|---|---|---|
20 | 圓括號 | n/a | (a + b) * c |
19 | 成員訪問 | 從左到右 | object.method |
19 | 須要計算的成員訪問 | 從左到右 | object[「a」+」b」] |
19 | new 帶參數列表 | n/a | new fun() |
19 | 函數調用 | 從左到右 | fun() |
18 | new 無參數列表 | 從右到左 | new fun |
思考:new Foo().getName()
和new Foo.getName()
兩個表達式中的運算優先級
咱們知道,構造函數沒有參數列表的時候是能夠省略括號的 也就是 new Foo()
等同於 new Foo
,根據上表,優先級越高的先執行:
new Foo().getName()
表達式中,new 帶參數列表優先級高於Foo()函數調用表達式,先執行new Foo()
,即表達式等同於(new Foo()).getName()
new Foo.getName()
表達式中,成員訪問表達式優先級高於new
無帶參列表,即表達式等同於new (Foo.getName())
該函數接受兩個參數,而且返回一個新建立的對象,而且將第一個參數做爲新建立的對象的原型,甚至能夠傳入null
來建立一個沒有原型的空對象,這樣建立的空對象將不繼承任何基礎方法,好比toString
,這意味着這樣建立的對象將沒法和+
一塊兒正常工做。
對象的三個屬性 :原型屬性、類屬性、可擴展性。
對象有自有屬性和繼承屬性,其中原型屬性就是做爲繼承屬性來使用的。經過new
建立的對象用構造函數的prototype
屬性做爲對象的原型,經過Object.create()
建立的對象使用第一個參數做爲建立對象的原型,沒有原型的對象爲數很少,其中包括Object.prototype
和Object.create(null)
。
能夠用a.isPrototypeOf(b)
方法判斷a
是不是b
原型,即b
是否繼承自a
.
能夠用Object.getPrototypeOf(a)
來獲取a
對象的原型,若a
不是對象類型則拋出類型錯誤。
一般和構造函數的名稱保持一致,經過內置構造函數建立的對象有類名,而且能夠經過相似Object.prototype.toString.call(new Date())
的方法來得到類名,而自定義的對象沒有類名,由於類屬性必定爲「Object
」。
宿主對象的可擴展性由js引擎決定(任何對象,不是原生對象就是宿主對象),ES5中,全部內置對象和自定義對象都是可擴展的。除非將其轉換爲不可擴展的。
可使用Object.isExtensible()
來檢測對象是否可擴展,使用Object.preventExtensions()
來將對象轉換爲不可擴展的。一旦將對象轉換爲不可擴展的就沒法再轉換爲可擴展的。不可擴展只對於對象的自有屬性,若是對象的原型擴展了方法那麼該對象將仍然繼承該方法。
若是一個屬性同時具備getter/setter
方法,則該屬性具備讀/寫性,若是隻有getter
方法,則是隻讀屬性,若是隻有setter
方法,則是隻寫屬性。
存取器屬性是可繼承的,使用方法以下實例:
var o = {
x: 1,
get y(){return this.x},
set y(value){this.x = value}
}; //{x: 1, y: 1}
o.y = 2; //{x: 2, y: 2}
var o = {x: 1, get y(){return this.y}}
o.y //RangeError: Maximum call stack size exceeded 讀取器裏讀取它本身無限回調
var o = {x: 1, set y(value){return value}}
o.y = 3;
o.y // undefined 讀取只寫屬性永遠返回undefined
var o = {x: 1, set y(){return value}} //Setter must have exactly one formal parameter
複製代碼
由上述可知,getter/setter
存取器屬性與屬性的可讀可寫性密切相關,因此可視爲屬性的特性。
普通屬性的四個特性:值、可寫性、可枚舉型、可配置性。分別對應{value, writable, enumerable, configurable}
讀取器屬性的四個特性:讀取、寫入、可枚舉性、可配置型。分別對應{get, set, enmurable, configurable }
能夠經過Object.getOwnPropertyDescriptor({x: 1}, x)
查看對象特定自有屬性特性。若是要查看繼承屬性,須要遍歷原型鏈Object.getPrototypeOf()
能夠經過Object.definedProperty(o, "x", {writable: false})
新建或修改某對象特定自有屬性的特性。對於新建的屬性來講,第三個參數中不存在的特性將被描述爲false
或undefined
,對於修改的屬性來講,第三個參數中不存在的特性將不會被修改。
能夠經過Object.seal()
將對象設置爲封閉的,即不可擴展的以及將對象屬性設置爲不可配置的,相對於Object.preventExtensions()
方法,Object.seal()
方法處理的對象將不能刪除和配置已有屬性,但可寫屬性依然能夠修改。封閉的對象將不能解封,能夠用Object.isSealed()
方法檢測對象是不是封閉的
能夠經過Object.freeze()
方法將對象凍結,即不可擴展的以及將對象屬性設置爲不可配置的還將全部數據屬性設置爲只讀的(對setter
屬性無效),可使用Object.isFrozen()
檢測對象是否凍結。
function Foo() {
getName = function() {
console.log(1);
}
return this;
}
Foo.getName = function() {
console.log(2);
}
Foo.prototype.getName = function() {
console.log(3);
}
var getName = function() {
console.log(4);
}
function getName() {
console.log(5);
}
Foo.getName();
getName();
Foo().getName();
getName();
new Foo.getName();
new Foo().getName();
new new Foo().getName();
複製代碼
var A = function() {};
A.prototype.n = 1;
var b = new A();
A.prototype = {
n: 2,
m: 3
}
var c = new A();
console.log(b.n);
console.log(b.m);
console.log(c.n);
console.log(c.m);
複製代碼
var F = function() {};
Object.prototype.a = function() {
console.log('a');
};
Function.prototype.b = function() {
console.log('b');
}
var f = new F();
f.a();
f.b();
F.a();
F.b();
複製代碼
function Person(name) {
this.name = name
}
let p = new Person('Tom');
//問題1:1. p.__proto__等於什麼?
//問題2:Person.__proto__等於什麼?
複製代碼
var foo = {},
F = function(){};
Object.prototype.a = 'value a';
Function.prototype.b = 'value b';
console.log(foo.a);
console.log(foo.b);
console.log(F.a);
console.log(F.b);
複製代碼
本題考察的知識點不少,包括函數聲明提早,原型鏈,執行上下文this,所以放在了本篇。
Foo.getName() Foo對象上有getName屬性,直接調用執行輸出2
getName() 這裏考察聲明提早, 函數聲明提早但函數定義表達式不提早,所以這一段實際被編譯爲:
var getName;
function getName(){
console.log(5);
}
getName = function(){
console.log(4);
}
getName();
複製代碼
所以輸出4
Foo().getName() F()給一個未聲明的變量getName賦值了一個函數,實際建立了一個同名的全局對象屬性getName並賦值爲function(){console.log(1)},又由於函數是普通調用,沒有綁定在對象上或實例上,返回的this即window,調用window.getName() 輸出1
Foo()建立的全局getName屬性覆蓋了定義的函數聲明,輸出1
new Foo.getName() 注意運算優先級 先計算Foo.getName() 輸出 2
new Foo().getName() 注意運算優先級,先執行new Foo(),new Foo()建立一個新對象,對象關聯到 Foo.prototype,返回以新建立的對象爲上下文的this,所以調用Foo.prototype.getName() 輸出3
new new Foo().getName() 執行順序new ( (new Foo()).getName()) 同上輸出3
本題考察了原型鏈。
var A = function() {};
A.prototype = {constructor: function(){}};
A.prototype = {constructor: function(){}, n: 1};
var b = new A();
b._proto_ = A.prototype;
b._proto_ = {constructor: function(){}, n: 1};
A.prototype = {
n: 2,
m: 3
};
var c = new A();
c._proto_ = A.prototype = {
n: 2,
m: 3
};
b.n = 1; b.m = undefined;
c.n = 2; c.m = 3;
複製代碼
考察原型鏈
F.prototype = {contructor: function(){}};
Object.prototype.a = function() {
console.log('a');
};
Function.prototype.b = function() {
console.log('b');
}
f._proto_ = F.prototype;
f.a => f._proto_.a => F.prototype.a => F.prototype._proto_.a => Object.prototype.a
f.a()//'a'
f.b => f._proto_.b => F.prototype.b => F.prototype._proto_.b =>
Object.prototype.b
f.b()// TypeError undefined is not a function
F.a => F._proto_.a => Function.prototype.a => Function.prototype._proto_.a => Object.prototype.a
F.a() // 'a'
F.b => F._proto_.b => Function.prototype.b
F.b() //'b'
複製代碼
(1) p._proto_ = Person.prototype;
(2)Person._proto_ = Function.prototype;
以下:
foo.a => foo._proto_.a => Object.prototype.a => 'value a'
foo.b => foo._proto_.b => Object.prototype.b => undefined
F.a => Foo._proto_.a => Function.prototype.a => Function.prototype._proto_.a => Object.prototype.a => 'value a'
F.b => Foo._proto_.b => Function.prototype.b => 'value b'
複製代碼