JavaScript面向對象編程

基本都是廖學峯老師JavaScript教程的內容。先前看也似是懂了,本身稍微理下做複習。javascript

一、原型鏈

JavaScript 的原型鏈和 Java 的 Class 區別就在, 它沒有「Class」的概念, 全部對象都是實例,所謂繼承關係不過是把一個對象的原型指向另外一個對象而已。java

Student是一個現有的對象,不一樣於C++,Python中類對象的概念,Student更符合實例對象。Studentname屬性和run()方法。xm只有name屬性,Studentxm是同等地位的實例對象。xm.__proto__ = Student;xm的原型指向了對象Student,看上去 xm彷彿是從Student繼承下來的。xm能夠調用Studentrun()方法。數組

var Student = {
    name: 'Robert',
    run: function() {
        console.log(this.name + ' is running...');
    }
};

var xm = {
    name: 'xiaoming'
}

xm.__proto__ = Student; 

console.log(xm.name);
xm.run();
console.log(Student.name);
Student.run();

// run output:
// xiaoming
// xiaoming is running...
// Robert
// Robert is running...

上述代碼僅用於演示目的。在編寫 JavaScript 代碼時,不要直接用 obj.__proto__去改變一個對象的原型,而且,低版本的IE也沒法使用__proto__


Object.create()方法能夠傳入一個原型對象,並建立一個基於該原型的新對象, 可是新對象什麼屬性都沒有。能夠編寫一個函數來建立新對象。函數

xm對象的__proto__屬性是{ name: 'Robert', run: [Function: run] },而沒有prototype屬性。this

var Student = {
    name: 'Robert',
    run: function() {
        console.log(this.name + ' is running...');
    }
};
function createStudent(name) {
    // 傳入一個原型對象,建立一個新的原型對象
    var s = Object.create(Student);
    s.name = name;
    return s;
}
var xm = createStudent('xiaoming');
xm.run()    // xiaoming is running...
// xm 對象的__proto__ 和 prototype
console.log(xm.__proto__)   //{ name: 'Robert', run: [Function: run] }
console.log(xm.prototype)   // undefined

// 原型鏈
console.log(xm.__proto__ === Student);
console.log(Student.__proto__ === Object.__proto__.__proto__);
console.log(Object.__proto__.__proto__.__proto__ === null);

對象xm原型鏈爲:
xm --> Student.__proto__ --> Object.__proto__ --> null

prototype

二、構造函數

除了直接用{ ... }建立一個對象外,JavaScript還能夠用一種構造函數的方法來建立對象。它的用法是,先定義一個構造函數,再用關鍵字new來調用這個函數,並返回一個對象。構造函數和普通函數並沒有差異。若是不寫new,它就是一個普通函數,它返回undefined。可是,若是寫了new,它就變成了一個構造函數,它綁定的this指向新建立的對象,並默認返回this,也就是說,不須要在最後寫return thiscode

function Student(name) {
    this.name = name;
    this.hello = function () {
        console.log('Hello, ' + this.name);
    };
}

var jack = new Student('jack');

console.log(jack.name);   //jack
jack.hello();     //Hello, jack

console.log(jack.__proto__ === Student.prototype)
console.log(Student.prototype.__proto__ === Object.prototype)
console.log(Object.prototype.__proto__ === null)

對象jack的原型鏈是:
jack --> Student.prototype --> Object.prototype --> null
用new Student()建立的對象還從原型上得到了一個constructor屬性,它指向函數Student自己:對象

console.log(jack.constructor === Student.prototype.constructor) // true
console.log(Student.prototype.constructor === Student) // true
console.log(Object.getPrototypeOf(jack) === Student.prototype) // true
console.log(jack instanceof Student) // true


三、__proto__和prototype
JavaScript 對每一個建立的對象都會設置一個原型,指向它的原型對象。在JS中萬物皆對象。字符串String是對象,數組([])是對象,對象({})是對象,函數方法(Function)是對象。下面分別看下通常對象 和 函數對象 的原型連。
String對象的原型鏈:
和以前的對象xm相似字符串對象str也只有__proto__屬性,沒有prototype屬性。
str的原型鏈是:
str --> String.prototype --> Object.prototype --> null

