JavaScript 中的面向?qū)ο?/h1>
本文轉(zhuǎn)載自微信公眾號(hào)「勾勾的前端世界」,作者西嶺。轉(zhuǎn)載本文請(qǐng)聯(lián)系勾勾的前端世界公眾號(hào)。
回憶一下什么是對(duì)象:Coding 第一奧義:面向?qū)ο缶幊?/a>
JavaScript 語(yǔ)言本身的設(shè)計(jì)缺陷,誤打誤撞,成了解釋最為徹底的“世界原本的樣子”的計(jì)算機(jī)編程語(yǔ)言;
——西嶺《凡人凡語(yǔ)》
Everything is object (萬(wàn)物皆對(duì)象),JS 語(yǔ)言中將一切都視為 對(duì)象 。
JavaScript 語(yǔ)言的對(duì)象體系,不基于“類(lèi)” 創(chuàng)建對(duì)象,是基于構(gòu)造函數(shù)(constructor)和原型鏈(prototype)。
簡(jiǎn)單方式創(chuàng)建對(duì)象
我們可以直接通過(guò) new Object() 創(chuàng)建:
- var person = new Object()
- person.name = 'Jack'
- person.age = 18
- person.sayName = function () {
- console.log(this.name)
- }
字面量方式創(chuàng)建對(duì)象
每次創(chuàng)建通過(guò) new Object() 比較麻煩,所以可以通過(guò)它的簡(jiǎn)寫(xiě)形式對(duì)象字面量來(lái)創(chuàng)建:
- var person = {
- name: 'Jack',
- age: 18,
- sayName: function () {
- console.log(this.name)
- }
- }
構(gòu)造函數(shù)
JavaScript 語(yǔ)言使用構(gòu)造函數(shù)作為對(duì)象的模板。
所謂 "構(gòu)造函數(shù)",就是一個(gè)普通的函數(shù),只不過(guò)我們專(zhuān)門(mén)用它來(lái)生成對(duì)象,這樣使用的函數(shù),就是構(gòu)造函數(shù)。
它提供模板,描述對(duì)象的基本結(jié)構(gòu)。一個(gè)構(gòu)造函數(shù),可以生成多個(gè)對(duì)象,這些對(duì)象都有相同的結(jié)構(gòu)。
- function Person (name, age) {
- this.name = name
- this.age = age
- this.sayName = function () {
- console.log(this.name)
- }
- }
- var p1 = new Person('Jack', 18)
- p1.sayName() // => Jack
- var p2 = new Person('Mike', 23)
- p2.sayName() // => Mike
解析構(gòu)造函數(shù)代碼的執(zhí)行
在上面的示例中,使用 new 操作符創(chuàng)建 Person 實(shí)例對(duì)象;
以這種方式調(diào)用構(gòu)造函數(shù)會(huì)經(jīng)歷以下 5 個(gè)步驟:
- 創(chuàng)建一個(gè)空對(duì)象,作為將要返回的對(duì)象實(shí)例。
- 將這個(gè)空對(duì)象的原型,指向構(gòu)造函數(shù)的prototype屬性。先記住,后面講
- 將這個(gè)空對(duì)象賦值給函數(shù)內(nèi)部的this關(guān)鍵字。
- 執(zhí)行構(gòu)造函數(shù)內(nèi)部的代碼。
- 返回新對(duì)象 (this)
- function Person (name, age) {
- // 當(dāng)使用 new 操作符調(diào)用 Person() 的時(shí)候,實(shí)際上這里會(huì)先創(chuàng)建一個(gè)對(duì)象
- // 然后讓內(nèi)部的 this 指向新創(chuàng)建的對(duì)象
- // 接下來(lái)所有針對(duì) this 的操作實(shí)際上操作的就是剛創(chuàng)建的這個(gè)對(duì)象
- this.name = name
- this.age = age
- this.sayName = function () {
- console.log(this.name)
- }
- // 在函數(shù)的結(jié)尾處會(huì)將 this 返回,也就是這個(gè)新對(duì)象
- }
構(gòu)造函數(shù)和實(shí)例對(duì)象的關(guān)系
構(gòu)造函數(shù)是根據(jù)具體的事物抽象出來(lái)的抽象模板,實(shí)例對(duì)象是根據(jù)抽象的構(gòu)造函數(shù)模板得到的具體實(shí)例對(duì)象。
實(shí)例對(duì)象由構(gòu)造函數(shù)而來(lái),一個(gè)構(gòu)造函數(shù)可以生成很多具體的實(shí)例對(duì)象,而每個(gè)實(shí)例對(duì)象都是獨(dú)一無(wú)二的。
每個(gè)對(duì)象都有一個(gè) constructor 屬性,該屬性指向創(chuàng)建該實(shí)例的構(gòu)造函數(shù)。
反推出來(lái),每一個(gè)對(duì)象都有其構(gòu)造函數(shù)
- console.log(p1.constructor === Person) // => true
- console.log(p2.constructor === Person) // => true
- console.log(p1.constructor === p2.constructor) // => true
因此,我們可以通過(guò)實(shí)例對(duì)象的 constructor 屬性判斷實(shí)例和構(gòu)造函數(shù)之間的關(guān)系。
構(gòu)造函數(shù)存在的問(wèn)題
以構(gòu)造函數(shù)為模板,創(chuàng)建對(duì)象,對(duì)象的屬性和方法都可以在構(gòu)造函數(shù)內(nèi)部定義。
- function Cat(name, color) {
- this.name = name;
- this.color = color;
- this.say = function () {
- console.log('hello'+this.name,this.color);
- };
- }
- var cat1 = new Cat('貓', '白色');
- var cat2 = new Cat('貓', '黑色');
- cat1.say();
- cat2.say();
在該示例中,從表面上看好像沒(méi)什么問(wèn)題,但是實(shí)際上這樣做,有一個(gè)很大的弊端。那就是對(duì)于每一個(gè)實(shí)例對(duì)象, name 和 say 都是一模一樣的內(nèi)容,每一次生成一個(gè)實(shí)例,都必須為重復(fù)的內(nèi)容,多占用一些內(nèi)存,如果實(shí)例對(duì)象很多,會(huì)造成極大的內(nèi)存浪費(fèi)。
那么,能不能將相同的內(nèi)容,放到公共部分,節(jié)約計(jì)算機(jī)資源呢?
原型
JavaScript 的每個(gè)對(duì)象都會(huì)繼承一個(gè)父級(jí)對(duì)象,父級(jí)對(duì)象稱(chēng)為 原型 (prototype) 對(duì)象。
原型也是一個(gè)對(duì)象,原型對(duì)象上的所有屬性和方法,都能被子對(duì)象 (派生對(duì)象) 共享,通過(guò)構(gòu)造函數(shù)生成實(shí)例對(duì)象時(shí),會(huì)自動(dòng)為實(shí)例對(duì)象分配原型對(duì)象。而每一個(gè)構(gòu)造函數(shù)都有一個(gè)prototype屬性,這個(gè)屬性就是實(shí)例對(duì)象的原型對(duì)象。
null 沒(méi)有自己的原型對(duì)象。
這也就意味著,我們可以把所有對(duì)象實(shí)例需要共享的屬性和方法直接定義在構(gòu)造函數(shù)的 prototype 屬性上,也就是實(shí)例對(duì)象的原型對(duì)象上。
- function Cat(color) {
- this.color = color;
- }
- Cat.prototype.name = "貓";
- Cat.prototype.sayhello = function(){
- console.log('hello'+this.name,this.color);
- }
- Cat.prototype.saycolor = function (){
- console.log('hello'+this.color);
- }
- var cat1 = new Cat('白色');
- var cat2 = new Cat('黑色');
- cat1.sayhello();
- cat2.saycolor();
這時(shí)所有實(shí)例對(duì)象的 name 屬性和 sayhello() 、saycolor 方法,其實(shí)都是在同一個(gè)內(nèi)存地址的對(duì)象中,也就是構(gòu)造函數(shù)的 prototype 屬性上,因此就提高了運(yùn)行效率節(jié)省了內(nèi)存空間。
原型及原型鏈
構(gòu)造函數(shù)的 prototyp 屬性,就是由這個(gè)構(gòu)造函數(shù) new 出來(lái)的所有實(shí)例對(duì)象的 原型對(duì)象
所有對(duì)象都有原型對(duì)象。
- function Cat(name, color) {
- this.name = name;
- }
- var cat1 = new Cat('貓');
- console.log(cat1.__proto__.__proto__.__proto__);
而原型對(duì)象中的屬性和方法,都可以被實(shí)例對(duì)象直接使用。
每當(dāng)代碼讀取某個(gè)對(duì)象的某個(gè)屬性時(shí),都會(huì)執(zhí)行一次搜索,目標(biāo)是具有給定名字的屬性。
- 搜索首先從對(duì)象實(shí)例本身開(kāi)始
- 如果在實(shí)例中找到了具有給定名字的屬性,則返回該屬性的值
- 如果沒(méi)有找到,則繼續(xù)搜索指針指向的原型對(duì)象,在原型對(duì)象中查找具有給定名字的屬性
- 如果在原型對(duì)象中找到了這個(gè)屬性,則返回該屬性的值
- 如果還是找不到,就到原型的原型去找,依次類(lèi)推。
- 如果直到最頂層的Object.prototype還是找不到,則返回undefined。
而這正是多個(gè)對(duì)象實(shí)例共享原型所保存的屬性和方法的基本原理。
對(duì)象的屬性和方法,有可能是定義在自身內(nèi),也有可能是定義在它的原型對(duì)象上。由于原型本身也是對(duì)象,又有自己的原型,所以形成了一條可向上追溯的鏈條,叫 原型鏈(prototype chain)。
注意,不在要原型上形成多層鏈?zhǔn)讲檎?,非常浪費(fèi)資源。
內(nèi)置標(biāo)準(zhǔn)庫(kù)與包裝對(duì)象
在內(nèi)置標(biāo)準(zhǔn)對(duì)象中,對(duì)象是 JavaScript 語(yǔ)言最主要的數(shù)據(jù)類(lèi)型,三種原始類(lèi)型的值——數(shù)值、字符串、布爾值——在一定條件下,也會(huì)自動(dòng)轉(zhuǎn)為對(duì)象,也就是原始類(lèi)型的“包裝對(duì)象”(wrapper)。
所謂“包裝對(duì)象”,就是分別與數(shù)值、字符串、布爾值相對(duì)應(yīng)的Number、String、Boolean三個(gè)原生對(duì)象。這三個(gè)原生對(duì)象可以把原始類(lèi)型的值變成(包裝成)對(duì)象。
- var v1 = new Number(123);
- var v2 = new String('abc');
- var v3 = new Boolean(true);
- typeof v1 // "object"
- typeof v2 // "object"
- typeof v3 // "object"
- v1 === 123 // false
- v2 === 'abc' // false
- v3 === true // false
包裝對(duì)象的最大目的,首先是使得 JavaScript 的對(duì)象涵蓋所有的值,其次使得原始類(lèi)型的值可以方便地調(diào)用某些方法。
原始類(lèi)型的值,可以自動(dòng)當(dāng)作對(duì)象調(diào)用,即調(diào)用各種對(duì)象的方法和參數(shù)。
這時(shí),JavaScript 引擎會(huì)自動(dòng)將原始類(lèi)型的值轉(zhuǎn)為包裝對(duì)象實(shí)例,在使用后立刻銷(xiāo)毀實(shí)例。
比如,字符串可以調(diào)用length屬性,返回字符串的長(zhǎng)度。
- 'abc'.length // 3
上面代碼中,abc是一個(gè)字符串,本身不是對(duì)象,不能調(diào)用length屬性。JavaScript 引擎自動(dòng)將其轉(zhuǎn)為包裝對(duì)象,在這個(gè)對(duì)象上調(diào)用length屬性。調(diào)用結(jié)束后,這個(gè)臨時(shí)對(duì)象就會(huì)被銷(xiāo)毀。這就叫原始類(lèi)型與實(shí)例對(duì)象的自動(dòng)轉(zhuǎn)換。