摘要:我們通過一個(gè)簡單的例子與圖示,來了解構(gòu)造函數(shù),實(shí)例與原型三者之間的關(guān)系。而原型對象的指向構(gòu)造函數(shù)。于是根據(jù)構(gòu)造函數(shù)與原型的特性,我們就可以將在構(gòu)造函數(shù)中,通過聲明的屬性與方法稱為私有變量與方法,它們被當(dāng)前被某一個(gè)實(shí)例對象所獨(dú)有。
如果要我總結(jié)一下學(xué)習(xí)前端以來我遇到了哪些瓶頸,那么面向?qū)ο笠欢ㄊ堑谝粋€(gè)毫不猶豫想到的。盡管我現(xiàn)在對于面向?qū)ο笥辛艘恍┑牧私猓钱?dāng)初的那種似懂非懂的痛苦,依然歷歷在目。
為了幫助大家能夠更加直觀的學(xué)習(xí)和了解面向?qū)ο螅視?huì)用盡量簡單易懂的描述來展示面向?qū)ο蟮南嚓P(guān)知識。并且也準(zhǔn)備了一些實(shí)用的例子幫助大家更加快速的掌握面向?qū)ο蟮恼嬷B。
jQuery的面向?qū)ο髮?shí)現(xiàn)
封裝拖拽
簡易版運(yùn)動(dòng)框架封裝
這可能會(huì)花一點(diǎn)時(shí)間,但是卻值得期待。所以如果有興趣的朋友可以來簡書和公眾號關(guān)注我。
而這篇文章主要來聊一聊關(guān)于面向?qū)ο蟮囊恍┲匾幕竟Α?/p>
在ECMAScript-262中,對象被定義為“無序?qū)傩缘募?,其屬性可以包含基本值,對象或者函?shù)”。
也就是說,在JavaScript中,對象無非就是由一些列無序的key-value對組成。其中value可以是基本值,對象或者函數(shù)。
// 這里的person就是一個(gè)對象 var person = { name: "Tom", age: 18, getName: function() {}, parent: {} }
我們可以通過new的方式創(chuàng)建一個(gè)對象。
var obj = new Object();
也可以通過對象字面量的形式創(chuàng)建一個(gè)簡單的對象。
var obj = {};
當(dāng)我們想要給我們創(chuàng)建的簡單對象添加方法時(shí),可以這樣表示。
// 可以這樣 var person = {}; person.name = "TOM"; person.getName = function() { return this.name; } // 也可以這樣 var person = { name: "TOM", getName: function() { return this.name; } }
假如我們有一個(gè)簡單的對象如下:
var person = { name: "TOM", age: "20", getName: function() { return this.name } }
當(dāng)我們想要訪問他的name屬性時(shí),可以用如下兩種方式訪問。
person.name // 或者 person["name"]
如果我們想要訪問的屬性名是一個(gè)變量時(shí),常常會(huì)使用第二種方式。例如我們要同時(shí)訪問person的name與age,可以這樣寫:
["name", "age"].forEach(function(item) { console.log(person[item]); })
這種方式一定要重視,記住它以后在我們處理復(fù)雜數(shù)據(jù)的時(shí)候會(huì)有很大的幫助。
使用上面的方式創(chuàng)建對象很簡單,但是在很多時(shí)候并不能滿足我們的需求。就以person對象為例。假如我們在實(shí)際開發(fā)中,不僅僅需要一個(gè)名字叫做TOM的person對象,同時(shí)還需要另外一個(gè)名為Jake的person對象,雖然他們有很多相似之處,但是我們不得不重復(fù)寫兩次。
var perTom = { name: "TOM", age: 20, getName: function() { return this.name } }; var perJake = { name: "Jake", age: 22, getName: function() { return this.name } }
很顯然這并不是合理的方式,當(dāng)相似對象太多時(shí),大家都會(huì)崩潰掉。
我們可以使用工廠模式的方式解決這個(gè)問題。顧名思義,工廠模式就是我們提供一個(gè)模子,然后通過這個(gè)模子復(fù)制出我們需要的對象。我們需要多少個(gè),就復(fù)制多少個(gè)。
var createPerson = function(name, age) { // 聲明一個(gè)中間對象,該對象就是工廠模式的模子 var o = new Object(); // 依次添加我們需要的屬性與方法 o.name = name; o.age = age; o.getName = function() { return this.name; } return o; } // 創(chuàng)建兩個(gè)實(shí)例 var perTom = createPerson("TOM", 20); var PerJake = createPerson("Jake", 22);
相信上面的代碼并不難理解,也不用把工廠模式看得太過高大上。很顯然,工廠模式幫助我們解決了重復(fù)代碼上的麻煩,讓我們可以寫很少的代碼,就能夠創(chuàng)建很多個(gè)person對象。但是這里還有兩個(gè)麻煩,需要我們注意。
第一個(gè)麻煩就是這樣處理,我們沒有辦法識別對象實(shí)例的類型。使用instanceof可以識別對象的類型,如下例子:
var obj = {}; var foo = function() {} console.log(obj instanceof Object); // true console.log(foo instanceof Function); // true
因此在工廠模式的基礎(chǔ)上,我們需要使用構(gòu)造函數(shù)的方式來解決這個(gè)麻煩。
在JavaScript中,new關(guān)鍵字可以讓一個(gè)函數(shù)變得與眾不同。通過下面的例子,我們來一探new關(guān)鍵字的神奇之處。
function demo() { console.log(this); } demo(); // window new demo(); // demo
為了能夠直觀的感受他們不同,建議大家動(dòng)手實(shí)踐觀察一下。很顯然,使用new之后,函數(shù)內(nèi)部發(fā)生了一些變化,讓this指向改變。那么new關(guān)鍵字到底做了什么事情呢。嗯,其實(shí)我之前在文章里用文字大概表達(dá)了一下new到底干了什么,但是一些同學(xué)好奇心很足,總期望用代碼實(shí)現(xiàn)一下,我就大概以我的理解來表達(dá)一下吧。
// 先一本正經(jīng)的創(chuàng)建一個(gè)構(gòu)造函數(shù),其實(shí)該函數(shù)與普通函數(shù)并無區(qū)別 var Person = function(name, age) { this.name = name; this.age = age; this.getName = function() { return this.name; } } // 將構(gòu)造函數(shù)以參數(shù)形式傳入 function New(func) { // 聲明一個(gè)中間對象,該對象為最終返回的實(shí)例 var res = {}; if (func.prototype !== null) { // 將實(shí)例的原型指向構(gòu)造函數(shù)的原型 res.__proto__ = func.prototype; } // ret為構(gòu)造函數(shù)執(zhí)行的結(jié)果,這里通過apply,將構(gòu)造函數(shù)內(nèi)部的this指向修改為指向res,即為實(shí)例對象 var ret = func.apply(res, Array.prototype.slice.call(arguments, 1)); // 當(dāng)我們在構(gòu)造函數(shù)中明確指定了返回對象時(shí),那么new的執(zhí)行結(jié)果就是該返回對象 if ((typeof ret === "object" || typeof ret === "function") && ret !== null) { return ret; } // 如果沒有明確指定返回對象,則默認(rèn)返回res,這個(gè)res就是實(shí)例對象 return res; } // 通過new聲明創(chuàng)建實(shí)例,這里的p1,實(shí)際接收的正是new中返回的res var p1 = New(Person, "tom", 20); console.log(p1.getName()); // 當(dāng)然,這里也可以判斷出實(shí)例的類型了 console.log(p1 instanceof Person); // true
JavaScript內(nèi)部再通過其他的一些特殊處理,將var p1 = New(Person, "tom", 20); 等效于var p1 = new Person("tom", 20);。就是我們認(rèn)識的new關(guān)鍵字了。具體怎么處理的,我也不知道,別刨根問底了,一直回答下去太難 - -!
老實(shí)講,你可能很難在其他地方看到有如此明確的告訴你new關(guān)鍵字到底對構(gòu)造函數(shù)干了什么的文章了。理解了這段代碼,你對JavaScript的理解又比別人深刻了一分,所以,一本正經(jīng)厚顏無恥求個(gè)贊可好?
當(dāng)然,很多朋友由于對于前面幾篇文章的知識理解不夠到位,會(huì)對new的實(shí)現(xiàn)表示非常困惑。但是老實(shí)講,如果你讀了我的前面幾篇文章,一定會(huì)對這里new的實(shí)現(xiàn)有似曾相識的感覺。而且我這里已經(jīng)盡力做了詳細(xì)的注解,剩下的只能靠你自己了。
但是只要你花點(diǎn)時(shí)間,理解了他的原理,那么困擾了無數(shù)人的構(gòu)造函數(shù)中this到底指向誰就變得非常簡單了。
所以,為了能夠判斷實(shí)例與對象的關(guān)系,我們就使用構(gòu)造函數(shù)來搞定。
var Person = function(name, age) { this.name = name; this.age = age; this.getName = function() { return this.name; } } var p1 = new Person("Ness", 20); console.log(p1.getName()); // Ness console.log(p1 instanceof Person); // true
關(guān)于構(gòu)造函數(shù),如果你暫時(shí)不能夠理解new的具體實(shí)現(xiàn),就先記住下面這幾個(gè)結(jié)論吧。
與普通函數(shù)相比,構(gòu)造函數(shù)并沒有任何特別的地方,首字母大寫只是我們約定的小規(guī)定,用于區(qū)分普通函數(shù);
new關(guān)鍵字讓構(gòu)造函數(shù)具有了與普通函數(shù)不同的許多特點(diǎn),而new的過程中,執(zhí)行了如下過程:
聲明一個(gè)中間對象;
將該中間對象的原型指向構(gòu)造函數(shù)的原型;
將構(gòu)造函數(shù)的this,指向該中間對象;
返回該中間對象,即返回實(shí)例對象。
雖然構(gòu)造函數(shù)解決了判斷實(shí)例類型的問題,但是,說到底,還是一個(gè)對象的復(fù)制過程。跟工廠模式頗有相似之處。也就是說,當(dāng)我們聲明了100個(gè)person對象,那么就有100個(gè)getName方法被重新生成。
這里的每一個(gè)getName方法實(shí)現(xiàn)的功能其實(shí)是一模一樣的,但是由于分別屬于不同的實(shí)例,就不得不一直不停的為getName分配空間。這就是工廠模式存在的第二個(gè)麻煩。
顯然這是不合理的。我們期望的是,既然都是實(shí)現(xiàn)同一個(gè)功能,那么能不能就讓每一個(gè)實(shí)例對象都訪問同一個(gè)方法?
當(dāng)然能,這就是原型對象要幫我們解決的問題了。
我們創(chuàng)建的每一個(gè)函數(shù),都可以有一個(gè)prototype屬性,該屬性指向一個(gè)對象。這個(gè)對象,就是我們這里說的原型。
當(dāng)我們在創(chuàng)建對象時(shí),可以根據(jù)自己的需求,選擇性的將一些屬性和方法通過prototype屬性,掛載在原型對象上。而每一個(gè)new出來的實(shí)例,都有一個(gè)__proto__屬性,該屬性指向構(gòu)造函數(shù)的原型對象,通過這個(gè)屬性,讓實(shí)例對象也能夠訪問原型對象上的方法。因此,當(dāng)所有的實(shí)例都能夠通過__proto__訪問到原型對象時(shí),原型對象的方法與屬性就變成了共有方法與屬性。
我們通過一個(gè)簡單的例子與圖示,來了解構(gòu)造函數(shù),實(shí)例與原型三者之間的關(guān)系。
由于每個(gè)函數(shù)都可以是構(gòu)造函數(shù),每個(gè)對象都可以是原型對象,因此如果在理解原型之初就想的太多太復(fù)雜的話,反而會(huì)阻礙你的理解,這里我們要學(xué)會(huì)先簡化它們。就單純的剖析這三者的關(guān)系。
// 聲明構(gòu)造函數(shù) function Person(name, age) { this.name = name; this.age = age; } // 通過prototye屬性,將方法掛載到原型對象上 Person.prototype.getName = function() { return this.name; } var p1 = new Person("tim", 10); var p2 = new Person("jak", 22); console.log(p1.getName === p2.getName); // true
通過圖示我們可以看出,構(gòu)造函數(shù)的prototype與所有實(shí)例對象的__proto__都指向原型對象。而原型對象的constructor指向構(gòu)造函數(shù)。
除此之外,還可以從圖中看出,實(shí)例對象實(shí)際上對前面我們所說的中間對象的復(fù)制,而中間對象中的屬性與方法都在構(gòu)造函數(shù)中添加。于是根據(jù)構(gòu)造函數(shù)與原型的特性,我們就可以將在構(gòu)造函數(shù)中,通過this聲明的屬性與方法稱為私有變量與方法,它們被當(dāng)前被某一個(gè)實(shí)例對象所獨(dú)有。而通過原型聲明的屬性與方法,我們可以稱之為共有屬性與方法,它們可以被所有的實(shí)例對象訪問。
當(dāng)我們訪問實(shí)例對象中的屬性或者方法時(shí),會(huì)優(yōu)先訪問實(shí)例對象自身的屬性和方法。
function Person(name, age) { this.name = name; this.age = age; this.getName = function() { console.log("this is constructor."); } } Person.prototype.getName = function() { return this.name; } var p1 = new Person("tim", 10); p1.getName(); // this is constructor.
在這個(gè)例子中,我們同時(shí)在原型與構(gòu)造函數(shù)中都聲明了一個(gè)getName函數(shù),運(yùn)行代碼的結(jié)果表示原型中的訪問并沒有被訪問。
我們還可以通過in來判斷,一個(gè)對象是否擁有某一個(gè)屬性/方法,無論是該屬性/方法存在與實(shí)例對象還是原型對象。
function Person(name, age) { this.name = name; this.age = age; } Person.prototype.getName = function() { return this.name; } var p1 = new Person("tim", 10); console.log("name" in p1); // true
in的這種特性最常用的場景之一,就是判斷當(dāng)前頁面是否在移動(dòng)端打開。
isMobile = "ontouchstart" in document; // 很多人喜歡用瀏覽器UA的方式來判斷,但并不是很好的方式
更簡單的原型寫法
根據(jù)前面例子的寫法,如果我們要在原型上添加更多的方法,可以這樣寫:
function Person() {} Person.prototype.getName = function() {} Person.prototype.getAge = function() {} Person.prototype.sayHello = function() {} ... ...
除此之外,我還可以使用更為簡單的寫法。
function Person() {} Person.prototype = { constructor: Person, getName: function() {}, getAge: function() {}, sayHello: function() {} }
這種字面量的寫法看上去簡單很多,但是有一個(gè)需要特別注意的地方。Person.prototype = {}實(shí)際上是重新創(chuàng)建了一個(gè){}對象并賦值給Person.prototype,這里的{}并不是最初的那個(gè)原型對象。因此它里面并不包含constructor屬性。為了保證正確性,我們必須在新創(chuàng)建的{}對象中顯示的設(shè)置constructor的指向。即上面的constructor: Person。
原型對象其實(shí)也是普通的對象。幾乎所有的對象都可能是原型對象,也可能是實(shí)例對象,而且還可以同時(shí)是原型對象與實(shí)例對象。這樣的一個(gè)對象,正是構(gòu)成原型鏈的一個(gè)節(jié)點(diǎn)。因此理解了原型,那么原型鏈并不是一個(gè)多么復(fù)雜的概念。
我們知道所有的函數(shù)都有一個(gè)叫做toString的方法。那么這個(gè)方法到底是在哪里的呢?
先隨意聲明一個(gè)函數(shù):
function add() {}
那么我們可以用如下的圖來表示這個(gè)函數(shù)的原型鏈。
其中add是Function對象的實(shí)例。而Function的原型對象同時(shí)又是Object原型的實(shí)例。這樣就構(gòu)成了一條原型鏈。原型鏈的訪問,其實(shí)跟作用域鏈有很大的相似之處,他們都是一次單向的查找過程。因此實(shí)例對象能夠通過原型鏈,訪問到處于原型鏈上對象的所有屬性與方法。這也是foo最終能夠訪問到處于Object原型對象上的toString方法的原因。
基于原型鏈的特性,我們可以很輕松的實(shí)現(xiàn)繼承。
我們常常結(jié)合構(gòu)造函數(shù)與原型來創(chuàng)建一個(gè)對象。因?yàn)闃?gòu)造函數(shù)與原型的不同特性,分別解決了我們不同的困擾。因此當(dāng)我們想要實(shí)現(xiàn)繼承時(shí),就必須得根據(jù)構(gòu)造函數(shù)與原型的不同而采取不同的策略。
我們聲明一個(gè)Person對象,該對象將作為父級,而子級cPerson將要繼承Person的所有屬性與方法。
function Person(name, age) { this.name = name; this.age = age; } Person.prototype.getName = function() { return this.name; }
首先我們來看構(gòu)造函數(shù)的繼承。在上面我們已經(jīng)理解了構(gòu)造函數(shù)的本質(zhì),它其實(shí)是在new內(nèi)部實(shí)現(xiàn)的一個(gè)復(fù)制過程。而我們在繼承時(shí)想要的,就是想父級構(gòu)造函數(shù)中的操作在子級的構(gòu)造函數(shù)中重現(xiàn)一遍即可。我們可以通過call方法來達(dá)到目的。
// 構(gòu)造函數(shù)的繼承 function cPerson(name, age, job) { Person.call(this, name, age); this.job = job; }
而原型的繼承,則只需要將子級的原型對象設(shè)置為父級的一個(gè)實(shí)例,加入到原型鏈中即可。
// 繼承原型 cPerson.prototype = new Person(name, age); // 添加更多方法 cPerson.prototype.getLive = function() {}
當(dāng)然關(guān)于繼承還有更好的方式。
假設(shè)原型鏈的終點(diǎn)Object.prototype為原型鏈的E(end)端,原型鏈的起點(diǎn)為S(start)端。
通過前面原型鏈的學(xué)習(xí)我們知道,處于S端的對象,可以通過S -> E的單向查找,訪問到原型鏈上的所有方法與屬性。因此這給繼承提供了理論基礎(chǔ)。我們只需要在S端添加新的對象,那么新對象就能夠通過原型鏈訪問到父級的方法與屬性。因此想要實(shí)現(xiàn)繼承,是一件非常簡單的事情。
因?yàn)榉庋b一個(gè)對象由構(gòu)造函數(shù)與原型共同組成,因此繼承也會(huì)分別有構(gòu)造函數(shù)的繼承與原型的繼承。
假設(shè)我們已經(jīng)封裝好了一個(gè)父類對象Person。如下。
var Person = function(name, age) { this.name = name; this.age = age; } Person.prototype.getName = function() { return this.name; } Person.prototype.getAge = function() { return this.age; }
構(gòu)造函數(shù)的繼承比較簡單,我們可以借助call/apply來實(shí)現(xiàn)。假設(shè)我們要通過繼承封裝一個(gè)Student的子類對象。那么構(gòu)造函數(shù)可以如下實(shí)現(xiàn)。
var Student = function(name, age, grade) { // 通過call方法還原Person構(gòu)造函數(shù)中的所有處理邏輯 Student.call(Person, name, age); this.grade = grade; } // 等價(jià)于 var Student = function(name, age, grade) { this.name = name; this.age = age; this.grade = grade; }
原型的繼承則稍微需要一點(diǎn)思考。首先我們應(yīng)該考慮,如何將子類對象的原型加入到原型鏈中?我們只需要讓子類對象的原型,成為父類對象的一個(gè)實(shí)例,然后通過__proto__就可以訪問父類對象的原型。這樣就繼承了父類原型中的方法與屬性了。
因此我們可以先封裝一個(gè)方法,該方法根據(jù)父類對象的原型創(chuàng)建一個(gè)實(shí)例,該實(shí)例將會(huì)作為子類對象的原型。
function create(proto, options) { // 創(chuàng)建一個(gè)空對象 var tmp = {}; // 讓這個(gè)新的空對象成為父類對象的實(shí)例 tmp.__proto__ = proto; // 傳入的方法都掛載到新對象上,新的對象將作為子類對象的原型 Object.defineProperties(tmp, options); return tmp; }
簡單封裝了create對象之后,我們就可以使用該方法來實(shí)現(xiàn)原型的繼承了。
Student.prototype = create(Person.prototype, { // 不要忘了重新指定構(gòu)造函數(shù) constructor: { value: Student } getGrade: { value: function() { return this.grade } } })
那么我們來驗(yàn)證一下我們這里實(shí)現(xiàn)的繼承是否正確。
var s1 = new Student("ming", 22, 5); console.log(s1.getName()); // ming console.log(s1.getAge()); // 22 console.log(s1.getGrade()); // 5
全部都能正常訪問,沒問題。在ECMAScript5中直接提供了一個(gè)Object.create方法來完成我們上面自己封裝的create的功能。因此我們可以直接使用Object.create.
Student.prototype = create(Person.prototype, { // 不要忘了重新指定構(gòu)造函數(shù) constructor: { value: Student } getGrade: { value: function() { return this.grade } } })
完整代碼如下:
function Person(name, age) { this.name = name; this.age = age; } Person.prototype.getName = function() { return this.name } Person.prototype.getAge = function() { return this.age; } function Student(name, age, grade) { // 構(gòu)造函數(shù)繼承 Person.call(this, name, age); this.grade = grade; } // 原型繼承 Student.prototype = Object.create(Person.prototype, { // 不要忘了重新指定構(gòu)造函數(shù) constructor: { value: Student } getGrade: { value: function() { return this.grade } } }) var s1 = new Student("ming", 22, 5); console.log(s1.getName()); // ming console.log(s1.getAge()); // 22 console.log(s1.getGrade()); // 5
在上面的繼承實(shí)現(xiàn)中,使用了一個(gè)大家可能不太熟悉的方法defineProperties。并且在定義getGrade時(shí)使用了一個(gè)很奇怪的方式。
getGrade: { value: function() { return this.grade } }
這其實(shí)是對象中的屬性類型。在我們平常的使用中,給對象添加一個(gè)屬性時(shí),直接使用object.param的方式就可以了,或者直接在對象中掛載。
var person = { name: "TOM" }
在ECMAScript5中,對每個(gè)屬性都添加了幾個(gè)屬性類型,來描述這些屬性的特點(diǎn)。他們分別是
configurable: 表示該屬性是否能被delete刪除。當(dāng)其值為false時(shí),其他的特性也不能被改變。默認(rèn)值為true
enumerable: 是否能枚舉。也就是是否能被for-in遍歷。默認(rèn)值為true
writable: 是否能修改值。默認(rèn)為true
value: 該屬性的具體值是多少。默認(rèn)為undefined
get: 當(dāng)我們通過person.name訪問name的值時(shí),get將被調(diào)用。該方法可以自定義返回的具體值時(shí)多少。get默認(rèn)值為undefined
set: 當(dāng)我們通過person.name = "Jake"設(shè)置name的值時(shí),set方法將被調(diào)用。該方法可以自定義設(shè)置值的具體方式。set默認(rèn)值為undefined
需要注意的是,不能同時(shí)設(shè)置value、writable 與 get、set的值。
我們可以通過Object.defineProperty方法來修改這些屬性類型。
下面我們用一些簡單的例子來演示一下這些屬性類型的具體表現(xiàn)。
configurable
// 用普通的方式給person對象添加一個(gè)name屬性,值為TOM var person = { name: "TOM" } // 使用delete刪除該屬性 delete person.name; // 返回true 表示刪除成功 // 通過Object.defineProperty重新添加name屬性 // 并設(shè)置name的屬性類型的configurable為false,表示不能再用delete刪除 Object.defineProperty(person, "name", { configurable: false, value: "Jake" // 設(shè)置name屬性的值 }) // 再次delete,已經(jīng)不能刪除了 delete person.name // false console.log(person.name) // 值為Jake // 試圖改變value person.name = "alex"; console.log(person.name) // Jake 改變失敗
enumerable
var person = { name: "TOM", age: 20 } // 使用for-in枚舉person的屬性 var params = []; for(var key in person) { params.push(key); } // 查看枚舉結(jié)果 console.log(params); // ["name", "age"] // 重新設(shè)置name屬性的類型,讓其不可被枚舉 Object.defineProperty(person, "name", { enumerable: false }) var params_ = []; for(var key in person) { params_.push(key) } // 再次查看枚舉結(jié)果 console.log(params_); // ["age"]
writable
var person = { name: "TOM" } // 修改name的值 person.name = "Jake"; // 查看修改結(jié)果 console.log(person.name); // Jake 修改成功 // 設(shè)置name的值不能被修改 Object.defineProperty(person, "name", { writable: false }) // 再次試圖修改name的值 person.name = "alex"; console.log(person.name); // Jake 修改失敗
value
var person = {} // 添加一個(gè)name屬性 Object.defineProperty(person, "name", { value: "TOM" }) console.log(person.name) // TOM
get/set
var person = {} // 通過get與set自定義訪問與設(shè)置name屬性的方式 Object.defineProperty(person, "name", { get: function() { // 一直返回TOM return "TOM" }, set: function(value) { // 設(shè)置name屬性時(shí),返回該字符串,value為新值 console.log(value + " in set"); } }) // 第一次訪問name,調(diào)用get console.log(person.name) // TOM // 嘗試修改name值,此時(shí)set方法被調(diào)用 person.name = "alex" // alex in set // 第二次訪問name,還是調(diào)用get console.log(person.name) // TOM
請盡量同時(shí)設(shè)置get、set。如果僅僅只設(shè)置了get,那么我們將無法設(shè)置該屬性值。如果僅僅只設(shè)置了set,我們也無法讀取該屬性的值。
Object.defineProperty只能設(shè)置一個(gè)屬性的屬性特性。當(dāng)我們想要同時(shí)設(shè)置多個(gè)屬性的特性時(shí),需要使用我們之前提到過的Object.defineProperties
var person = {} Object.defineProperties(person, { name: { value: "Jake", configurable: true }, age: { get: function() { return this.value || 22 }, set: function(value) { this.value = value } } }) person.name // Jake person.age // 22
我們可以使用Object.getOwnPropertyDescriptor方法讀取某一個(gè)屬性的特性值。
var person = {} Object.defineProperty(person, "name", { value: "alex", writable: false, configurable: false }) var descripter = Object.getOwnPropertyDescriptor(person, "name"); console.log(descripter); // 返回結(jié)果如下 descripter = { configurable: false, enumerable: false, value: "alex", writable: false }
關(guān)于面向?qū)ο蟮幕A(chǔ)知識大概就是這些了。我從最簡單的創(chuàng)建一個(gè)對象開始,解釋了為什么我們需要構(gòu)造函數(shù)與原型,理解了這其中的細(xì)節(jié),有助于我們在實(shí)際開發(fā)中靈活的組織自己的對象。因?yàn)槲覀儾⒉皇撬械膱鼍岸紩?huì)使用構(gòu)造函數(shù)或者原型來創(chuàng)建對象,也許我們需要的對象并不會(huì)聲明多個(gè)實(shí)例,或者不用區(qū)分對象的類型,那么我們就可以選擇更簡單的方式。
我們還需要關(guān)注構(gòu)造函數(shù)與原型的各自特性,有助于我們在創(chuàng)建對象時(shí)準(zhǔn)確的判斷我們的屬性與方法到底是放在構(gòu)造函數(shù)中還是放在原型中。如果沒有理解清楚,這會(huì)給我們在實(shí)際開發(fā)中造成非常大的困擾。
最后接下來的幾篇文章,我會(huì)挑幾個(gè)面向?qū)ο蟮睦?,繼續(xù)幫助大家掌握面向?qū)ο蟮膶?shí)際運(yùn)用。
前端基礎(chǔ)進(jìn)階系列目錄
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/90547.html
摘要:不過其實(shí)簡書文章評論里有很多大家的問題以及解答,對于進(jìn)一步理解文中知識幫助很大的,算是有點(diǎn)可惜吧。不過也希望能夠?qū)φ趯W(xué)習(xí)前端的你有一些小幫助。如果在閱讀中發(fā)現(xiàn)了一些錯(cuò)誤,請?jiān)谠u論里告訴我,我會(huì)及時(shí)更改。 前端基礎(chǔ)進(jìn)階(一):內(nèi)存空間詳細(xì)圖解 前端基礎(chǔ)進(jìn)階(二):執(zhí)行上下文詳細(xì)圖解 前端基礎(chǔ)進(jìn)階(三):變量對象詳解 前端基礎(chǔ)進(jìn)階(四):詳細(xì)圖解作用域鏈與閉包 前端基礎(chǔ)進(jìn)階(五):全方位...
摘要:個(gè)人前端文章整理從最開始萌生寫文章的想法,到著手開始寫,再到現(xiàn)在已經(jīng)一年的時(shí)間了,由于工作比較忙,更新緩慢,后面還是會(huì)繼更新,現(xiàn)將已經(jīng)寫好的文章整理一個(gè)目錄,方便更多的小伙伴去學(xué)習(xí)。 showImg(https://segmentfault.com/img/remote/1460000017490740?w=1920&h=1080); 個(gè)人前端文章整理 從最開始萌生寫文章的想法,到著手...
摘要:二構(gòu)造函數(shù)我們先復(fù)習(xí)一下構(gòu)造函數(shù)的知識上面的例子中和都是的實(shí)例。這兩個(gè)實(shí)例都有一個(gè)構(gòu)造函數(shù)屬性,該屬性是一個(gè)指針指向。原型鏈其中是對象的實(shí)例。 一. 普通對象與函數(shù)對象 JavaScript 中,萬物皆對象!但對象也是有區(qū)別的。分為普通對象和函數(shù)對象,Object 、Function 是 JS 自帶的函數(shù)對象。下面舉例說明 var o1 = {}; var o2 =new Objec...
摘要:很多情況下,通常一個(gè)人類,即創(chuàng)建了一個(gè)具體的對象。對象就是數(shù)據(jù),對象本身不包含方法。類是相似對象的描述,稱為類的定義,是該類對象的藍(lán)圖或原型。在中,對象通過對類的實(shí)體化形成的對象。一類的對象抽取出來。注意中,對象一定是通過類的實(shí)例化來的。 showImg(https://segmentfault.com/img/bVTJ3H?w=900&h=385); 馬上就要到七夕了,離年底老媽老爸...
閱讀 2500·2021-11-16 11:45
閱讀 2478·2021-10-11 10:59
閱讀 2284·2021-10-08 10:05
閱讀 3909·2021-09-23 11:30
閱讀 2400·2021-09-07 09:58
閱讀 869·2019-08-30 15:55
閱讀 797·2019-08-30 15:53
閱讀 1945·2019-08-29 17:00