類和實例是大多數面向對象編程語言的基本概念javascript
類:類是對象的類型模板html
實例:實例是根據類建立的對象
可是,JavaScript
語言的對象體系,不是基於「類」的,而是基於構造函數(constructor
)和原型鏈(prototype
)。了與普通函數區別,構造函數名字的第一個字母一般大寫。java
構造函數的特色有兩個。編程
函數體內部使用了this關鍵字,表明了所要生成的對象實例。segmentfault
生成對象的時候,必需用new命令數組
new
與構造函數new命令自己就能夠執行構造函數,因此後面的構造函數能夠帶括號,也能夠不帶括號。app
下面兩行代碼是等價的。編程語言
var v = new Vehicle(); var v = new Vehicle;
應該很是當心,避免出現不使用new命令、直接調用構造函數的狀況。爲了保證構造函數必須與new命令一塊兒使用,一個解決辦法是,在構造函數內部使用嚴格模式,即第一行加上use strict
。函數
原理:因爲在嚴格模式中,函數內部的this
不能指向全局對象,默認等於undefined
,致使不加new調用會報錯(JavaScript
不容許對undefined
添加屬性)。oop
new命令的原理
建立一個空對象,做爲將要返回的對象實例
將這個空對象的原型,指向構造函數的prototype屬性
將這個空對象賦值給函數內部的this關鍵字
開始執行構造函數內部的代碼
即:
var obj = {}; obj.__proto__ = Base.prototype; Base.call(obj);
構造函數的return
若是構造函數內部有return語句,並且return後面跟着一個對象,new命令會返回return語句指定的對象;不然,就會無論return語句,返回this對象。
若是對普通函數(內部沒有this關鍵字的函數)使用new命令,則會返回一個空對象
這裏遇到了一個問題,問題描述以下普通函數用new測試的時候箭頭函數報錯了
JavaScript
對每一個建立的對象都會設置一個原型,指向它的原型對象。
當咱們用obj.xxx
訪問一個對象的屬性時,JavaScript
引擎先在當前對象上查找該屬性,若是沒有找到,就到其原型對象上找,若是尚未找到,就一直上溯到Object.prototype
對象,最後,若是尚未找到,就只能返回undefined
。
例如,建立一個Array對象:var arr = [1, 2, 3];
其原型鏈是:arr ----> Array.prototype ----> Object.prototype ----> null
Array.prototype
定義了indexOf()、shift()
等方法,所以你能夠在全部的Array
對象上直接調用這些方法。
很容易想到,若是原型鏈很長,那麼訪問一個對象的屬性就會由於花更多的時間查找而變得更慢,所以要注意不要把原型鏈搞得太長。
new Student()
function Student(name) { this.name = name; this.hello = function () { alert('Hello, ' + this.name + '!'); } } var xiaoming= new Student('xiaoming'), xiaohong= new Student('xiaohong');
xiaoming ↘
xiaohong -→ Student.prototype ----> Object.prototype ----> null
xiaojun ↗
用new Student()建立的對象還從原型上得到了一個constructor
屬性,它指向函數Student
自己:
xiaoming.constructor === Student.prototype.constructor; // true Student.prototype.constructor === Student; // true Object.getPrototypeOf(xiaoming) === Student.prototype; // true xiaoming instanceof Student; // true
constructor
constructor屬性的做用,是分辨原型對象到底屬於哪一個構造函數。
function F() {}; var f = new F(); f.constructor === F // true f.constructor === RegExp // false
上面代碼表示,使用constructor屬性,肯定實例對象f的構造函數是F,而不是RegExp。
xiaoming.name //"xiaoming" xiaohong.name //"xiaohong" xiaoming.hello /*function() { alert('Hello, ' + this.name + '!'); } */ xiaohong.hello /*function() { alert('Hello, ' + this.name + '!'); } */ xiaoming.hello === xiaohong.hello //false
xiaoming和xiaohong各自的name不一樣,這是對的,不然咱們沒法區分誰是誰了。
xiaoming和xiaohong各自的hello是一個函數,但它們是兩個不一樣的函數,雖然函數名稱和代碼都是相同的!
若是咱們經過new Student()
建立了不少對象,這些對象的hello函數實際上只須要共享同一個函數就能夠了,這樣能夠節省不少內存。
要讓建立的對象共享一個hello函數,根據對象的屬性查找原則,咱們只要把hello函數移動到xiaoming、xiaohong這些對象共同的原型上就能夠了,也就是Student.prototype
:
修改代碼以下:
function Student(name) { this.name = name; } Student.prototype.hello = function () { alert('Hello, ' + this.name + '!'); }; xiaoming.hello === xiaohong.hello //true
借用構造函數繼承
基本思想很簡單,在子類型的構造函數內部調用父類型的構造函數:
function SuperType(){ this.colors = ["red", "blue", "green"]; } function SubType(){ //繼承了 SuperType SuperType.call(this); } var instance1 = new SubType(); instance1.colors.push("black"); alert(instance1.colors); //"red,blue,green,black" var instance2 = new SubType(); alert(instance2.colors); //"red,blue,green"
問題:方法都在構造函數內部定義,函數的複用性就無從談起了。在超類型的原型中定義的方法,對子類型而言是不可見的。考慮這些問題,借用構造函數也是不多單獨使用。
組合繼承
實現的思路是使用原型鏈實現對原型屬性和方法的繼承,而經過constructor stealing技術實現對實例屬性的繼承。
function SuperType(name){ this.name = name; this.colors = ["red", "blue", "green"]; } SuperType.prototype.sayName = function(){ alert(this.name); }; function SubType(name, age){ //繼承屬性 SuperType.call(this, name); this.age = age; } //繼承方法 SubType.prototype = new SuperType(); SubType.prototype.constructor = SubType; SubType.prototype.sayAge = function(){ alert(this.age); }; var instance1 = new SubType("Nicholas", 29); instance1.colors.push("black"); alert(instance1.colors); //"red,blue,green,black" instance1.sayName(); //"Nicholas"; instance1.sayAge(); //29 var instance2 = new SubType("Greg", 27); alert(instance2.colors); //"red,blue,green" instance2.sayName(); //"Greg"; instance2.sayAge(); //27
組合繼承避免了原型鏈和借用構造函數的缺陷,融合二者之長,是最經常使用的JS繼承模式。
原型式繼承
若是隻是想讓一個對象與另外一個對象保持相似的狀況下,沒有必要興師動衆地建立構造函數。咱們可使用原型式繼承。 Rect.prototype = Object.create(Shape.prototype);
原型繼承
JavaScript的原型繼承實現方式就是:
定義新的構造函數,並在內部用call()調用但願「繼承」的構造函數,並綁定this;
藉助中間函數F實現原型鏈繼承,最好經過封裝的inherits函數完成;
繼續在新的構造函數的原型上定義新方法。
var print = require('./print.js'); function Student(props) { this.name = props.name || 'Unnamed'; } Student.prototype.hello = function () { print('Hello, ' + this.name + '!'); }; function Sub(props) { Student.call(this, props); this.grade = props.grade || 1; } Sub.prototype.getGrade = function() { return this.grade; }; function inherits(Child, Parent) { var F = function() {}; F.prototype = Parent.prototype; Child.prototype = new F(); Child.prototype.constructor = Child; } inherits(Sub, Student); var xiaoming = new Sub({ name: 'xiaoming', grade: 100 }); print(xiaoming.name + ' ' + xiaoming.grade); print(xiaoming instanceof Student); print(xiaoming instanceof Sub);
class
繼承class Student { constructor(name) { this.name = name; } hello() { alert('Hello, ' + this.name + '!'); } } class PrimaryStudent extends Student { constructor(name, grade) { super(name); // 記得用super調用父類的構造方法! this.grade = grade; } myGrade() { alert('I am at grade ' + this.grade); } }
ES6引入的class和原有的JavaScript原型繼承有什麼區別呢?實際上它們沒有任何區別,class的做用就是讓JavaScript引擎去實現原來須要咱們本身編寫的原型鏈代碼。簡而言之,用class的好處就是極大地簡化了原型鏈代碼。
這裏遇到的問題是isPrototypeOf的問題
JavaScript
不提供多重繼承功能,即不容許一個對象同時繼承多個對象。可是,能夠經過變通方法,實現這個功能。
function M1() { this.hello = 'hello'; } function M2() { this.world = 'world'; } function S() { M1.call(this); M2.call(this); } S.prototype = M1.prototype; var s = new S(); s.hello // 'hello' s.world // 'world' s instanceof M2 //false
上面代碼中,子類S同時繼承了父類M1和M2。固然,從繼承鏈來看,S只有一個父類M1,可是因爲在S的實例上,同時執行M1和M2的構造函數,因此它同時繼承了這兩個類的方法。
apply的應用:轉換相似數組的對象
Array.prototype.slice.apply({0:1,length:1}) // [1] Array.prototype.slice.apply({0:1}) // [] Array.prototype.slice.apply({0:1,length:2}) // [1, undefined] Array.prototype.slice.apply({length:1}) // [undefined]
bind結合call,能夠改寫一些JavaScript原生方法的使用形式
[1, 2, 3].slice(0, 1) // [1] // 等同於 Array.prototype.slice.call([1, 2, 3], 0, 1) // [1]
call方法實質上是調用Function.prototype.call方法,所以上面的表達式能夠用bind方法改寫。
var push = Function.prototype.call.bind(Array.prototype.push); var pop = Function.prototype.call.bind(Array.prototype.pop); var a = [1 ,2 ,3]; push(a, 4) a // [1, 2, 3, 4] pop(a) a // [1, 2, 3]
某個屬性究竟是原型鏈上哪一個對象自身的屬性。
function getDefiningObject(obj, propKey) { while (obj && !{}.hasOwnProperty.call(obj, propKey)) { obj = Object.getPrototypeOf(obj); } return obj; }
獲取實例對象obj的原型對象,有三種方法。
obj.__proto__ obj.constructor.prototype Object.getPrototypeOf(obj)
推薦最後一種
面向對象感受還沒怎麼搞明白,模塊的東西還沒弄明白,有時間補上