<i>以下 JS 為 ECMA-262 第五版</i>
什么是面向對象
面向對象是一種和面向過程不同的思維方法。用類作為模版做蛋糕。 但是 JS 沒有類,它的對象和其他基于類的語言的對象不同;
Java 的對象: 有三個主要特征,行為·狀態·標志。即為有自己的可調用方法,保存著描述當前特征的信息,唯一身份;
Python的對象: 包含特性和方法的合集;
JS 的對象: 無序屬性的集合,其屬性可以包含基本值、對象或者函數; (ECMA-262)實際上是散列表;
既然 JS 的對象與典型面向對象的語言不同,為什么 JS 可以面向對象?
可能需要回答的問題:
- JS 的對象是怎樣的?
- 面向對象的需求是什么?
- JS 的對象是否可以符合面向對象的需求?要怎么做才可以符合?
- JS 的【引用類型】和【類】的區別
1. JS 的對象是怎樣的?
- JS 的對象是無序屬性的集合,其屬性可以包含基本值、對象或者函數; (ECMA-262) 實際上是散列表;
- JS 的對象是 Object 的實例(即為引用類型的實例)也被稱為引用類型的值。每個實例都具有7個屬性/方法,它們分別是: constructor, hasOwnProperty(), isPrototypeOf(), propertyIsEnumerable(), toLocaleString(), toString(), valueOf()。
- JS 的對象有兩種屬性: 數據屬性和訪問器屬性。數據屬性包括 [[Configurable]], [[Enumberable]], [[Writable]], [[Value]]。訪問器屬性包括一對 getter, setter 函數,有以下4個特性 [[Configurabel]], [[Enumberable]], [[Get]], [[Set]]。這兩種屬性均使用 Object.defineProperty() 修改。 如下:
//ECMAScript 5 Object.defineProperty() method (IE9 and Firefox 4).
var book = {
_year: 2004,
name: "javascript",
edition: 1
};
// 修改數據屬性
Object.defineProperty(book, "name" , {
configurable : false,
value : "python"
})
// 修改訪問器屬性
Object.defineProperty(book, "year", {
get: function(){
return this._year;
},
set: function(newValue){
if (newValue > 2004) {
this._year = newValue;
this.edition += newValue - 2004;
}
}
});
alert(book.name) //python
delete book.name //因設置可刪除性 configurable 為 false ,所以不可刪除
alert(book.name) //python
book.year = 2005;
alert(book.edition); //2
2. 面向對象的需求是什么?
封裝、繼承、多態
<b>封裝</b> 可以隱藏實現細節,使得代碼模塊化;
<b>繼承</b> 可以擴展已存在的代碼模塊(類);它們的目的都是為了——代碼重用。
<b>多態</b> 則是為了實現另一個目的——接口重用!
點擊查看摘自
3. JS 是否可以符合面向對象的要求?
- JS 是否可以實現封裝?
此問題可以演化為JS是否可以隱藏細節,使得代碼模塊化?
已知的模式為以下: 工廠模式 、 構造函數模式 、 原型模式 、ES6的Class
<b>工廠模式</b> :解決了相似對象的問題,卻沒有解決對象識別的問題,不知道對象的類型。
<b>構造函數模式</b> :
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
alert(this.name);
};
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
person1.sayName(); //"Nicholas"
person2.sayName(); //"Greg"
alert(person1 instanceof Object); //true
alert(person1 instanceof Person); //true
alert(person2 instanceof Object); //true
alert(person2 instanceof Person); //true
alert(person1.constructor == Person); //true
alert(person2.constructor == Person); //true
// 相對于 constructor, instanceof 對于對象類型的檢測更為可靠
//每個方法都要在每個實例上重新創建
alert(person1.sayName == person2.sayName); //false
//可以把函數定義的方法轉移到函數外 如下
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = sayName;
}
function sayName(){
alert(this.name);
}
構造函數實際上經歷了: 創建對象 ==》 this 指向新對象 ==》 為新對象添加屬性 ==》返回新對象
缺點是 - 轉移了函數定義的方法,方法放到全局,破壞了封裝性。
<b>原型模式</b> :
function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
person1.sayName(); //"Nicholas"
var person2 = new Person();
person2.sayName(); //"Nicholas"
alert(person1.sayName == person2.sayName); //true
alert(Person.prototype.isPrototypeOf(person1)); //true
alert(Person.prototype.isPrototypeOf(person2)); //true
//only works if Object.getPrototypeOf() is available
if (Object.getPrototypeOf){
alert(Object.getPrototypeOf(person1) == Person.prototype); //true
alert(Object.getPrototypeOf(person1).name); //"Nicholas"
}
<b>ES6 class 模式</b> :
class Point{
constructor(x,y){
this.x = x
this.y = y
}
toString(){
return "(" + this.x + "," + this.y +")"
}
}
ES6的語法實際上是一個語法糖, 上述模式相當于組合使用函數模式和原型模式:
function Point(x,y){
this.x = x
this.y = y
}
Point.prototype.toString = function(){
return "(" + this.x + "," + this.y +")"
}
<b>動態原型模式</b> :只有在方法不存在的情況下才添加方法。
function Person(name, age, job){
//properties
this.name = name;
this.age = age;
this.job = job;
//methods
if (typeof this.sayName != "function"){
Person.prototype.sayName = function(){
alert(this.name);
};
}
}
var friend = new Person("Nicholas", 29, "Software Engineer");
friend.sayName();
- JS 如何繼承?
JS 的函數沒有簽名,所以不支持接口繼承,只支持實現繼承,是依靠原型鏈來實現的。
每個函數都有原型對象 prototype,原型對象的 constructor 屬性是一個指向protoype 屬性所在函數的指針。讀取對象的某個屬性時,會一級一級向上搜索,從對象本身開始。如果在實例中找到了具有給定名字的屬性,就返回該屬性的值,如果沒有找到,則繼續搜索指針指向的原型對象。
關于幾種繼承方式,在下一篇