摘要:起因構造函數對象字面量都可以用來創建單個對象,但有明顯缺點使用同一個接口創建很多對象,會產生大量的重復代碼。組合使用構造函數模式和原型模式創建自定義類型的最常見方式,就是組合使用構造函數模式與原型模式。
寫在前面
注:這個系列是本人對js知識的一些梳理,其中不少內容來自書籍:Javascript高級程序設計第三版和JavaScript權威指南第六版,感謝它們的作者和譯者。有發現什么問題的,歡迎留言指出。
起因Object構造函數、對象字面量、Object.creat都可以用來創建單個對象,但有明顯缺點:使用同一個接口創建很多對象,會產生大量的重復代碼。所以才開始了創建對象的模式的探索。
檢測對象的類3種常見的檢測任意對象的類的技術:instanceof運算符、constructor屬性、構造函數的名字。3種各有優劣,適用于不同場景。(但往往我們更關注對象可以完成什么工作,對象屬于哪個類并不是最重要的)
1.instanceof運算符
運算符左邊是對象,右邊是構造函數,如o instanceof f,如果在o的原型鏈中查找到f,就返回true:
var date = new Date(); console.log(date instanceof Date);//true console.log(date instanceof Object);//true console.log(date instanceof Number);//false
這種方式的缺點:①無法通過對象來獲得類名,只能檢測對象是否屬于指定的類名,②如果是不同的執行上下文,如客戶端中每個窗口和框架子頁面都具有多帶帶的執行上下文,其中一個框架頁面中的數組不是另一個框架頁面的Array()構造函數的實例。
2.constructor屬性
console.log(date.constructor == Date);//true
缺點:①和instance的第2點缺點一樣,執行上下文的問題,②并不是所有的對象都有constrctor屬性,如果創建對象時把對象的prototype直接覆蓋了而又沒有指定constrctor屬性,就會沒有這個屬性。
3.構造函數的名稱
//返回對象的類 function classof(o) { return Object.prototype.toString.call(o).slice(8,-1); } //返回函數的名字(可能是空字符串),不是函數就返回null Function.prototype.getName = function () { if("name" in this) return this.name; return this.name = this.toString().match(/functions*([^()]*)(/)[1]; } function type(o) { //type,class,name var t,c,n; //處理null值特殊情況 if(o === null) return "null"; //處理NaN和它自身不相等 if(o !== o) return "nan"; //識別出原始值的類型 if((t = typeof o) !== "object") return t; //識別出大多數的內置對象(類名除了"Object") if((c=classof(o)) !== "Object") return c; //如果對象構造函數的名字存在,就返回它 if(o.constructor && typeof o.constructor === "function" && (n = o.constructor.getName()) ) return n; //其他的類型無法判別,返回"Object" return "Object"; }
這種方法的問題:①并不是所有的構造函數都有constructor屬性,②并不是所有的函數都有名字(name是非標準屬性),如果使用不帶名字的函數定義表達式定義一個構造函數,getName()方法會返回空字符串。
//這種情況下如果沒有name屬性,那么getName()方法就返回空字符串了 var Example = function (x, y) { this.x = x;this.y = y; }1.工廠模式
function creatPerson(name, age, job) { var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function () { console.log(this.name); }; return o; } var person1 = creatPerson("jaychou",34,"singer"); var person2 = creatPerson("xiaoming",15,"student"); //{name: "jaychou", age: 34, job: "singer", sayName: ?} console.log(person1); //{name: "xiaoming", age: 15, job: "student", sayName: ?} console.log(person2);
工廠模式的最大缺點:沒有解決對象識別的問題,不知道一個對象的類型。
2.構造函數模式顯然構造函數可用來創建特定類型的對象,如Array,Date等,重寫上面的例子:
function Person(name, age, job) { this.name = name; this.age = age; this.job = job; this.sayName = function () { console.log(this.name); } } //{name: "jaychou", age: 34, job: "singer", sayName: ?} var person1 = new Person("jaychou",34,"singer"); //{name: "xiaoming", age: 15, job: "student", sayName: ?} var person2 = new Person("xiaoming",15,"student");
與之前對比:①沒有顯式地創建對象,②直接將屬性和方法賦給了 this 對象,③沒有 return 語句
使用new操作符調用Person構造函數后:
創建一個新對象
將構造函數的作用域賦給新對象(this指向了這個新對象)
執行構造函數中的代碼(為新對象添加屬性和方法)
返回新對象
類型的標識有了,構造函數模式的主要缺點是:每個方法都要在每個實例上重新創建一遍(函數也是對象的一種,導致重復創建對象了)。
3.原型模式①基本
function Person() { } Person.prototype.name = "jaychou"; Person.prototype.age = 34; Person.prototype.job = "singer"; Person.prototype.sayName = function () { console.log(this.name); } var person1 = new Person(); person1.sayName();//jaychou var person2 = new Person(); person2.sayName();//jaychou console.log(person1.sayName == person2.sayName);//true
創建的每個函數都有一個prototype(原型)屬性,這個屬性是一個指針,指向一個對象,而這個對象的用途是包含可以由特定類型的所有實例共享的屬性和方法。
//{name: "jaychou", age: 34, job: "singer", sayName: ?, constructor: ?} console.log(Person.prototype);
我們打印了Person.prototype,默認情況下會有一個constructor屬性,這個屬性指向函數(在這里就是指向構造函數),其他的name,age,job,sayName屬性和方法都是通過Person.prototype.添加進去的,所以由構造函數Person創建的實例都會包含這些屬性和方法,它們是共享的。**
而且實例的內部包含一個指針(內部屬性),指向構造函數的原型對象:[[Prototype]],在Firefox、Safari和Chrome中支持屬性__proto__:
//{name: "jaychou", age: 34, job: "singer", sayName: ?, constructor: ?} console.log(person1.__proto__); console.log(Person.prototype == person1.__proto__);//true //打印對象的constructor:在這里person1的constructor就是Person console.log(person1.constructor == Person);//true
另外,可以通過 isPrototypeOf 方法來確定對象之間是否存在原型關系:
console.log(Person.prototype.isPrototypeOf(person1));//true
還有,可以通過 Object.getPrototype 返回對象的原型:
//打印對象的原型:{name: "jaychou", age: 34, job: "singer", sayName: ?, constructor: ?} console.log(Object.getPrototypeOf(person1)); console.log(Object.getPrototypeOf(person1).name);//jaychou
之前也有提及,查詢屬性和方法時先在當前實例中找,沒有的話就到實例的原型鏈中找,而在實例中添加的屬性會屏蔽原型中的同名屬性。
②更簡單的原型語法
上一個例子中每增加一個屬性和方法就要敲一遍Person.prototype,比較麻煩,所以更簡單的做法是用一個包含所有屬性和方法的對象字面量來重寫整個原型對象:
Person.prototype = { name:"jaychou", age:34, job:"singer", sayName:function () { console.log(this.name); } } var person3 = new Person(); console.log(Person.prototype.constructor == Person);//false console.log(Person.prototype.constructor == Object);//true console.log(person3.constructor == Person);//false console.log(person3.constructor == Object);//true
上面的代碼將 Person.prototype設置為等于一個以對象直接量形式創建的新對象,結果就導致了constructor 屬性不再指向 Person 了,因為本質上完全重寫了默認的prototype對象,因此constructor 屬性也就變成了新對象的constructor屬性(指向Object構造函數),不再指向 Person函數。看下面的例子就更清楚了:
function Teacher(){ }; var tea1 = new Teacher(); Person.prototype = tea1; //true:因為Person.prototype被重寫成tea1,tea1的constructor自然指向了Teacher構造函數 console.log(Person.prototype.constructor == Teacher);
所以,如果constrctor的值很重要,可以在上面代碼的基礎上手動加回去,最好仿照原生的把constructor屬性設置成不可枚舉的:
Object.defineProperty(Person.prototype, "constructor", { enumerable: false, value: Person }); console.log(Person.prototype.constructor == Person);//true
注意一個問題: 如果某個實例已經被創建了之后,再直接重寫了構造函數的原型對象,那么之前已被創建好的對象內部的原型指針還是指向舊的原型,如果舊實例調用了新原型里面定義的方法,就會報錯了。所以重寫函數的原型對象時要特別注意這個問題。
③原型對象模式的問題
以上的創建對象在原型里共享了所有的屬性和方法,對于方法還好,對于大多數情況下屬性共享帶來的問題就顯而易見了。
4.組合使用構造函數模式和原型模式創建自定義類型的最常見方式,就是組合使用構造函數模式與原型模式。構造函數模式用于定義實例屬性,而原型模式用于定義方法和共享的屬性。
function Person(name, age, job) { this.name = name; this.age = age; this.job = job; } Person.prototype = { constructor:Person, sayName:function () { console.log(this.name); } } var person1 = new Person("jaychou",34,"singer"); var person2 = new Person("xiaoming",15,"student"); person1.sayName();//jaychou person2.sayName();//xiaoming console.log(person1.sayName === person2.sayName);//true5.動態原型模式(最常用)
對上面的例子進行視覺上的美化,希望把所有的內容都放在構造函數里面,可以通過檢查某個應該存在的方法或屬性是否存在,來決定是否需要初始化原型:
function Person(name, age, job) { this.name = name; this.age = age; this.job = job; //添加共享的方法或屬性 if(typeof this.sayName != "function"){ Person.prototype.sayName = function () { console.log(this.name); }; Person.prototype.sayJob = function () { console.log(this.job); } } }
注意: 使用動態原型模式時,不能使用對象直接量重寫原型,原因上面已經解釋過了,重寫會切斷了舊實例和新原型之間的聯系。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/108370.html
摘要:而且在超類型的原型中定義的方法,對子類型而言也是不可見的,結果所有類型都只能使用構造函數模式。在主要考慮對象而不是自定義類型和構造函數的情況下,這個模式也不錯。 寫在前面 注:這個系列是本人對js知識的一些梳理,其中不少內容來自書籍:Javascript高級程序設計第三版和JavaScript權威指南第六版,感謝它們的作者和譯者。有發現什么問題的,歡迎留言指出。 1.原型鏈 將原型鏈作...
摘要:跨域請求詳解從繁至簡前端掘金什么是為什么要用是的一種使用模式,可用于解決主流瀏覽器的跨域數據訪問的問題。異步編程入門道典型的面試題前端掘金在界中,開發人員的需求量一直居高不下。 jsonp 跨域請求詳解——從繁至簡 - 前端 - 掘金什么是jsonp?為什么要用jsonp?JSONP(JSON with Padding)是JSON的一種使用模式,可用于解決主流瀏覽器的跨域數據訪問的問題...
摘要:關鍵字計算為當前執行上下文的屬性的值。毫無疑問它將指向了這個前置的對象。構造函數也是同理。嚴格模式無論調用位置,只取顯式給定的上下文綁定的,通過方法傳入的第一參數,否則是。其實并不屬于特殊規則,是由于各種事件監聽定義方式本身造成的。 this 是 JavaScript 中非常重要且使用最廣的一個關鍵字,它的值指向了一個對象的引用。這個引用的結果非常容易引起開發者的誤判,所以必須對這個關...
摘要:同理,原型鏈也是實現繼承的主要方式的只是語法糖。原型對象也可能擁有原型,并從中繼承方法和屬性,一層一層以此類推。利用構造函數小明張三張三小明缺點每次實例化都需要復制一遍函數到實例里面。寄生構造函數模式只有被類出來的才能用。 showImg(https://segmentfault.com/img/bVbo4hv?w=1800&h=1000); 引言 最近又攀登了一下JS三座大山中的第二...
摘要:作用域分類作用域共有兩種主要的工作模型。換句話說,作用域鏈是基于調用棧的,而不是代碼中的作用域嵌套。詞法作用域詞法作用域中,又可分為全局作用域,函數作用域和塊級作用域。 一篇鞏固基礎的文章,也可能是一系列的文章,梳理知識的遺漏點,同時也探究很多理所當然的事情背后的原理。 為什么探究基礎?因為你不去面試你就不知道基礎有多重要,或者是說當你的工作經歷沒有亮點的時候,基礎就是檢驗你好壞的一項...
閱讀 1599·2023-04-26 02:43
閱讀 2997·2021-11-11 16:54
閱讀 1344·2021-09-23 11:54
閱讀 1165·2021-09-23 11:22
閱讀 2359·2021-08-23 09:45
閱讀 845·2019-08-30 15:54
閱讀 3094·2019-08-30 15:53
閱讀 3184·2019-08-30 15:53