var str = 'aaaaaaaa'

//對象的__proto__ 和 prototype
console.log(str.__proto__)  //[String: '']
console.log(str.prototype)  //undefined

console.log(typeof(str.__proto__))  //object

// 原型鏈
console.log(str.__proto__ === String.prototype) //true
console.log(String.prototype.__proto__ === Object.prototype)    //true
console.log(Object.prototype.__proto__ === null)    //true

// 構造函數
console.log(str.constructor === String.prototype.constructor) //true
console.log(String.prototype.constructor === String)    //true
console.log(Object.prototype.constructor === Object)    //true

// 類型
console.log(typeof(String.prototype))       //object
console.log(typeof(String.prototype.__proto__)) //object
console.log(typeof(Object.prototype))       //object
console.log(typeof(Object.prototype.__proto__))     //object

函數對象的原型鏈,函數對象不只有__proto__,還有屬性prototype
那麼Function()Object()和前面的String()是函數對象,由於它們有prototype屬性。
經過new Foo()構建的對象foo_object的原型鏈是:
foo_object --> Foo.prototype --> Object.prototype --> null
函數Foo()也是一個對象,它的原型鏈是:
Foo() --> Function.prototype --> Object.prototype --> nullblog

function Foo() {
    return 0;
}

var foo_object = new Foo();

// 函數對象的__proto__ 和 prototype
console.log(Foo.__proto__)  //[Function]
console.log(Foo.prototype)  //Foo {}

console.log(typeof(Foo.__proto__))  //function
console.log(typeof(Foo.prototype))  //object

// 原型鏈
console.log(foo_object.__proto__ === Foo.prototype);    // true
console.log(Foo.prototype.__proto__ === Object.prototype);  // true
console.log(Object.prototype.__proto__ === null)    // true

console.log(Foo.__proto__ === Function.prototype);  // true
console.log(Function.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__ === null)    // true

// 構造函數
console.log(Foo.constructor === Function.prototype.constructor)   //true
console.log(Function.prototype.constructor === Function)    //true
console.log(Object.prototype.constructor === Object)    //true

// 類型
console.log(typeof(Function.prototype))     //function
console.log(typeof(Function.prototype.__proto__))   //object
console.log(typeof(Object.prototype))       //object
console.log(typeof(Object.prototype.__proto__))     //object

1).全部對象有屬性__proto__,指向對象的原型對象。
2).函數對象除了有屬性__proto__,還有屬性prototypeprototype屬性指向(構造)函數對象共享的屬性和方法(即一個由此構造函數構造而來的對象能夠繼承prototype指向的屬性及方法),prototype屬性使您有能力向對象添加屬性和方法。其中有一個constructor屬性指向函數對象自己(String.prototype.constructor === StringFunction.prototype.constructor === Function)。
繼承

展現下如何使用 prototype 屬性來向對象添加屬性:
1)經過Student.prototype的方式向函數對象添加了agehello()屬性,後續構建的對象jacksara都繼承了所增屬性。
2)hello()屬性分別在sara對象構建以前和jack對象構建以後添加的,但繼承獲得的hello()是同樣的。那麼在JS中一個已構建的對象,還能夠經過其原型對象「不着痕跡」的擴展屬性。

function Student(name) {
    this.name = name;
}

Student.prototype.age = 23;

var jack = new Student('jack');


Student.prototype.hello = function () {
    console.log('Hello, ' + this.name + '!');
}; 

var sara = new Student('sara')

console.log(jack.age);   //23
jack.hello();   //Hello, jack!

console.log(sara.age)   //23
sara.hello()    //Hello, sara!

console.log(sara.hello === jack.hello)  //true


四、原型繼承

JavaScript因爲採用原型繼承,咱們沒法直接擴展一個Class,由於不存在Class這種類型。
Student的構造函數

function Student(props) {
    this.name = props.name || 'Unnamed';
}

Student.prototype.hello = function () {
    console.log('Hello, ' + this.name + '!');
}

