基本都是廖學峯老師JavaScript教程的內容。先前看也似是懂了,本身稍微理下做複習。javascript
JavaScript 的原型鏈和 Java 的 Class 區別就在, 它沒有「Class」的概念, 全部對象都是實例,所謂繼承關係不過是把一個對象的原型指向另外一個對象而已。java
Student
是一個現有的對象,不一樣於C++,Python中類對象的概念,Student
更符合實例對象。Student
有name
屬性和run()
方法。xm
只有name
屬性,Student
和xm
是同等地位的實例對象。xm.__proto__ = Student;
把xm
的原型指向了對象Student
,看上去 xm
彷彿是從Student
繼承下來的。xm
能夠調用Student
的run()
方法。數組
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 this。code
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
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 --> null
blog
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__
,還有屬性prototype
,prototype
屬性指向(構造)函數對象共享的屬性和方法(即一個由此構造函數構造而來的對象能夠繼承prototype指向的屬性及方法),prototype
屬性使您有能力向對象添加屬性和方法。其中有一個constructor
屬性指向函數對象自己(String.prototype.constructor === String
,Function.prototype.constructor === Function
)。繼承
展現下如何使用 prototype 屬性來向對象添加屬性:
1)經過Student.prototype
的方式向函數對象添加了age
和hello()
屬性,後續構建的對象jack
和sara
都繼承了所增屬性。
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
構造函數不等於繼承了Student
,PrimaryStudent
建立的對象的原型鏈仍是:
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從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的好處就是極大地簡化了原型鏈代碼。