摘要:但是,它是用構造器調用來這樣做的。本質是因為讓函數調用變成了構造器調用假設有一個需要繼承的一個類型添加一個屬性如何檢查類繼承自省錯誤方法只能查詢的祖先。
名詞解析
字面量
對象字面量 var a = {};
數組字面量 var arr = [1,2,3];
正則表達式字面量 var reg = /[a-z]/g;
屬性描述符
configurable:fasle是一個單向操作,同時阻止使用 delete 操作符移除既存屬性的能力。
enumerable控制是否在屬性枚舉操作中出現,比如 for..in 循環。
writeable:false時,會阻止同名屬性在 [[Prototype]] 鏈的低層被創建(遮蔽)。不能使用 = 賦值,而必須使用 Object.defineProperty(..)
如果一個 foo 在 [[Prototype]] 鏈的高層某處被找到,而且它是一個 setter,那么這個 setter 總是被調用。沒有 foo 會被添加到(也就是遮蔽在)myObject 上,這個 foo setter 也不會被重定義。
對象常量: writable:false 與 configurable:false 組合
防止擴展: Object.preventExtensions(..)
封印: Object.seal(..) 等同于 防止擴展+configurable:false
凍結: Object.freeze(..) 等同于 封印+writable:false
例:創建一個可寫的,可枚舉的,可配置的屬性p
var o2 = {}; Object.defineProperties(o2, { "p": {value: 2, writable: true, enumerable: true, configurable: true }, }); Object.getOwnPropertyDescriptor( o2, "p" );
o2 = Object.create({}, { p: {value: 2, writable: true, enumerable: true, configurable: true }, }); o2.hasOwnProperty( "p" )原型
prototype(顯式原型)
所有的函數默認都會得到一個公有的,不可枚舉的屬性,稱為 prototype,它可以指向任意的對象。__proto__是每個對象都有的一個屬性,而prototype是函數才會有的屬性。(通過Function.prototype.bind方法構造出來的函數是個例外,它沒有prototype屬性。)
[[Prototype]](隱式原型,非標準寫法__proto__)
如果默認的 [[Get]] 操作不能直接在對象上找到被請求的屬性,那么它會沿著對象的 [[Prototype]] 鏈 繼續處理。
所有的對象(包括函數)都有一個私有屬性(b.__proto__)指向它的原型對象(Foo.prototype)。該原型對象也有一個自己的原型對象(Object.__proto__) ,層層向上直到一個對象的原型對象為null。其中,下面3種隱式原型表述在本文中相同
Person.__proto__ 非標準
Object.getPrototypeOf(Person) 標準
[[Prototype]] 概念
原型圖解 1.首先了解內置對象基本內置對象 String ,Number ,Boolean ,Object ,Function ,Array
其他內置對象 Date,RegExp,Error
這些對象的[[Prototype]]都指向Function.prototype(Object.getPrototypeOf(String) === Function.prototype)
Function.prototype是唯一一個typeof Xxx.prototype==="function"的prototype。其它的構造器的prototype都是"object".
Object.prototype.constructor === Object Object.prototype.__proto__ === null Function.prototype.constructor === Function Function.prototype.__proto__ === Object.prototype2.其次試著定義一個函數,看看發生什么
定義一個函數(function Foo)會創建原型對象(Object Foo.prototype)。
constructor(構造器) 是在前面 用 new 關鍵字調用的任何函數。
function Foo(){} Foo.__proto__ === Function.prototype Foo.prototype.constructor === Foo//function Foo(){} Foo.prototype.__proto__ === Object.prototype3.接著new一下,也許應該叫委托
new會沿者 [[Prototype]] 鏈向上找到.constructor
new Foo() 得到一個新對象(我們叫他 a),這個新對象 a 內部地被 [[Prototype]] 鏈接至 Foo.prototype 對象。
var b = new Foo()//new作用:構建一個被鏈接到另一個對象的對象,外加這個函數要做的其他任何事。 b.__proto__===Foo.prototype4.instanceof 和原型鏈什么關系
object instanceof constructor用于測試constructor.prototype是否出現在object的原型鏈中的任何位置。
//像這樣 object.__proto__===constructor.prototype? object.__proto__.__proto__===constructor.prototype? object.__proto__.__proto__.__proto__===constructor.prototype? ...
b instanceof Foo//true b instanceof Object//true Foo.prototype.isPrototypeOf(b)//true Object.prototype.isPrototypeOf(b)//true Foo.prototype = {}; b instanceof Foo//false b instanceof Object//true Foo.prototype.isPrototypeOf(b)//false Object.prototype.isPrototypeOf(b)//true‘類’與‘繼承’ ES5
大家模擬了很多繼承的方法,但本質上都是兩種的變體:
1.原型繼承原型繼承,本質是兩個對象間建立鏈接,一個對象將對屬性/函數的訪問 委托 到另一個對象上。
1.1使用 new
// 假設有一個需要繼承的一個類型 Animal function Cat() {} Cat.prototype = new Animal // 添加一個屬性 Cat.prototype.name = "cat"
Bar.prototype = new Foo() 確實 創建了一個新的對象,這個新對象也的確鏈接到了我們希望的Foo.prototype。但是,它是用 Foo(..) “構造器調用”來這樣做的。如果這個函數有任何副作用(比如logging,改變狀態,注冊其他對象,向 this添加數據屬性,等等),這些副作用就會在鏈接時發生(而且很可能是對錯誤的對象!),而不是像可能希望的那樣,僅最終在 Bar()的“后裔”被創建時發生。于是,我們剩下的選擇就是使用 Object.create(..) 來制造一個新對象,這個對象被正確地鏈接,而且沒有調用 Foo(..)時所產生的副作用。一個輕微的缺點是,我們不得不創建新對象,并把舊的扔掉,而不是修改提供給我們的默認既存對象。
1.2使用Object.create()
function Shape() {this.x = 0;this.y = 0;}// 父類 Shape.prototype.move = function(x, y) {this.x += x;this.y += y;};// 父類的方法 function Rectangle() {Shape.call(this);} //子類 Rectangle.prototype = Object.create(Shape.prototype);// 子類續承父類 Rectangle.prototype.constructor = Rectangle; var rect = new Rectangle(); rect instanceof Rectangle; // true rect instanceof Shape; // true
Object.create()做了什么?
Object.create=function (o){ function F() {} F.prototype = o; return new F(); }
兩種操作將Bar.prototype 鏈接至 Foo.prototype:
// ES6 以前 // 扔掉默認既存的 `Bar.prototype` Bar.prototype = Object.create( Foo.prototype ); // ES6+ // 修改既存的 `Bar.prototype` Object.setPrototypeOf( Bar.prototype, Foo.prototype );2.構造繼承
構造繼承,為了符合表面上我們用 new 調用它,而且我們觀察到它“構建”了一個對象。
本質是因為 new 讓函數調用變成了“構造器調用”
// 假設有一個需要繼承的一個類型 Animal function Cat(name){ Animal.call(this) // 添加一個屬性 this.name = name || "cat" }如何 檢查“類”繼承/自省:
錯誤方法:a instanceof Foo
instanceof 只能查詢 a 的“祖先”。
勉強正確方法:用來檢查 o1 是否關聯到(委托至)o2 的幫助函數
function isRelatedTo(o1, o2) { function F(){} F.prototype = o2; return o1 instanceof F; }
正確方法:Foo.prototype.isPrototypeOf( a )
isPrototypeOf(..) 回答的問題是:在 a 的整個 [[Prototype]] 鏈中,Foo.prototype 出現過嗎?
function A(name){ this.name=name; } A.prototype.sayName=function(){ console.log(this.name); } function B(age){ this.age=age; }
//原型繼承 B.prototype=new A("mbj"); //被B的實例共享 var foo=new B(18); foo.age; //18,age是本身攜帶的屬性 foo.name; //mbj,等價于foo.__proto__.name foo.sayName(); //mbj,等價于foo.__proto__.proto__.sayName() foo.toString(); //"[object Object]",等價于foo.__proto__.__proto__.__proto__.toString(); //構造函數繼承
原型繼承缺點:
1.所有子類共享父類實例,如果某一個子類修改了父類,其他的子類在繼承的時候,會造成意想不到的后果。
2.構造子類實例的時候,不能給父類傳遞參數。
//構造函數繼承,避免了原型繼承缺點 function B(age,name){ this.age=age;A.call(this,name); } var foo=new B(18,"wmy"); foo.name; //wmy foo.age; //18 foo.sayName(); //undefined
構造函數繼承缺點:
1.父類的prototype中的函數不能復用
//原型繼承+構造函數繼承 function B(age,name){ this.age=age;A.call(this,name); } B.prototype=new A("mbj"); var foo=new B(18,"wmy"); foo.name; //wmy foo.age; //18 foo.sayName(); //wmy
結合了上述兩種方式的優點,但占用空間更大。
ES6在 ES2015/ES6 中引入了class關鍵字,但只是語法糖,JavaScript 仍然是基于原型的。
注意:函數聲明會提升,類聲明不會。
//類聲明 class Rectangle { constructor(height, width,x, y) {this.height = height,this.width = width,this.x = x,this.y = y;} get area() {return this.calcArea()} calcArea() {return this.height * this.width;} static distance(a, b) {return Math.hypot(a.x - b.x, a.y - b.y);} } const square1 = new Rectangle(10, 10,5,5); const square2 = new Rectangle(10, 10,6,6); console.log(square1.area); console.log(Rectangle.distance(square1 , square2 )); //類表達式 let Rectangle = class Rectangle {constructor() {}};
extend 實現繼承
class Rectangle extends Shape { move() { super.move();//super 關鍵字用于調用對象的父對象上的函數 } }
常規(非可構造)對象 實現繼承
var Animal = {}; class Cat {} Object.setPrototypeOf(Cat.prototype, Animal);
混入(Mixin)
Object.assign(target, ...sources) 方法只會拷貝源對象(...sources)自身的并且可枚舉的屬性到目標對象(target)。
function MyClass() { SuperClass.call(this) OtherSuperClass.call(this) } // 繼承一個類 MyClass.prototype = Object.create(SuperClass.prototype) // 混合其它 Object.assign(MyClass.prototype, OtherSuperClass.prototype) //Object.assign 會把 OtherSuperClass原型上的函數拷貝到 MyClass原型上, //使 MyClass 的所有實例都可用 OtherSuperClass 的方法 // 重新指定constructor MyClass.prototype.constructor = MyClass MyClass.prototype.myMethod = function() {}// do a thing
當繼承的函數被調用時,this 指向的是當前繼承的對象,而不是繼承的函數所在的原型對象。
var o = { a: 2, m: function(){ return this.a + 1; } }; var p = Object.create(o); // p是一個繼承自 o 的對象 p.a = 4; // 創建 p 的自身屬性 a console.log(p.m()); // 5
派生
class MyArray extends Array { static get [Symbol.species]() { return Array; } }面向委托的設計
var Widget = { init: function(width,height){ this.width = width || 50; this.height = height || 50; this.$elem = null; }, insert: function($where){ if (this.$elem) { this.$elem.css( { width: this.width + "px", height: this.height + "px" } ).appendTo( $where ); } } }; var Button = Object.create( Widget ); Button.setup = function(width,height,label){ // delegated call this.init( width, height ); this.label = label || "Default"; this.$elem = $( "
Object.create 和 new 區別
js中__proto__和prototype的區別和關系?
你不懂JS: this 與對象原型
行為委托
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/53406.html
摘要:但是,它是用構造器調用來這樣做的。本質是因為讓函數調用變成了構造器調用假設有一個需要繼承的一個類型添加一個屬性如何檢查類繼承自省錯誤方法只能查詢的祖先。 名詞解析 字面量對象字面量 var a = {};數組字面量 var arr = [1,2,3];正則表達式字面量 var reg = /[a-z]/g; 屬性描述符 configurable:fasle是一個單向操作,同時阻止使...
摘要:但是,它是用構造器調用來這樣做的。本質是因為讓函數調用變成了構造器調用假設有一個需要繼承的一個類型添加一個屬性如何檢查類繼承自省錯誤方法只能查詢的祖先。 名詞解析 字面量對象字面量 var a = {};數組字面量 var arr = [1,2,3];正則表達式字面量 var reg = /[a-z]/g; 屬性描述符 configurable:fasle是一個單向操作,同時阻止使...
摘要:捕獲所有參數綁定當一個函數用作構造函數時使用關鍵字,它的被綁定到正在構造的新對象。使用來調用函數,或者說發生構造函數調用時,會自動執行下面的操作你不知道的創建或者說構造一個全新的對象。在箭頭函數中,與封閉詞法上下文的保持一致。 this 實際上是在函數被調用時發生的綁定,它指向什么完全取決于函數的調用位置(也就是函數的調用方法)。 四條規則:(你不知道的JS) 1. 默認綁定 func...
摘要:捕獲所有參數綁定當一個函數用作構造函數時使用關鍵字,它的被綁定到正在構造的新對象。使用來調用函數,或者說發生構造函數調用時,會自動執行下面的操作你不知道的創建或者說構造一個全新的對象。在箭頭函數中,與封閉詞法上下文的保持一致。 this 實際上是在函數被調用時發生的綁定,它指向什么完全取決于函數的調用位置(也就是函數的調用方法)。 四條規則:(你不知道的JS) 1. 默認綁定 func...
閱讀 2797·2023-04-25 23:08
閱讀 1583·2021-11-23 09:51
閱讀 1564·2021-10-27 14:18
閱讀 3115·2019-08-29 13:25
閱讀 2831·2019-08-29 13:14
閱讀 2895·2019-08-26 18:36
閱讀 2193·2019-08-26 12:11
閱讀 811·2019-08-26 11:29