[js] 说说你对js对象生命周期的理解
[js] 說說你對js對象生命周期的理解
一切皆對象
咱們經常聽到JS中“一切皆對象”?有沒有問想過這是什么意思?其它語言也有“一切皆對象”之說,如Python。但是Python中的對象不僅僅是像JS對象這樣的存放值和值的容器。Python中的對象是一個類。JS中有類似的東西,但JS中的“對象”只是鍵和值的容器:
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()
這時咱們使用瀏覽器控制臺來查看默認被附加的函數和屬性,這個謎團就會變得更加復雜:
640?wx_fmt=png
誰把這些方法放在函數呢。 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'注意內置對象有大寫字母:
StringNumberBooleanObjectSymbolNullUndefined以下除了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”。
構建JavaScript對象
目前為止,只介紹了關于“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 nowvar 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 nowvar 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 nowvar 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) {根據上面描述的,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: TrueTom也是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”僅適用于對象的第一級:嵌套對象不受操作的影響。
class
有大量關于ES6 類的文章,所以在這里只討論幾點。JS是一種真正的面向對象語言嗎?看起來是這樣的,如果咱們看看這段代碼
class Person {constructor(name) {this.name = name;}greet() {console.log(`Hello ${this.name}`);} }語法與Python等其他編程語言中的類非常相似:
class Person:def __init__(self, name):self.name = namedef 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類。
思考
代碼部署后可能存在的BUG沒法實時知道,事后為了解決這些BUG,花了大量的時間進行log 調試,這邊順便給大家推薦一個好用的BUG監控工具 Fundebug。
原文:https://github.com/valentinogagliardi/Little-JavaScript-Book/blob/v1.0.0/manuscript/chapter5.md
個人簡介
我是歌謠,歡迎和大家一起交流前后端知識。放棄很容易,
但堅持一定很酷。歡迎大家一起討論
主目錄
與歌謠一起通關前端面試題
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的[js] 说说你对js对象生命周期的理解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 手把手教你做一个Excel 2007数据
- 下一篇: [css] 一个页面引用多个文件,如何防