對象是
JavaScript中的基本數據結構,與許多面向對象的語言一樣,JavaScript也支持繼承,但不同的是JavaScript的繼承機制基於 原型 ,而不是 類,雖然原型使用了很多傳統面嚮對象語言的概念,但是使用原型和使用類依舊存在很大差異。
1、 prototype 、 getPrototypeOf 和 __proto__ 之間的區別
原型包括三個獨立但相關的訪問器,即 prototype 、 getPrototypeOf 和 __proto__ :
-
C.prototype屬性是new C()`創建的對象的原型。
-
Object.getPrototypeOf(obj)是ES5中檢索對象原型的標準函數。
-
obj.__proto__是檢索對象原型的非標準方法。
// 創建一個類
function User(name, passwordHash) {
this.name = name;
this. passwordHash = age;
}
// 創建原型
User.prototype.toString = function() {
return 'User:' + this.name;
};
User.prototype.checkPassword = function() {
return hash(password) === this.passwordHash;
};
var u = new User('dreamapple', '0eajkjfkahjfh7as7d678as6');
// User.prototype == new User().__proto_ User.prototype == Object.getPrototypeOf(u)
console.log(User.prototype === u.__proto__); // true
console.log(User.prototype === Object.getPrototypeOf(u)); // true
構造函數及其實例的原型關係
2、儘量使用 Object.getPropertyOf 函數而不要使用 __proto__ 屬性
-
使用符合標準的
Object.getPrototypeOf函數而不要使用非標準的__proto__屬性。 -
在支持
__proto__屬性的非ES5環境中實現Object.getPrototypeOf函數。 -
不要修改
__proto__屬性 -
使用
Object.create()函數給新對象設置自定義的原型。
如下:
//==========================eg.2=========================
var obj = Object.create(null);
console.log('__proto__' in obj); // false
console.log(Object.getPrototypeOf(obj)); // null
// 可以使用 __proto__屬性來模仿 Object.getPropertyOf() 函數
if('undefined' === typeof Object.getPrototypeOf) {
Object.getPrototypeOf = function(obj) {
var t = typeof obj;
if(!obj || (t !== 'object' && t !== 'function')) {
throw new Error('not an object');
}
return obj.__proto__;
}
}
//==========================eg.2=========================
function User(name) {
this.name = name;
}
var user = new User('dreamapple');
console.log(user); // User { name: 'dreamapple' }
console.log(user.__proto__);
/* Object
constructor:User(name)
__proto__:Object
*/
// 使用user對象的原型 通過使用Object.create()方法創建一個新的對象
var user1 = Object.create(Object.getPrototypeOf(user));
console.log(user1); // User {}
// 永遠不要修改 __proto__ 屬性
user1.__proto__ = {}; // X
3、在原型中存儲方法
-
將方法存儲在實例對象中將創建函數的多個副本,因為每個實例對象都有一份副本。
-
將方法存儲於原型中優於存儲在實例對象中。
function User(name, age) {
// 一般屬性
this.name = name;
this.age = age;
// 在實例屬性上的方法
this.getName = function() {
console.log('My name is ' + this.name);
return this.name;
}
}
// 在原型上的方法
User.prototype = {
getAge: function() {
console.log('My age is ' + this.age);
return this.age;
}
}
var user = new User('dreamapple', 22);
// 方法 getName 在 user 的實例對象中複製了一份,而方法 getAge 在User的原型上供給所有實例公用
console.log(user); // User { name: 'dreamapple', age: 22, getName: [Function] }
4、只將實例屬性(狀態)儲存在實例對象中
從上一個例子可以看出,儲存在父對象原型中的方法或者屬性不會拷貝到實例對象中,而是由所有實例對象公用,這就會出現一個問題,假如屬性設置不當,就會導致不同實例調用同一個方法時可能會修改到共用的屬性,如下:
function Tree(name){
this.name = name;
}
Tree.prototype = {
children : [],
addChild : function(x) {
this.children.push(x);
}
};
var left = new Tree('left');
left.addChild(1);
left.addChild(3);
var right = new Tree('right');
right.addChild(5);
right.addChild(7);
console.log(Tree.prototype.children); //[1, 3, 5, 7]
console.log(right); //[1, 3, 5, 7]
console.log(left); //[1, 3, 5, 7]
5、在子類的構造函數中調用父類的構造函數
-
在子類的構造函數中顯式地傳入this作為顯式的接受者調用父類構造函數。
-
使用
Object.create()函數來構造子類的原型對象以避免調用父類的構造函數。
示例如下:
//定義一個父類
function Animal(cate, food) {
this.cate = cate;
this.food = food;
}
Animal.prototype = {
sayHello : function(){
console.log(this.cate + ' eat ' + this.food);
}
}
//定義子類
function Dog (cate, food, name, age) {
Animal.call(this,cate,food);
this.name = name;
this.age = age;
}
//將子類與父類原型關聯
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.wang = function() {
console.log(this.name + ' do not like ' + '?');
}
var tom = new Dog('dog','meat','old tom',15);
tom.wang(); //"old tom do not like ?"
tom.sayHello(); //"dog eat meat"
6、其他的一些注意項
-
不要重用父類的屬性名
-
避免繼承標準類
-
避免輕率地使用猴子補丁(例如隨意為Array原型添加一個方法,但可以通過測試條件為原型添加polyfills)