JS面向對象
oop
繼承:實例可以繼承A對象中的方法和屬性,減少代碼冗余
封裝:對象把實現過程封裝在方法中,調用者可以不需了解過程直接調用
多態:一種事物,可以有多種表現形式
構造函數
是對象的模版
function Person(){
this.name = 'king';
var age = 12; // 私有屬性,是指針針對實例而言
this.say = function() {
console.log('hi~')
}
}
對象
- 普通對象: 沒有prototype,有proto
- 函數對象: 只有函數有prototype屬性,所有的對象都有proto隱式屬性
- 實例對象: var son = new Person();
創建對象方式
new運算符
MDN:語法new constructor[([arguments])],創建一個用戶定義的對象類型的實例或具有構造函數的內置對象的實例
- 創建一個空的簡單JavaScript對象(即{})
- 鏈接該對象(即設置該對象的構造函數)到另一個對象
- 將步驟1新創建的對象作為this的上下文
- 如果該函數沒有返回對象,則返回this
但實際上new具體做了什么操作
var son = new Person();
當這段代碼運行的時候,內部實際上執行的是:
// 創建一個空對象
var other = new Object();
// 將空對象的原型賦值為構造函數的原型
other.__proto__ = Person.prototype;
// 改變this指向
Person.call(other);
最后一步如何理解,當構造函數是否返回對象,可做如下嘗試
// 無返回對象時
function Person(name){
this.name = name;
this.age = 12;
}
Person.prototype.say = function(){
console.log('say hi')
}
var son = new Person('king');
console.log(son.name); // 'king'
console.log(son.say()); // 'say hi'
由此可以得出結論,new通過構造函數Person創造出來的實例son,可以訪問Person中的內部屬性,以及原型鏈上的方法,當對構造函數Person如下修改時
// 返回非對象
function Person(name){
this.name = name;
this.age = 12;
return 1
}
Person.prototype.say = function(){
console.log('say hi')
}
var son = new Person('king');
console.log(son.name); // 'king'
console.log(son.say()); // 'say hi'
// 返回對象
function Person(name){
this.name = name;
this.age = 12;
return {color: 'red'}
}
Person.prototype.say = function(){
console.log('say hi')
}
var son = new Person('king');
console.log(son); // '{color: "red"}'
綜上,可以很好理解MDN上關于new操作符的最后一步操作結果
Object.create()
語法:Object.create(proto[, propertiesObject]),創建一個新對象,使用現有的對象來提供新創建的對象的proto
內部實現方式
Object.create = function (o) {
// o參數是原型對象,不需要加.prototype
var F = function () {};
F.prototype = o;
return new F();
};
Object.create = function (obj) {
var B={};
Object.setPrototypeOf(B,obj); // or B.__proto__=obj;
return B;
};
MDN demo:
const person = {
isHuman: false,
printIntroduction: function() {
console.log(`My name is ${this.name}. Am I human? ${this.isHuman}`);
}
};
const me = Object.create(person);
console.log(me): // {}
me.name = 'Matthew'; // "name" is a property set on "me", but not on "person"
me.isHuman = true; // inherited properties can be overwritten
me.printIntroduction();
// expected output: "My name is Matthew. Am I human? true"
第二個參數
//該參數是一個屬性描述對象,它所描述的對象屬性,會添加到實例對象,作為該對象自身的屬性。
var obj = Object.create({}, {
p1: {
value: 123,
enumerable: true,
configurable: true,
writable: true,
},
p2: {
value: 'abc',
enumerable: true,
configurable: true,
writable: true,
}
});
// 等同于
var obj = Object.create({});
obj.p1 = 123;
obj.p2 = 'abc';
個人感覺跟new的用法非常相似,區別在于:
字面量和new關鍵字創建的對象是Object的實例,原型指向Object.prototype,繼承內置對象Object
Object.create(proto, propertiesObject)創建的對象的原型取決于proto,proto為null,新對象是空對象,沒有原型,不繼承任何對象;proto為指定對象,新對象的原型指向指定對象,繼承指定對象。propertiesObject是可選參數,指定要添加到新對象上的可枚舉的屬性(即其自定義的屬性和方法,可用hasOwnProperty()獲取的,而不是原型對象上的)的描述符及相應的屬性名稱,如果不傳則實例對象為{}。
Object.create(o),如果o是一個構造函數,則采用這種方法來創建對像沒有意義
Object.create(o),如果o是一個字面量對象或實例對象,那么相當于是實現了對象的淺拷貝
封裝
把"屬性"(property)和"方法"(method),封裝到一個構造函數里,并且他的實例對象可以繼承他的所有屬性和方法,構建構造函數
prototype
解決所有實例指向prototype對象地址,而不需要每個實例對象重復生成構造內部屬性方法,這意味著,我們可以把那些不變的屬性和方法,直接定義在prototype對象上,被構造函數實例繼承
// before
function Person(name){
this.name = name;
this.say = function(){
console.log('say hi');
}
}
//after
function Person(name){
this.name = name;
this.age = 12;
}
Person.prototype.say = function(){
console.log('say hi')
}
var son = new Person('king');
var daughter = new Person('kim');
isPrototypeOf()用來判斷,某個proptotype對象和某個實例之間的關系; Person.prototype.isPrototypeOf(son) // true hasOwnProperty()判斷某一個屬性到底是本地屬性,還是繼承自prototype對象的屬性; son.hasOwnProperty("age") //false in運算符還可以用來遍歷某個對象的所有屬性,包含繼承的屬性 for(var prop in son)
原型,原型鏈
內置對象:Object、Function都是js內置的函數, 類似的還有我們常用到的Array、RegExp、Date、Boolean、Number、String
js分為函數對象和普通對象,每個對象都有proto屬性,但是只有函數對象才有prototype屬性
除了Object的原型對象(Object.prototype)的proto指向null,其他內置函數對象的原型對象和自定義構造函數的proto都指向Object.prototype, 因為原型對象本身是普通對象
function F(){};
F.prototype.__proto__ = Object.prototype;
Array.prtotype.__proto__ = Object.prototype;
Object.prototype.__proto__ = null;
F.__proto__ = Function.prototype;
var f = new F();
f.__proto__ = F.prototype;
繼承
- 構造函數繼承
function Person(name){
this.name = name
}
function Son(name){
Person.call(this,name)
}
var obj = new Son('king');
console.log(obj.name) // 'king'
- prototype模式
組合模式
function Person(age){
this.age = age
}
function Son(age){
this.name = 'king';
Person.call(this,age);
}
// 改變Son的prototype指向Person的實例,那么Son的實例就可以繼承Person
Son.prototype = new Person();
console.log(Son.prototype.constructor == Person.prototype.constructor) //true
// 避免繼承鏈混亂,將Son.prototype對象的constructor改回Son
Son.prototype.constructor = Son;
var obj = new Son(12);
console.log(obj.age) // 'king'
console.log(Person.prototype.isPrototypeOf(obj)) // true 同時繼承Person和Son
function Person(){
}
Person.prototype.age = 12;
function Son(name){
this.name = name;
}
// Son.prototype指向Person.prototype
Son.prototype = Person.prototype;
console.log(Son.prototype.constructor == Person.prototype.constructor) //true
// 然而也修改了Person.prototype.constructor為Son
Son.prototype.constructor = Son;
var obj = new Son('king');
console.log(obj.age) // 'king'
console.log(Person.prototype.isPrototypeOf(obj)) // true 同時繼承Person和Son
Son.prototype.sex = 'male';
console.log(Person.prototype.sex); // 'male'
差別:與前一種方法相比,這樣做的優點是效率比較高(不用執行和建立Person的實例了),比較省內存。缺點是 Person.prototype和Son.prototype現在指向了同一個對象,那么任何對Son.prototype的修改,都會反映到Person.prototype,再次改進如下,
利用空對象作為中介
function Person(){
this.age = 12
}
Person.prototype.sex = 'male';
function Son(){
this.name = 'king';
}
var F = function(){};
F.prototype = Person.prototype;
Son.prototype = new F();
Son.prototype.constructor = Son;
Son.uber = Person.prototype;
var obj = new Son();
console.log(obj.sex); // 'male'
// 封裝一下
function extend(Child, Parent) {
var F = function(){};
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child;
Child.uber = Parent.prototype;
}
extend(Son,Parent);
var obj = new Son();
console.log(obj.sex); // 'male'
- 拷貝繼承
利用 for...in 拷貝Person.prototype上的所有屬性給Son.prototype,Son的實例就相當于繼承了Person.prototype的所有屬性以及Son的屬性
- 非構造函數繼承
1.json格式的發明人Douglas Crockford,提出了一個object()函數,即后來的內置函數Object.create()
function object(o){
var F = function(){};
F.prototype = o;
return new F();
}
var child = {
name: 'child'
}
var parent = {
name: 'parent'
}
var child = object(parent)
console.log(child.name); // 'parent'
2.淺拷貝
var parent = {
area: ['A','B']
}
function extendCopy(o){
var c = {};
for(var i in o){
c[i] = o[i]
}
return c;
}
var child = extendCopy(parent);
child.area.push('C');
console.log(parent.area); // ['A','B','C']
現象:當父對象的屬性值為數組或者對象時,子對象改變那個屬性值,父對象也會隨之改變,因此,子對象只是獲得了內存地址,而不是真正的拷貝,extendCopy只能用作基本數據類型的拷貝,這也是早期jquery實現繼承的方式
3.深拷貝
var parent = {
area: ['A','B']
}
function deepCopy(p, c) {
var c = c || {};
for (var i in p) {
if (typeof p[i] === 'object') {
c[i] = (p[i].constructor === Array) ? [] : {};
deepCopy(p[i], c[i]);
} else {
c[i] = p[i];
}
}
return c;
}
var child = deepCopy(parent);
child.area.push('C');
console.log(parent.area); // ['A','B']