做者:valentinogagliardihtml
譯者:前端小智前端
來源:githubgit
阿里雲最近在作活動,低至2折,有興趣能夠看看: promotion.aliyun.com/ntms/yunpar…github
爲了保證的可讀性,本文采用意譯而非直譯。編程
我們常常聽到JS中「一切皆對象」? 有沒有問想過這是什麼意思? 其它語言也有「一切皆對象」之說,如Python
。 可是Python
中的對象不只僅是像JS對象這樣的存放值和值的容器。 Python
中的對象是一個類。 JS中有相似的東西,但JS中的「對象」只是鍵和值的容器:json
var obj = { name: "Tom", age: 34 }
複製代碼
實際上,JS中的對象是一種「啞」類型,但不少其餘實體彷佛都是從對象派生出來的。 甚至是數組,在JS中建立一個數組,以下所示:數組
var arr = [1,2,3,4,5]
複製代碼
而後用typeof
運算符檢查類型,會看到一個使人驚訝的結果:瀏覽器
typeof arr
"object"
複製代碼
看來數組是一種特殊的對象! 即便JS中的函數也是對象。 若是你深刻挖掘,還有更多,建立一個函數,該函數就會附加一些方法:微信
var a = function(){ return false; }
a.toString()
複製代碼
輸出:編程語言
"function(){ return false; }"
複製代碼
我們並無在函數聲明toString
方法,因此在底層必定還有東西。它從何而來? Object
有一個名爲.toString
的方法。 彷佛我們的函數具備相同的Object
方法。
Object.toString()
複製代碼
這時我們使用瀏覽器控制檯來查看默認被附加的函數和屬性,這個謎團就會變得更加複雜:
誰把這些方法放在函數呢。 JS中的函數是一種特殊的對象,這會不會是個暗示? 再看看上面的圖片:咱們的函數中有一個名爲prototype
的奇怪命名屬性,這又是什麼鬼?
JS中的prototype
是一個對象。 它就像一個揹包,附着在大多數JS內置對象上。 例如 Object
, Function
, Array
, Date
, Error
,都有一個「prototype
」:
typeof Object.prototype // 'object'
typeof Date.prototype // 'object'
typeof String.prototype // 'object'
typeof Number.prototype // 'object'
typeof Array.prototype // 'object'
typeof Error.prototype // 'object'
複製代碼
注意內置對象有大寫字母:
如下除了Object
是類型以外,其它是JS的基本類型。另外一方面,內置對象就像JS類型的鏡像,也用做函數。例如,可使用String
做爲函數將數字轉換爲字符串:
String(34)
複製代碼
如今回到「prototype
」。prototype
是全部公共方法和屬性的宿主,從祖先派生的「子」對象能夠從使用祖先的方法和屬性。也就是說,給定一個原始 prototype
,我們能夠建立新的對象,這些對象將使用一個原型做爲公共函數的真實源,不 Look see see。
假設有個要求建立一個聊天應用程序,有我的物對象。這我的物能夠發送消息,登陸時,會收到一個問候。
根據需求我們很容易定義這個麼一 Person
對象:
var Person = {
name: "noname",
age: 0,
greet: function() {
console.log(`Hello ${this.name}`);
}
};
複製代碼
你可能會想知道,爲何這裏要使用字面量的方式來聲明 Person
對象。 稍後會詳細說明,如今該 Person
爲「模型」
。經過這個模型,我們使用 Object.create()
來建立覺得這個模型爲基礎的對象。
JS中對象彷佛以某種方式連接在一塊兒,Object.create()
說明了這一點,此方法從原始對象開始建立新對象,再來建立一個新Person
對象:
var Person = {
name: "noname",
age: 0,
greet: function() {
console.log(`Hello ${this.name}`);
}
};
var Tom = Object.create(Person);
複製代碼
如今,Tom 是一個新的對象,可是我們沒有指定任何新的方法或屬性,但它仍然能夠訪問Person
中的name
和age
屬性。
var Person = {
name: "noname",
age: 0,
greet: function() {
console.log(`Hello ${this.name}`);
}
};
var Tom = Object.create(Person);
var tomAge = Tom.age;
var tomName = Tom.name;
console.log(`${tomAge} ${tomName}`);
// Output: 0 noname
複製代碼
如今,能夠從一個共同的祖先開始建立新的person。但奇怪的是,新對象仍然與原始對象保持鏈接,這不是一個大問題,由於「子」對象能夠自定義屬性和方法
var Person = {
name: "noname",
age: 0,
greet: function() {
console.log(`Hello ${this.name}`);
}
};
var Tom = Object.create(Person);
Tom.age = 34;
Tom.name = "Tom";
var tomAge = Tom.age;
var tomName = Tom.name;
console.log(`${tomAge} ${tomName}`);
// Output: 34 Tom
複製代碼
這種方式被稱爲「屏蔽」原始屬性。 還有另外一種將屬性傳遞給新對象的方法。 Object.create
將另外一個對象做爲第二個參數,能夠在其中爲新對象指定鍵和值:
var Tom = Object.create(Person, {
age: {
value: 34
},
name: {
value: "Tom"
}
});
複製代碼
以這種方式配置的屬性默認狀況下不可寫,不可枚舉,不可配置。 不可寫意味着以後沒法更改該屬性,更改會被忽略:
var Tom = Object.create(Person, {
age: {
value: 34
},
name: {
value: "Tom"
}
});
Tom.age = 80;
Tom.name = "evilchange";
var tomAge = Tom.age;
var tomName = Tom.name;
Tom.greet();
console.log(`${tomAge} ${tomName}`);
// Hello Tom
// 34 Tom
複製代碼
不可枚舉意味着屬性不會在 for...in
循環中顯示,例如:
for (const key in Tom) {
console.log(key);
}
// Output: greet
複製代碼
可是正如我們所看到的,因爲JS引擎沿着原型鏈向上查找,在「父」對象上找到greet
屬性。最後,不可配置意味着屬性既不能修改也不能刪除。
Tom.age = 80;
Tom.name = "evilchange";
delete Tom.name;
var tomAge = Tom.age;
var tomName = Tom.name;
console.log(`${tomAge} ${tomName}`);
// 34 Tom
複製代碼
若是要更改屬性的行爲,只需配writable
(可寫性),configurable
(可配置),enumerable
(可枚舉)屬性便可。
var Tom = Object.create(Person, {
age: {
value: 34,
enumerable: true,
writable: true,
configurable: true
},
name: {
value: "Tom",
enumerable: true,
writable: true,
configurable: true
}
});
複製代碼
如今,Tom
也能夠經過如下方式訪問greet()
:
var Person = {
name: "noname",
age: 0,
greet: function() {
console.log(`Hello ${this.name}`);
}
};
var Tom = Object.create(Person);
Tom.age = 34;
Tom.name = "Tom";
var tomAge = Tom.age;
var tomName = Tom.name;
Tom.greet();
console.log(`${tomAge} ${tomName}`);
// Hello Tom
// 34 Tom
複製代碼
暫時不要過於擔憂「this
」。 拉下來會詳細介紹。暫且先記住,「this」是對函數執行的某個對象的引用。在我們的例子中,greet()
在Tom
的上下文中運行,所以能夠訪問「this.name
」。
目前爲止,只介紹了關於「prototype」的一點知識 ,還有玩了一會 Object.create()
以外但我們沒有直接使用它。 隨着時間的推移出現了一個新的模式:構造函數
。 使用函數建立新對象聽起來很合理, 假設你想將Person
對象轉換爲函數,你能夠用如下方式:
function Person(name, age) {
var newPerson = {};
newPerson.age = age;
newPerson.name = name;
newPerson.greet = function() {
console.log("Hello " + newPerson.name);
};
return newPerson;
}
複製代碼
所以,不須要處處調用object.create()
,只需將Person
做爲函數調用:
var me = Person("Valentino");
複製代碼
構造函數模式有助於封裝一系列JS對象的建立和配置。 在這裏, 我們使用字面量的方式建立對象。 這是一種從面嚮對象語言借用的約定,其中類名開頭要大寫。
上面的例子有一個嚴重的問題:每次我們建立一個新對象時,一遍又一遍地重複建立greet()
函數。可使用Object.create()
,它會在對象之間建立連接,建立次數只有一次。 首先,我們將greet()
方法移到外面的一個對象上。 而後,可使用Object.create()
將新對象連接到該公共對象:
var personMethods = {
greet: function() {
console.log("Hello " + this.name);
}
};
function Person(name, age) {
// greet lives outside now
var newPerson = Object.create(personMethods);
newPerson.age = age;
newPerson.name = name;
return newPerson;
}
var me = Person("Valentino");
me.greet();
// Output: "Hello Valentino"
複製代碼
這種方式比剛開始會點,還能夠進一步優化就是使用prototype
,prototype
是一個對象,能夠在上面擴展屬性,方法等等。
Person.prototype.greet = function() {
console.log("Hello " + this.name);
};
複製代碼
移除了personMethods
。 調整Object.create
的參數,不然新對象不會自動連接到共同的祖先:
function Person(name, age) {
// greet lives outside now
var newPerson = Object.create(Person.prototype);
newPerson.age = age;
newPerson.name = name;
return newPerson;
}
Person.prototype.greet = function() {
console.log("Hello " + this.name);
};
var me = Person("Valentino");
me.greet();
// Output: "Hello Valentino"
複製代碼
如今公共方法的來源是Person.prototype
。 使用JS中的new
運算符,能夠消除Person
中的全部噪聲,而且只須要爲this
分配參數。
下面代碼:
function Person(name, age) {
// greet lives outside now
var newPerson = Object.create(Person.prototype);
newPerson.age = age;
newPerson.name = name;
return newPerson;
}
複製代碼
改爲:
function Person(name, age) {
this.name = name;
this.age = age;
}
複製代碼
完整代碼:
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.greet = function() {
console.log("Hello " + this.name);
};
var me = new Person("Valentino");
me.greet();
// Output: "Hello Valentino"
複製代碼
注意,使用new
關鍵字,被稱爲「構造函數調用」
,new
幹了三件事情
建立一個空對象
將空對象的__proto__
指向構造函數的prototype
使用空對象做爲上下文的調用構造函數
function Person(name, age) { this.name = name; this.age = age; }
根據上面描述的,new Person("Valentino")
作了:
var obj = {}
__proto__
指向構造函數的 prototype:obj.__proto__ = Person().prototype
Person.call(obj)
檢查JS對象之間的原型連接有不少種方法。 例如,Object.getPrototypeOf
是一個返回任何給定對象原型的方法。 考慮如下代碼:
var Person = {
name: "noname",
age: 0,
greet: function() {
console.log(`Hello ${this.name}`);
}
};
var Tom = Object.create(Person);
複製代碼
檢查Person
是不是Tom
的原型:
var tomPrototype = Object.getPrototypeOf(Tom);
console.log(tomPrototype === Person);
// Output: true
複製代碼
固然,若是使用構造函數調用構造對象,Object.getPrototypeOf
也能夠工做。 可是應該檢查原型對象,而不是構造函數自己:
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.greet = function() {
console.log("Hello " + this.name);
};
var me = new Person("Valentino");
var mePrototype = Object.getPrototypeOf(me);
console.log(mePrototype === Person.prototype);
// Output: true
複製代碼
除了Object.getPrototypeOf
以外,還有另外一個方法isPrototypeOf
。 該方法用於測試一個對象是否存在於另外一個對象的原型鏈上,以下所示,檢查 me
是否在 Person.prototype
上:
Person.prototype.isPrototypeOf(me) && console.log('Yes I am!')
複製代碼
instanceof
運算符也能夠用於測試構造函數的prototype
屬性是否出如今對象的原型鏈中的任何位置。 老實說,這個名字有點誤導,由於JS中沒有「實例」。 在真正的面嚮對象語言中,實例是從類建立的新對象。 請考慮Python中的示例。 我們有一個名爲Person
的類,我們從該類建立一個名爲「tom」的新實例:
class Person():
def __init__(self, age, name):
self.age = age;
self.name = name;
def __str__(self):
return f'{self.name}'
tom = Person(34, 'Tom')
複製代碼
注意,在Python中沒有new
關鍵字。如今,我們可使用isinstance
方法檢查tom
是不是Person
的實例
isinstance(tom, Person)
// Output: True
複製代碼
Tom
也是Python
中「object
」的一個實例,下面的代碼也返回true
:
isinstance(tom, object)
// Output: True
複製代碼
根據isinstance
文檔,「若是對象參數是類參數的實例,或者是它的(直接、間接或虛擬)子類的實例,則返回true
」。我們在這裏討論的是類。如今讓我們看看instanceof
作了什麼。我們將從JS中的Person
函數開始建立tom
(由於沒有真正的類)
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.greet = function() {
console.log(`Hello ${this.name}`);
};
var tom = new Person(34, "Tom");
複製代碼
使用isinstance
方法檢查tom
是不是Person
和 Object
的實例
if (tom instanceof Object) {
console.log("Yes I am!");
}
if (tom instanceof Person) {
console.log("Yes I am!");
}
複製代碼
所以,能夠得出結論:JS對象的原型老是鏈接到直接的「父對象」和Object.prototype
。沒有像Python
或Java
這樣的類。JS是由對象組成,那麼什麼是原型鏈呢?若是你注意的話,我們提到過幾回「原型鏈」。JS對象能夠訪問代碼中其餘地方定義的方法,這看起來很神奇。再次考慮下面的例子:
var Person = {
name: "noname",
age: 0,
greet: function() {
console.log(`Hello ${this.name}`);
}
};
var Tom = Object.create(Person);
Tom.greet();
複製代碼
即便該方法不直接存在於「Tom
」對象上,Tom
也能夠訪問greet()
。
這是JS的一個內在特徵,它從另外一種稱爲Self
的語言中借用了原型系統。 當訪問greet()
時,JS引擎會檢查該方法是否可直接在Tom
上使用。 若是不是,搜索將繼續向上連接,直到找到該方法。
「鏈」是Tom
鏈接的原型對象的層次結構。 在咱們的例子中,Tom
是Person
類型的對象,所以Tom
的原型鏈接到Person.prototype
。 而Person.prototype
是Object
類型的對象,所以共享相同的Object.prototype
原型。 若是在Person.prototype
上沒有greet()
,則搜索將繼續向上連接,直到到達Object.prototype
。 這就是我們所說的**「原型鏈」**。
大多數狀況下,JS 對象「可擴展」是必要的,這樣我們能夠向對象添加新屬性。 但有些狀況下,咱們但願對象不受進一步操縱。 考慮一個簡單的對象:
var superImportantObject = {
property1: "some string",
property2: "some other string"
};
複製代碼
默認狀況下,每一個人均可以向該對象添加新屬性
var superImportantObject = {
property1: "some string",
property2: "some other string"
};
superImportantObject.anotherProperty = "Hei!";
console.log(superImportantObject.anotherProperty); // Hei!
複製代碼
Object.preventExtensions()
方法讓一個對象變的不可擴展,也就是永遠不能再添加新的屬性。
var superImportantObject = {
property1: "some string",
property2: "some other string"
};
Object.preventExtensions(superImportantObject);
superImportantObject.anotherProperty = "Hei!";
console.log(superImportantObject.anotherProperty); // undefined
複製代碼
這種技術對於「保護」代碼中的關鍵對象很是方便。JS 中還有許多預先建立的對象,它們都是爲擴展而關閉的,從而阻止開發人員在這些對象上添加新屬性。這就是「重要」對象的狀況,好比XMLHttpRequest
的響應。瀏覽器供應商禁止在響應對象上添加新屬性
var request = new XMLHttpRequest();
request.open("GET", "https://jsonplaceholder.typicode.com/posts");
request.send();
request.onload = function() {
this.response.arbitraryProp = "我是新添加的屬性";
console.log(this.response.arbitraryProp); // undefined
};
複製代碼
這是經過在「response」對象上內部調用Object.preventExtensions
來完成的。 您還可使用Object.isExtensible
方法檢查對象是否受到保護。 若是對象是可擴展的,它將返回true
:
var superImportantObject = {
property1: "some string",
property2: "some other string"
};
Object.isExtensible(superImportantObject) && console.log("我是可擴展的");
複製代碼
若是對象不可擴展的,它將返回false
:
var superImportantObject = {
property1: "some string",
property2: "some other string"
};
Object.preventExtensions(superImportantObject);
Object.isExtensible(superImportantObject) ||
console.log("我是不可擴展的!");
複製代碼
固然,對象的現有屬性能夠更改甚至刪除
var superImportantObject = {
property1: "some string",
property2: "some other string"
};
Object.preventExtensions(superImportantObject);
delete superImportantObject.property1;
superImportantObject.property2 = "yeees";
console.log(superImportantObject); // { property2: 'yeees' }
複製代碼
如今,爲了防止這種操做,能夠將每一個屬性定義爲不可寫和不可配置。爲此,有一個方法叫Object.defineProperties
。
var superImportantObject = {};
Object.defineProperties(superImportantObject, {
property1: {
configurable: false,
writable: false,
enumerable: true,
value: "some string"
},
property2: {
configurable: false,
writable: false,
enumerable: true,
value: "some other string"
}
});
複製代碼
或者,更方便的是,能夠在原始對象上使用Object.freeze
:
var superImportantObject = {
property1: "some string",
property2: "some other string"
};
Object.freeze(superImportantObject);
複製代碼
Object.freeze
工做方式與Object.preventExtensions
相同,而且它使全部對象的屬性不可寫且不可配置。 惟一的缺點是「Object.freeze
」僅適用於對象的第一級:嵌套對象不受操做的影響。
有大量關於ES6 類的文章,因此在這裏只討論幾點。JS是一種真正的面嚮對象語言嗎?看起來是這樣的,若是我們看看這段代碼
class Person {
constructor(name) {
this.name = name;
}
greet() {
console.log(`Hello ${this.name}`);
}
}
複製代碼
語法與Python
等其餘編程語言中的類很是類似:
class Person:
def __init__(self, name):
self.name = name
def greet(self):
return 'Hello' + self.name
複製代碼
或 PHP
class Person {
public $name;
public function __construct($name){
$this->name = $name;
}
public function greet(){
echo 'Hello ' . $this->name;
}
}
複製代碼
ES6中引入了類。可是在這一點上,我們應該清楚JS中沒有「真正的
」類。 一切都只是一個對象,儘管有關鍵字class
,「原型系統」仍然存在。 新的JS版本是向後兼容的,這意味着在現有功能的基礎上添加了新功能,這些新功能中的大多數都是遺留代碼的語法糖。
JS中的幾乎全部東西都是一個對象。 從字面上看。 JS對象是鍵和值的容器,也可能包含函數。 Object
是JS中的基本構建塊:所以能夠從共同的祖先開始建立其餘自定義對象。 而後我們能夠經過語言的內在特徵將對象連接在一塊兒:原型系統。
從公共對象開始,能夠建立共享原始「父」的相同屬性和方法的其餘對象。 可是它的工做方式不是經過將方法和屬性複製到每一個孩子,就像OOP語言那樣。 在JS中,每一個派生對象都保持與父對象的鏈接。 使用Object.create
或使用所謂的構造函數建立新的自定義對象。 與new
關鍵字配對,構造函數相似於模仿傳統的OOP類。
new
在底層下作了哪些事嗎?代碼部署後可能存在的BUG無法實時知道,過後爲了解決這些BUG,花了大量的時間進行log 調試,這邊順便給你們推薦一個好用的BUG監控工具 Fundebug。
阿里雲最近在作活動,低至2折,有興趣能夠看看:promotion.aliyun.com/ntms/yunpar…
乾貨系列文章彙總以下,以爲不錯點個Star,歡迎 加羣 互相學習。
由於篇幅的限制,今天的分享只到這裏。若是你們想了解更多的內容的話,能夠去掃一掃每篇文章最下面的二維碼,而後關注我們的微信公衆號,瞭解更多的資訊和有價值的內容。
每次整理文章,通常都到2點才睡覺,一週4次左右,挺苦的,還望支持,給點鼓勵