JS面向?qū)ο?/h1>
oop
繼承:實例可以繼承A對象中的方法和屬性,減少代碼冗余
封裝:對象把實現(xiàn)過程封裝在方法中,調(diào)用者可以不需了解過程直接調(diào)用
多態(tài):一種事物,可以有多種表現(xiàn)形式
構(gòu)造函數(shù)
是對象的模版
function Person(){
this.name = 'king';
var age = 12; // 私有屬性镜会,是指針針對實例而言
this.say = function() {
console.log('hi~')
}
}
對象
- 普通對象: 沒有prototype,有proto
- 函數(shù)對象: 只有函數(shù)有prototype屬性终抽,所有的對象都有proto隱式屬性
- 實例對象: var son = new Person();
創(chuàng)建對象方式
new運算符
MDN:語法new constructor[([arguments])]戳表,創(chuàng)建一個用戶定義的對象類型的實例或具有構(gòu)造函數(shù)的內(nèi)置對象的實例
- 創(chuàng)建一個空的簡單JavaScript對象(即{})
- 鏈接該對象(即設(shè)置該對象的構(gòu)造函數(shù))到另一個對象
- 將步驟1新創(chuàng)建的對象作為this的上下文
- 如果該函數(shù)沒有返回對象,則返回this
繼承:實例可以繼承A對象中的方法和屬性,減少代碼冗余
封裝:對象把實現(xiàn)過程封裝在方法中,調(diào)用者可以不需了解過程直接調(diào)用
多態(tài):一種事物,可以有多種表現(xiàn)形式
是對象的模版
function Person(){
this.name = 'king';
var age = 12; // 私有屬性镜会,是指針針對實例而言
this.say = function() {
console.log('hi~')
}
}
MDN:語法new constructor[([arguments])]戳表,創(chuàng)建一個用戶定義的對象類型的實例或具有構(gòu)造函數(shù)的內(nèi)置對象的實例
但實際上new具體做了什么操作
var son = new Person();
當這段代碼運行的時候昼伴,內(nèi)部實際上執(zhí)行的是:
// 創(chuàng)建一個空對象
var other = new Object();
// 將空對象的原型賦值為構(gòu)造函數(shù)的原型
other.__proto__ = Person.prototype;
// 改變this指向
Person.call(other);
最后一步如何理解匾旭,當構(gòu)造函數(shù)是否返回對象,可做如下嘗試
// 無返回對象時
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'
由此可以得出結(jié)論圃郊,new通過構(gòu)造函數(shù)Person創(chuàng)造出來的實例son,可以訪問Person中的內(nèi)部屬性价涝,以及原型鏈上的方法,當對構(gòu)造函數(shù)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上關(guān)于new操作符的最后一步操作結(jié)果
Object.create()
語法:Object.create(proto[, propertiesObject])色瘩,創(chuàng)建一個新對象,使用現(xiàn)有的對象來提供新創(chuàng)建的對象的proto
內(nèi)部實現(xiàn)方式
Object.create = function (o) {
// o參數(shù)是原型對象逸寓,不需要加.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"
第二個參數(shù)
//該參數(shù)是一個屬性描述對象居兆,它所描述的對象屬性,會添加到實例對象竹伸,作為該對象自身的屬性泥栖。
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的用法非常相似,區(qū)別在于:
字面量和new關(guān)鍵字創(chuàng)建的對象是Object的實例佩伤,原型指向Object.prototype聊倔,繼承內(nèi)置對象Object
Object.create(proto, propertiesObject)創(chuàng)建的對象的原型取決于proto,proto為null生巡,新對象是空對象耙蔑,沒有原型,不繼承任何對象孤荣;proto為指定對象甸陌,新對象的原型指向指定對象,繼承指定對象盐股。propertiesObject是可選參數(shù)钱豁,指定要添加到新對象上的可枚舉的屬性(即其自定義的屬性和方法,可用hasOwnProperty()獲取的疯汁,而不是原型對象上的)的描述符及相應(yīng)的屬性名稱,如果不傳則實例對象為{}牲尺。
Object.create(o),如果o是一個構(gòu)造函數(shù),則采用這種方法來創(chuàng)建對像沒有意義
Object.create(o),如果o是一個字面量對象或?qū)嵗龑ο螅敲聪喈斢谑菍崿F(xiàn)了對象的淺拷貝
封裝
把"屬性"(property)和"方法"(method)谤碳,封裝到一個構(gòu)造函數(shù)里溃卡,并且他的實例對象可以繼承他的所有屬性和方法,構(gòu)建構(gòu)造函數(shù)
prototype
解決所有實例指向prototype對象地址蜒简,而不需要每個實例對象重復(fù)生成構(gòu)造內(nèi)部屬性方法瘸羡,這意味著,我們可以把那些不變的屬性和方法搓茬,直接定義在prototype對象上犹赖,被構(gòu)造函數(shù)實例繼承
// 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對象和某個實例之間的關(guān)系; Person.prototype.isPrototypeOf(son) // true hasOwnProperty()判斷某一個屬性到底是本地屬性卷仑,還是繼承自prototype對象的屬性; son.hasOwnProperty("age") //false in運算符還可以用來遍歷某個對象的所有屬性,包含繼承的屬性 for(var prop in son)
原型峻村,原型鏈
內(nèi)置對象:Object、Function都是js內(nèi)置的函數(shù), 類似的還有我們常用到的Array系枪、RegExp雀哨、Date、Boolean私爷、Number雾棺、String
js分為函數(shù)對象和普通對象,每個對象都有proto屬性衬浑,但是只有函數(shù)對象才有prototype屬性
除了Object的原型對象(Object.prototype)的proto指向null捌浩,其他內(nèi)置函數(shù)對象的原型對象和自定義構(gòu)造函數(shù)的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;
繼承
- 構(gòu)造函數(shù)繼承
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'
差別:與前一種方法相比尸饺,這樣做的優(yōu)點是效率比較高(不用執(zhí)行和建立Person的實例了),比較省內(nèi)存助币。缺點是 Person.prototype和Son.prototype現(xiàn)在指向了同一個對象浪听,那么任何對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的屬性
- 非構(gòu)造函數(shù)繼承
1.json格式的發(fā)明人Douglas Crockford,提出了一個object()函數(shù)俭缓,即后來的內(nèi)置函數(shù)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']
現(xiàn)象:當父對象的屬性值為數(shù)組或者對象時克伊,子對象改變那個屬性值,父對象也會隨之改變华坦,因此愿吹,子對象只是獲得了內(nèi)存地址,而不是真正的拷貝惜姐,extendCopy只能用作基本數(shù)據(jù)類型的拷貝犁跪,這也是早期jquery實現(xiàn)繼承的方式
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']