前言
不管是哪門語言,千變?nèi)f化不離其宗,深入理解其本質(zhì),方能應(yīng)用自如。對應(yīng)到j(luò)s,閉包,原型,函數(shù),對象等是需要花費大功夫思考、理解的。
這一次我們來說一說在JavaScript中經(jīng)常會用到的一個復(fù)雜基本類型,對象,先從對象的屬性講起,再講對象的創(chuàng)建方法,基本涵蓋了創(chuàng)建對象的各種方法,大家一起學(xué)習(xí)呀~
一、對象
要掌握對象的使用及繼承,首先當(dāng)然需要先理解它,接下來,將會對對象的屬性類型進行一個整理
1、什么是對象
對象其實是無序?qū)傩缘募希鋵傩钥梢园局担瑢ο蠡蛘吆瘮?shù),比如像下面這個例子就是一個person對象啦
var person = { name: "NIcholas", age: 29, sayName: function() { console.log(this.name); } }
從上面的例子我們可以看到,對象可以是由屬性和其相應(yīng)的值構(gòu)成,對象中可以包含函數(shù),也可以包含其它對象
2、屬性類型
在JavaScript中,其實有兩種屬性,包括數(shù)據(jù)屬性和訪問器屬性
(1)數(shù)據(jù)屬性
數(shù)據(jù)屬性包含一個數(shù)據(jù)值的位置,在這個位置可以讀取和寫入值,一般來說,有4個描述其行為的特性:
a、[[Configurable]]:表示能否通過delete刪除屬性從而重新定義屬性,默認(rèn)值為true
b、[[Enumerable]]:表示能否通過for-in循環(huán)返回屬性,默認(rèn)值為true
c、[[Writable]]:表示能否修改屬性的值,默認(rèn)值為true
d、[[Value]]:包含這個屬性的數(shù)據(jù)值,默認(rèn)值為undefined
一般來說,數(shù)據(jù)屬性都有自己的默認(rèn)值,那么如果我們要修改數(shù)據(jù)屬性默認(rèn)的特性,應(yīng)該怎么辦呢?這個時候就需要用到Object,defineProperty()方法啦,這個方法接收三個參數(shù):屬性所在的對象,屬性的名字和一個描述對象,來看下面的例子
var person = {}; Object.defineProperty(person, "name", { writable: false, value: "Nicholas" }); console.log(person.name); // Nicholas // 重新賦值 person.name = "Greg"; console.log(person.name); // Nicholas
從上面的例子我們可以看到,因為設(shè)置了person對象的name屬性為不可修改,因此無論你在后面怎么修改person的name屬性的值,name屬性的值都不會發(fā)生改變
(2)訪問器屬性
訪問器屬性不包含數(shù)據(jù)值,它們包含一對兒getter和setter函數(shù),在讀取訪問器屬性時,會調(diào)用getter函數(shù),這個函數(shù)負(fù)責(zé)返回有效的值,在寫入訪問器屬性時,會調(diào)用setter函數(shù)并傳入新值,這個函數(shù)負(fù)責(zé)決定如何處理數(shù)據(jù),訪問器屬性有以下4個特性
a、[[Configurable]]:表示能否通過delete刪除屬性從而重新定義屬性,默認(rèn)值為true
b、[[Enumerable]]:表示能否通過for-in循環(huán)返回屬性,默認(rèn)值為true
c、[[Get]]:在讀取屬性時調(diào)用的函數(shù),默認(rèn)值為undefined
d、[[Set]]:在寫入屬性時調(diào)用的函數(shù),默認(rèn)值為undefined
訪問器屬性不能直接定義,必須調(diào)用Object.definedProperty()
來定義的,來看下面的例子
var book = { _year:2004, edition:1 } Object.defineProperty(book, "year", { get: function() { return this._year; }, set: function(newValue) { this._year = newValue; } });
這里要說明一下,book對象中_year前面的下劃線是一種常用的記號,用于表示只能通過對象方法訪問的屬性,還有呀,大家不要小看了Object.definedProperty()
這個方法,這個方法可是很強大呀,像vue的雙向數(shù)據(jù)綁定,其實就是用到了這個方法去實現(xiàn)的
(3)讀取屬性的特性
既然JavaScript有數(shù)據(jù)屬性和訪問器屬性,那么我們怎樣才能讀取它們呀,這個時候就需要用到Object.getOwnPropertyDescriptor()
方法了,這個方法可以取得給定屬性的描述符,接收兩個參數(shù),分別是屬性所在的對象和要讀取其描述符的屬性名稱
二、對象的創(chuàng)建
在了解了對象之后,接下來我們就需要說下怎么創(chuàng)建對象了,最簡單的方法,當(dāng)然就是使用前面說的對象字面量的方法去創(chuàng)建啦,但是如果我們需要創(chuàng)建好多個對象,用前面的方法就不行了,我們需要用到其它更加簡便的方法,幫助我們創(chuàng)建出多個對象
1、工廠模式
這種模式其實是一個設(shè)計模式,抽象了創(chuàng)建具體對象的過程,主要是通過在函數(shù)內(nèi)部創(chuàng)建一個對象,為其添加屬性和方法,并將對象返回,從而實現(xiàn)創(chuàng)建多個對象的目的,來看下面的例子
function createPerson(name, age) { var o = new Object(); o.name = name; o.age = age; o.sayName = function() { console.log(this.name); }; return o; } var person1 = createPerson("Nicholas", 29); var person2 = createPerson("Greg", 27);
優(yōu)點:能夠解決創(chuàng)建多個對象的問題,兼容各個瀏覽器
缺點:沒有解決對象識別的問題,不能知道一個對象的類型
2、構(gòu)造函數(shù)模式
這種模式主要通過創(chuàng)建自定義的構(gòu)造函數(shù),從而定義自定義對象類型的屬性和方法,來看下面例子
function Person(name, age) { this.name = name; this.age = age; this.sayName = function() { console.log(this.name); } } var person1 = new Person("Nicholas", 29); var person2 = new Person("Greg", 27);
這里要說明一下,構(gòu)造函數(shù)需要以一個大寫字母開頭,而非構(gòu)造函數(shù)應(yīng)該以一個小寫字母開頭,這個主要是為了區(qū)別構(gòu)造函數(shù)和其它函數(shù),構(gòu)造函數(shù)其實本身也是函數(shù),只是用來創(chuàng)建對象而已
其中,要創(chuàng)建Person的新實例,需要使用new操作符,其實這里會經(jīng)過4個步驟,首先,將會創(chuàng)建一個新對象,接著會將構(gòu)造函數(shù)的作用域賦給新對象,this指向了這個對象,接著會執(zhí)行構(gòu)造函數(shù)中的代碼,為這個新對象添加屬性,最后會返回新對象
優(yōu)點:可以創(chuàng)建多個對象,解決對象的識別問題
缺點:每個實例都會創(chuàng)建不同的function實例,而其實創(chuàng)建完成同樣任務(wù)的function實例是很沒有必要的
這里還是要說明一下,對象類型的檢測需要用到instanceof操作符,比如像上面的例子可以用下面的方法檢測
console.log(person1 instanceof Person); // true console.log(person2 instanceof Person); // true
使用構(gòu)造函數(shù)模式可以解決對象的識別問題,而這也是工廠模式無法辦到的
3、原型模式
我們都知道,每個函數(shù)都有一個prototype屬性,這個屬性是一個指針,指向一個對象,而這個對象就是原型對象,包含了所有實例共享的屬性和方法,如果我們要創(chuàng)建的對象需要共享屬性和方法,就可以使用這種方法創(chuàng)建
function Person() { } Person.prototype.name = "Nicholas"; Person.prototype.age = 29; Person.prototype.sayName = function() { console.log(this.name); } var person1 = new Person(); var person2 = new Person(); person1.sayName(); // Nicholas person2.sayName(); // Nicholas
優(yōu)點:不用為構(gòu)造函數(shù)傳遞參數(shù),可以創(chuàng)建多個相同的對象
缺點:原型中的屬性被很多實例共享,當(dāng)屬性為包含引用類型值的屬性時,修改一個實例中屬性的值,另一個實例中的屬性的值也會改變
4、組合使用構(gòu)造函數(shù)模式和原型模式
通過前面的分析,我們應(yīng)該可以看到構(gòu)造函數(shù)模式和原型模式的優(yōu)點和缺點啦,構(gòu)造函數(shù)可以創(chuàng)建多個不同屬性值的對象,原型模式可以用于定義方法和共享的屬性,我們可以將這兩種模式結(jié)合起來,這種模式現(xiàn)在是使用最廣泛的一種模式啦
function Person(name, age) { this.name = name; this.age = age; } Person.prototype = { constructor: Person, sayName: function() { console.log(this.name); } } var person1 = new Person("Nicholas", 29); var person2 = new Person("Greg", 27); person1.sayName(); // Nicholas person2.sayName(); // Greg person1.sayName === person2.sayName; // true
從上面的例子我們可以看到,使用這種模式創(chuàng)建對象,每個實例都會有自己的一份實例屬性的副本,但同時又共享著對方法的引用,最大限度地節(jié)省了內(nèi)存,另外,這種混成模式還支持向構(gòu)造函數(shù)傳遞參數(shù),可謂是集兩種模式之長
5、動態(tài)原型模式
這種模式主要是將所有信息都封裝在了構(gòu)造函數(shù)里,因為在組合構(gòu)造函數(shù)模式和原型模式中,構(gòu)造函數(shù)和原型模式是獨立的,有些開發(fā)人員會感到很困惑,因此,這種模式也是為了解決這個問題,通過在構(gòu)造函數(shù)中初始化初始化原型,又保持了同時使用構(gòu)造函數(shù)和原型的優(yōu)點,換句話說,就是可以通過在構(gòu)造函數(shù)中,檢查某個應(yīng)該存在的方法是否有效,來決定是否需要初始化原型
function Person(name, age) { this.name = name; this.age = age; if(typeof this.sayName != "function") { Person.prototype.sayName = function() { console.log(this.name); } } } var person1 = new Person("Nicholas", 29); person1.sayName(); // Nicholas
這里要說明一下,在if語句中的代碼,只有在首次調(diào)用構(gòu)造函數(shù)時才會執(zhí)行,之后原型已經(jīng)得到初始化,不需要再做什么修改了
6、寄生構(gòu)造函數(shù)模式
這種模式其實和工廠模式很像,除了使用new操作符并把使用的包裝函數(shù)叫做構(gòu)造函數(shù)之外,和工廠模式可以說是一模一樣的,這種模式的基本思想是創(chuàng)建一個函數(shù),該函數(shù)的作用僅僅是封裝創(chuàng)建對象的代碼,然后再返回新創(chuàng)建的對象
function Person(name, age) { var o = new Object(); o.name = name; o.age = age; o.sayName = function() { console.log(this.name); } return o; } var person1 = new Person("Nicholas", 29); person1.sayName(); // Nicholas
寄生構(gòu)造函數(shù)模式和工廠模式真的是很像,那么既然有了工廠模式,為什么還要有寄生構(gòu)造函數(shù)模式呢?其實這個模式主要是用來給js原生的構(gòu)造函數(shù)定義一些新的方法,我們可以看下面這個例子
function SpecialArray() { var values = new Array(); values.push.apply(values, argumens); values.toPipedString = function() { return this.join("|"); } return values; } var colors = new SpecialArray("red","blue"); console.log(colors.toPipedString()); // red|blue
從上面這個例子我們可以看到,我們在構(gòu)造函數(shù)里面創(chuàng)建了一個新的數(shù)組,然后通過push方法初始化這個數(shù)組,并且又給數(shù)組的實例添加了一個toPipedString方法,并且將所創(chuàng)建的數(shù)組返回,因此呢,當(dāng)我們通過new創(chuàng)建了SpecialArray實例時,其實就得到增加了新方法的數(shù)組實例啦,就可以在這個實例上使用我們添加的新的方法toPipedString
7、穩(wěn)妥構(gòu)造函數(shù)模式
在說這種模式之前,要先說一下穩(wěn)妥對象,穩(wěn)妥對象就是指沒有公共屬性,而且其方法也不引用this的對象,穩(wěn)妥對象最適合在一些安全的環(huán)境中,或者防止數(shù)據(jù)被其他應(yīng)用程序改動時使用,穩(wěn)妥構(gòu)造函數(shù)模式遵循與寄生構(gòu)造函數(shù)類似的模式,但是還是有下面的不同,第一個是新創(chuàng)建的對象實例方法不引用this,第二個是不使用new操作符調(diào)用構(gòu)造函數(shù)
function Person(name, age) { var o = new Object(); _name = name; _age = age; o.sayName = function() { return _name; } return o; } var person1 = new Person("Nicholas", 29); person1.sayName(); // Nicholas
在上面這個例子中,我們在構(gòu)造函數(shù)中創(chuàng)建了一個對象后,可以繼續(xù)添加一些私有的變量和函數(shù),要修改這些私有的變量和函數(shù),只能通過創(chuàng)建的對象的方法進行訪問,這里在對象上創(chuàng)建的方法其實可以看作就是公有方法,比如說這里的_name和_age就是私有變量,而對象o的sayName方法就是訪問私有變量的公有方法了,這里除了調(diào)用sayName()方法外,沒有其它方法可以訪問其數(shù)據(jù)成員
8、es6中創(chuàng)建對象的方法
最后要來說下es6中創(chuàng)建對象新增的方法啦,在es6中,引入了 Class(類)這個概念,作為對象的模板,通過class關(guān)鍵字,可以定義類,基本上,ES6 的class可以看作只是一個語法糖,它的絕大部分功能,ES5 都可以做到,新的class寫法只是讓對象原型的寫法更加清晰、更像面向?qū)ο缶幊痰恼Z法而已
class Person { constructor(name, age) { this.name = name; this.age = age; } sayName() { console.log(this.name); } } var person1 = new Person("Nicholas", 29); person1.sayName(); // Nicholas
上面這個例子其實就是組合構(gòu)造函數(shù)模式和原型模式的改寫,其中,constructor屬性直接指向類本身,該方法會默認(rèn)返回實例對象,在里面定義的屬性和方法都是實例本身具有的方法,不是其它實例共享的,而像sayName方法就是定義在原型上的方法了,是所有實例一起共享的
好啦,今天就介紹到這里了,不知道大家對對象和對象的創(chuàng)建是否有了一個比較詳細(xì)的了解了呢
總結(jié)
聲明:本網(wǎng)頁內(nèi)容旨在傳播知識,若有侵權(quán)等問題請及時與本網(wǎng)聯(lián)系,我們將在第一時間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com