字面量
對象字面量 var a = {};
數組字面量 var arr = [1,2,3];
正則表達式字面量 var reg = /[a-z]/g;
javascript
屬性描述符css
configurable:fasle
是一個單向操做,同時阻止使用 delete
操做符移除既存屬性的能力。enumerable
控制是否在屬性枚舉操做中出現,好比 for..in
循環。writeable:false
時,會阻止同名屬性在 [[Prototype]] 鏈的低層被建立(遮蔽)。不能使用 = 賦值,而必須使用 Object.defineProperty(..) writable:false
與 configurable:false
組合Object.preventExtensions(..)
Object.seal(..)
等同於 防止擴展+configurable:falseObject.freeze(..)
等同於 封印+writable:false例:建立一個可寫的,可枚舉的,可配置的屬性pjava
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屬性。)git
[[Prototype]](隱式原型,非標準寫法__proto__)
若是默認的 [[Get]] 操做不能直接在對象上找到被請求的屬性,那麼它會沿着對象的 [[Prototype]] 鏈 繼續處理。
全部的對象(包括函數)都有一個私有屬性(b.__proto__)指向它的原型對象(Foo.prototype)。該原型對象也有一個本身的原型對象(Object.__proto__) ,層層向上直到一個對象的原型對象爲null。其中,下面3種隱式原型表述在本文中相同github
基本內置對象 String ,Number ,Boolean ,Object ,Function ,Array
其餘內置對象 Date,RegExp,Error正則表達式
Object.getPrototypeOf(String) === 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.prototype
function
Foo)會建立原型對象(Object
Foo.prototype)。function Foo(){} Foo.__proto__ === Function.prototype Foo.prototype.constructor === Foo//function Foo(){} Foo.prototype.__proto__ === Object.prototype
new
會沿者 [[Prototype]] 鏈向上找到.constructorvar b = new Foo()//new做用:構建一個被連接到另外一個對象的對象,外加這個函數要作的其餘任何事。 b.__proto__===Foo.prototype
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
你們模擬了不少繼承的方法,但本質上都是兩種的變體:app
原型繼承,本質是兩個對象間創建連接,一個對象將對屬性/函數的訪問 委託 到另外一個對象上。
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 );
構造繼承,爲了符合表面上咱們用 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
結合了上述兩種方式的優勢,但佔用空間更大。
在 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 = $( "<button>" ).text( this.label ); }; Button.build = function($where) { // delegated call this.insert( $where ); this.$elem.click( this.onClick.bind( this ) ); }; Button.onClick = function(evt) { console.log( "Button '" + this.label + "' clicked!" ); }; $( document ).ready( function(){ var $body = $( document.body ); var btn1 = Object.create( Button ); btn1.setup( 125, 30, "Hello" ); var btn2 = Object.create( Button ); btn2.setup( 150, 40, "World" ); btn1.build( $body ); btn2.build( $body ); } );
Object.create 和 new 區別
js中__proto__和prototype的區別和關係?
你不懂JS: this 與對象原型
行爲委託