摘要:屬性每個函數默認有屬性方法返回的函數除外,其值為構造函數創建對象繼承的對象。其思路使用原型鏈實現原型屬性和方法的繼承通過借用構造函數實現實例屬性繼承。
1 類和模塊
每個獨立的JavaScript對象都是一個屬性的集合,獨立對象間沒有任何關系
ES5中的類是基于原型繼承實現的:如果兩個對象從同一個原型對象繼承屬性,稱兩個對象為同一個類的實例。r instanceof Range.prototype操作符是檢查對象r是否繼承自Range.prototype
JavaScript中的類可以動態繼承
1.1 類和原型JavaScript中所有類的實例都從同一個原型對象上繼承屬性。原型對象是函數的prototype屬性,每個函數都有。Function.bind()方法返回的函數沒有prototype屬性。
工廠方法:顯式創建一個對象,并將其作為返回值
1.2 類和構造函數構造函數用來初始化新創建的對象,每個新創建對象都繼承了構造函數的prototype屬性指向的原型對象。
關于構造函數的約定:
構造函數的首字母大寫;
構造函數必須通過new關鍵字調用才能創建對象,否則與普通函數無異;
原型對象必須通過Range.prototype引用
通過new關鍵字調用構造函數時,先創建一個空對象,將構造函數的this綁定到該對象;然后利用構造函數初始化該對象
function Range(from, to) { this.from = from; this.to = to; } // 新創建的所有對象都繼承這個原型對象 Range.prototype = { // 重置原型對象的constructor屬性 // 判斷x是否在范圍之內 includes: function(x) {return this.from <= x && x <= this.to;}, // 對于范圍內的整數調用一次f方法 foreach: function(f) { for(var x=Math.ceil(this.from); x<=this.to; x++) { f(x); } }, toString: function() {return "(" + this.from + "..." + this.to + ")";} }; var r = new Range(1, 3); console.log(r instanceof Range); // true r.foreach(console.log); // 1 2 3 console.log(Range.prototype.constructor); // 原型對象的constructor屬性被重置,不再指向Range()1.3 構造函數和類的標識
原型對象是類的唯一標識:當且僅當兩個對象繼承自同一個原型對象時,他們才屬于同一個類的實例。
r instanceof Range.prototype操作符是檢查對象r是否繼承自Range.prototype
1.4 constructor屬性原型對象中的constructor屬性是構造函數的引用,但如果直接用字面量對象重寫Range.prototype,新對象中沒有constructor屬性,會默認指向Object()構造函數。
重置constructor屬性指向的方法:
// 重置constructor屬性的方法: // 1 顯式為原型添加一個構造函數屬性 Range.prototype = { constructor: Range, // 顯式增加指向Range的constructor屬性 includes: function(x) {return this.from <= x && x <= this.to;}, foreach: function(f) { for(var x=Math.ceil(this.from); x<=this.to; x++) { f(x); } }, toString: function() {return "(" + this.from + "..." + this.to + ")";} }; // 2 依次為原型對象添加方法 Range.prototype.includes = function(x) { return this.from <= x && x <= this.to; }; Range.prototype.foreach = function(a) { for(var a=Math.ceil(this.from); a<=this.to; a++) { f(a); } }; Range.prototype.toString = function(x) { return "(" + this.from + "..." + this.to + ")"; };2 類的補充 2.1 JavaScript中的函數
JavaScript中類中的函數以值的形式出現,如果一個屬性值是函數,稱其為方法。
類的三種對象:
構造函數對象:定義類名,任何添加到構造函數對象本身的屬性都是類字段或類方法
原型對象:原型對象的所有屬性都被實例對象繼承。
實例對象:類的每個實例對象都是獨立對象,直接為每個實例對象定義的屬性不會被其他實例共享。實例方法與屬性
/* * Complex用于描述復數類 * 復數是實數與虛數之和,虛數i的平方為-1 */ function Complex(real, imaginary) { if(isNaN(real) || isNaN(imaginary)) { // 確保兩個參數都是數字 throw new TypeError(); } this.r = real; this.i = imaginary; } // 兩個復數對象之和為一個新的復數對象,使用this代表當前復數對象 Complex.prototype.add = function(that) { return new Complex(this.r + that.r, this.i + that.i); }; Complex.prototype.multiply = function(that) { return new Complex(this.r * that.r - this.i * that.i, this.r * that.i + this.i * that.r); }; // 復數對象的模:原點(0, 0)到復平面的距離 Complex.prototype.mag = function() { return Math.sqrt(this.r * this.r + this.i * this.i) }; // 復數求負運算 Complex.prototype.neg = function() { return new Complex(-this.r, -this.i); }; // 將復數轉化為字符串 Complex.prototype.toString = function() { return "{" + this.r + "," + this.i + "}"; }; // 當前復數對象是否與另外一個復數對象值相等 Complex.prototype.equal = function(that) { return that != null && that.constructor === Complex && this.r === that. r && this.i === that.i; }; // 類屬性 Complex.ZERO = new Complex(0, 0); Complex.ONE = new Complex(1, 0); Complex.I = new Complex(0, 1); // 類方法:將實例對象toString()方法返回的字符串解析為一個Complex對象 // 或拋出類型錯誤異常 Complex._format = /^{([^,]+),([^}]+)}$/; Complex.parse = function(s) { try { // 假設解析成功 var m = Complex._format.exec(s); return new Complex(parseFloat(m[1]), parseFloat(m[2]); } catch(e) { throw new TypeError("can"t parse " + s + "as a complex number"); } };2.2 類的擴充
JavaScript中基于原型對象的繼承機制是動態的:原型對象的屬性發生變化,會影響所有繼承該原型對象的實例對象,即使實例對象已經定義。(原理應該是實例對象中只是保存指向原型對象的引用)
2.3 類和類型不推薦直接在prototype對象上添加屬性或方法,ES5之前不能設置添加的屬性和方法為不可枚舉,會被for-in循環遍歷,ES5中通過Object.defineProperty()方法設置對象屬性。
使用typeof操作符可以區分基本數據類型:undefined、null、number、string、function、object和boolean。要區分數組,有兩種方法:
ES5中的Array.isArray()方法
typeof o === "object" && Object.prototype.toString.call(o).slice(8, -1) === "Array"
區分自定義類型使用typeof操作符并不能區分自定義類型:instanceof操作符、constructor屬性和構造函數名稱三種方式可以區分自定義類型,但各自與各自的缺點
1 instanceof操作符如果對象o繼承自對象c.prototype,o instanceof c返回true,缺點是不能返回類名稱,只能檢測對象是否屬于某個類。其中c.prototype可以是原型鏈上的對象
使用c.prototype.isPrototypeOf(o)方法可以檢測o繼承的原型鏈上是否有原型對象c.prototype。
2 constructor屬性每個函數默認有prototype屬性(bind()方法返回的函數除外),其值為構造函數創建對象繼承的對象。原型對象constructor屬性指向構造函數。
缺點是并非所有對象都帶有constructor屬性。
function typeAndValue(x) { if(x == null || x == undefined) { return ""; //null和undefined沒有構造函數 } switch (x.constructor) { case Number: return "Number: " + x; // 原始類型 case String: return "String: " + x; case Date: return "Date: " + x; // 內置類型 case RegExp: return "RegExp: " + x; case Complex: return "Complex: " + x; // 自定義類型 } };3 構造器函數的名稱
在多個執行上下文中都存在構造器函數的副本時,instanceof操作符與constructor屬性檢測結果會出錯,但是構造器函數本身的名稱沒有改變,可以作為標識
4 鴨子類型可以向鴨子一樣走路、游泳并且嘎嘎叫的鳥就是鴨子。
以部分特征屬性來描述一類對象(關注對象能做什么,弱化對象的類型)
3 繼承許多OO語言支持接口繼承與實現繼承。但是ECMAScript沒有函數簽名,只支持實現繼承,繼承的實現主要依賴于原型鏈
3.1 原型鏈原型鏈式ECMAScript實現繼承的主要方法:子類的原型對象是父類的實例對象。
構造函數、原型與實例的關系:
構造函數的prototype屬性指向原型對象;
原型對象的constructor屬性指向構造函數;
實例的__proto__屬性指向原型對象,實例與構造函數沒有直接聯系
將SubType的原型重寫為SuperType的實例對象,新原型對象作為SuperType一個實例擁有全部屬性和方法,內部__proto__屬性指向SuperType的原型。
instance指向SubType的原型,SubType的原型指向SuperType的原型。形成一條原型鏈:原型鏈的搜索機制。先搜索實例對象instance,再搜索Subype的原型,再搜索SuperType的原型,依次向上
// 父類 function SuperType() { this.property = true; } SuperType.prototype.getSuperValue = function() { return this.property; } // 子類 function SubType() { this.subProperty = false; } // 子類的原型對象是父類的實例對象(其__proto__屬性指向父類的原型對象) SubType.prototype = new SuperType(); SubType.prototype.getSubValue = function() { return this.subProperty; } var instance = new SubType(); instance.getSuperValue(); // true,子類調用父類的方法
注:實例對象instance的原型的構造函數不是SubType,而是SuperType。因為重置SubType.prototype的指向,但是沒有重置construtor的指向
console.log(instance.__proto__.constructor); // function SuperType() {native code}1原型鏈末端
所有引用類型都繼承自Object,函數的默認原型是Object的實例,默認原型內包含指向Object.prototype的引用,這是所有自定義類型都會繼承toString()、valueOf()等方法的根本原因
Object.prototype沒有原型,其原型為null,即
Object.prototype.__proto__ === null; // true
2原型與實例的關系Object.prototype.__proto__是原型鏈的末端,出口
使用instanceof操作符與isPrototypeOf()方法:
instance instanceof Object; // true instance instanceof SuperType; // true instance instanceof SubType; // true Object.prototype.isPrototypeOf(instance); // true SuperType.prototype.isPrototypeOf(instance); // true SubType.prototype.isPrototypeOf(instance); // true3 謹慎定義子類中方法的位置
如果在子類中定義新方法或者重寫父類的方法,必須子類替換原型語句SubType.prototype = new SuperType();之后,否則不起作用。
function SuperType() { this.property = true; } SuperType.prototype.getSuperValue = function() { return this.property; } // 子類 function SubType() { this.subProperty = false; } // 子類的原型對象是父類的實例對象(其__proto__屬性指向父類的原型對象) SubType.prototype = new SuperType(); // 添加新方法 SubType.prototype.getSubValue = function() { return this.subProperty; } // 重寫父類中的方法 SuperType.prototype.getSuperValue = function() { return false; } var instance = new SubType(); instance.getSuperValue(); // false
在使用原型鏈實現繼承時,不能使用字面量方式創建原型對象,否則會切斷原型鏈,將原型對象重行指向字面量對象
function SuperType() { this.property = true; } SuperType.prototype.getSuperValue = function() { return this.property; } // 子類 function SubType() { this.subProperty = false; } // 子類的原型對象是父類的實例對象(其__proto__屬性指向父類的原型對象) SubType.prototype = new SuperType(); // 使用字面量方式添加新方法,使上一行代碼無效 SubType.prototype = { getSubValue: function() { return this.subProperty; }, getSuperValue: function() { return false; } } var instance = new SubType(); instance.getSuperValue(); // false4 原型鏈的問題
對于包含引用類型值的原型對象:所有勢力共享原型的屬性,如果其屬性值是引用類型:在一個實例上修改該引用類型的值,會體現在所有的實例對象上 。-----所以需要將引用類型值定義在構造函數中,而非原型對象中。
function SuperColor() { this.color = ["red", "blue"]; } function SubColor() { } SubColor.prototype = new SuperColor(); // 子類原型定義為父類的實例,但是color屬性值為引用類型 var col1 = new SubColor(); col1.color.push("green"); console.log(col1.color); // ["red", "blue", "green"] // 注意,所有的實例對象的color都改變 var col2 = new SubColor(); console.log(col2.color); // ["red", "blue", "green"]
沒有辦法在不影響所有對象實例的情況下,向父類的構造函數傳遞參數。
基于上述2點原因,很少多帶帶使用原型鏈
3.2 借用構造函數constructor stealing在子類中,利用創建的對象,以方法的形式調用父類構造器函數,父類構造器函數僅用于初始化子類中創建的對象
基本思想:在子類構造函數內部調用父類構造函數。因為函數只是特定環境中執行代碼的對象,可以使用call()、apply()方法在新創建對象上執行構造函數
1.通過new調用SubColor():本質先創建一個對象,將其綁定到this,再利用this調用函數SuperColor(),設置this.color屬性值
2.每次調用new SubColor()創建的都是獨立的對象,所以不影響
function SuperColor() { this.color = ["red", "blue"]; } function SubColor() { // 繼承SuperColor // 使用新創建的對象this來調用SuperColor()函數,設置this.color屬性值 // 每次調用new SubColor()創建的都是獨立的對象,所以不影響 SuperColor.call(this); } var col3 = new SubColor(); col1.color.push("green"); console.log(col3.color); // ["red", "blue", "green"] var col4 = new SubColor(); console.log(col4.color); // ["red", "blue"]傳遞參數
通過借用構造器函數可以向父類構造函數傳遞參數。將參數掛載在call()或apply()方法中:將父類構造器哈數僅用作初始化對象用
function SuperType(name) { this.name = name; } function SubType() { // 繼承SuperType,同時傳遞參數"Tracy" SuperType.call(this, "Tracy"); this.age = 23; // 實例屬性 } var kyxy = new SubType(); console.log(kyxy.name); // "Tracy" console.log(kyxy.age); // 23借用構造器函數的問題
3.3 組合繼承如果僅僅使用借用構造函數模式,只能講方法都定義在構造函數中,不能復用函數,所以借用構造函數模式很少多帶帶使用
將原型鏈模式與借用構造函數模式組合,發揮二者的長處。其思路:
使用原型鏈實現原型屬性和方法的繼承;通過借用構造函數實現實例屬性繼承。組合繼承避免原型鏈與借用構造函數的缺點,融合優點,是ECMAScript中最常用的的繼承模式
首先使用借用構造函數模式繼承實例屬性
再使用原型鏈模式繼承原型的屬性與方法
借用構造函數模式:利用子類創建空對象,將父類的實例屬性拷貝到子類中。因為每個子類是獨立的對象,所以享有父類的拷貝也是相互獨立的
實例間共享的原型對象中的屬性依然通過原型鏈模式實現
function SuperType(name) { this.name = name; this.color = ["red", "blue"]; } SuperType.prototype.sayName = function() { coonsle.log(this.name); } function SubType(name, age) { // 實例屬性的繼承(不再是引用,而是多帶帶一份拷貝) SuperType.call(this, name); this.age = age; } // 原型屬性與方法的繼承 SubType.prototype = new SuperType(); // 重置原型對象constructor的指向 SubType.prototype.constructor = SubType; SubType.prototype.sayAge = function() { console.log(this.age); } var p1 = new SubType("Kyxy", 23); p1.color.push("black"); p1.sayAge(); // 23 p1.sayName(); // "Kyxy" p1.color; // ["red", "blue", "black"] var p2 = new SubType("Tracy", 23); p2.color; // ["red", "blue"]3.4 總結
ECMAScript中創建對象的模式:
工廠模式
構造函數模式
原型模式
ECMAScript中主要的繼承模式是組合繼承:綜合原型鏈模式與借用構造函數模式的優點。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/81989.html
摘要:異步函數對象接口,包含和兩個成員方法。哈希計數在整個的源碼中都沒有找到和方法的調用,這兩個方法的具體作用是在原生中實現類式繼承和私有屬性一類的功能。 文件結構 utils/HashObject.ts文件:showImg(https://segmentfault.com/img/bVZpuq?w=642&h=472); 首先解釋一下文件結構圖 __extends方法 通過原型對象模擬類...
摘要:類總所周知,不像其他面向對象語言那樣支持類,但是可以通過函數和原型來模擬類。如果你學習過或者其他面向對象語言的話,你會覺得很熟悉。結論下一個版本的會帶來一個更加簡單更加友好的語法來幫助那些從面向對象語言轉過來的開發者的學習。 原文地址:http://www.frontendjournal.com/javascript-es6-learn-important-features-in-a-...
摘要:又將整個文藝類閱讀系統的業務劃分為兩大部分,分別是面向管理員和合作作者的后臺管理系統和面向用戶的移動端,系統的需求分析將圍繞這兩部分進行展開。 效果展示 showImg(https://user-gold-cdn.xitu.io/2018/8/26/16576a709bd02f5f?w=1409&h=521&f=gif&s=30128195); showImg(https://user...
摘要:又將整個文藝類閱讀系統的業務劃分為兩大部分,分別是面向管理員和合作作者的后臺管理系統和面向用戶的移動端,系統的需求分析將圍繞這兩部分進行展開。 效果展示 showImg(https://user-gold-cdn.xitu.io/2018/8/26/16576a709bd02f5f?w=1409&h=521&f=gif&s=30128195); showImg(https://user...
閱讀 3271·2021-11-23 10:09
閱讀 2070·2021-10-26 09:51
閱讀 985·2021-10-09 09:44
閱讀 3913·2021-10-08 10:04
閱讀 2752·2021-09-22 15:14
閱讀 3632·2021-09-22 15:02
閱讀 1071·2021-08-24 10:03
閱讀 1733·2019-12-27 12:14