若是要基於Student擴展出PrimaryStudent,能夠先定義出PrimaryStudent

function PrimaryStudent(props) {
    // 調用Student構造函數,綁定this變量:
    Student.call(this, props);
    this.grade = props.grade || 1;
}

可是,調用了Student構造函數不等於繼承了StudentPrimaryStudent建立的對象的原型鏈仍是:
new PrimaryStudent() --> PrimaryStudent.prototype --> Object.prototype --> null
要想辦法把原型鏈修改成:
new PrimaryStudent() --> PrimaryStudent.prototype --> Student.prototype --> Object.prototype --> null
這樣,原型鏈對了,繼承關係就對了。新的基於PrimaryStudent建立的對象不但能調用PrimaryStudent.prototype定義的方法,也能夠調用Student.prototype定義的方法。
可藉助一箇中間對象來實現正確的原型鏈,中間對象能夠用一個空函數F來實現,原型指向Student.prototype。代碼以下:

// Student構造函數:
function Student(props) {
    this.name = props.name || 'Unnamed';
}

Student.prototype.hello = function () {
    console.log('Hello, ' + this.name + '!');
}

// PrimaryStudent構造函數:
function PrimaryStudent(props) {
    Student.call(this, props);
    this.grade = props.grade || 1;
}
// 中間對象-空函數F():
function F() {
}
// step1:把F的原型指向Student.prototype:
F.prototype = Student.prototype;

// step2:把PrimaryStudent的原型指向一個新的F對象,F對象的原型正好指向Student.prototype:
PrimaryStudent.prototype = new F(); //PrimaryStudent.prototype.__proto__ === F.prototype === Student.prototype

// step3:把PrimaryStudent原型的構造函數修復爲PrimaryStudent:
PrimaryStudent.prototype.constructor = PrimaryStudent;

// 繼續在PrimaryStudent原型(就是new F()對象)上定義方法:
PrimaryStudent.prototype.getGrade = function () {
    return this.grade;
};

// 建立xiaoming:
var xiaoming = new PrimaryStudent({
    name: '小明',
    grade: 2
});
xiaoming.name; // '小明'
xiaoming.grade; // 2

// 驗證原型鏈:
console.log(xiaoming.__proto__ === PrimaryStudent.prototype); // true
console.log(PrimaryStudent.prototype.__proto__ === Student.prototype); // true
console.log(Student.prototype.__proto__ === Object.prototype);  // true
// 驗證繼承關係:
console.log(xiaoming instanceof PrimaryStudent); // true
console.log(xiaoming instanceof Student); // true


五、class繼承

新的關鍵字class從ES6開始正式被引入到JavaScript中。
若是用新的class關鍵字來編寫Student,並且能夠直接經過extends來實現繼承,原型繼承的中間對象,原型對象的構造函數等都不須要考慮了。
使用class定義的對象如Student仍是函數對象,class的目的就是讓定義類更簡單。class的定義包含了構造函數constructor和定義在原型對象上的函數hello()(注意沒有function關鍵字),這樣就避免了Student.prototype.hello = function () {...}這樣分散的代碼。

// class關鍵
class Student {
    constructor(name) {
        this.name = name;
    }

    hello() {
        console.log('Hello, ' + this.name + '!');
    }
}
console.log(typeof(Student));   //function

var xiaoming = new Student('小明');
xiaoming.hello();   // Hello, 小明!

class PrimaryStudent extends Student {
    constructor(name, grade) {
        super(name); // 記得用super調用父類的構造方法!
        this.grade = grade;
    }

    myGrade() {
        console.log('I am at grade ' + this.grade);
    }
}

var xiaohong = new PrimaryStudent('小紅', 24) 
xiaohong.hello()    // Hello, 小紅!
xiaohong.myGrade()  // I am at grade 24

ES6引入的class和原有的JavaScript原型繼承有什麼區別呢?實際上它們沒有任何區別,class的做用就是讓JavaScript引擎去實現原來須要咱們本身編寫的原型鏈代碼。簡而言之,用class的好處就是極大地簡化了原型鏈代碼。

相關文章
相關標籤/搜索