OOP:Object Oriented Programming 面向對象編程。
題外話:面向對象的範圍實在太大,先把這些大的東西理解理解。
根據高程和權威指南上的定義,對象是一組沒有特定順序的值,咱們能夠把對象當作是從字符串到值的映射。
原型:編程
根據權威指南上的定義:每個js對象(null除外)都和另外一個對象相關聯, 「另外一個」對象就是咱們熟知的原型,每個對象都從原型上繼承屬性。原型也是對象。 通俗點講,就是一個擁有prototype屬性、且這個屬性指向函數的原型對象的對象。
原型的好處就是: 原型上的方法和屬性會被全部實例所共享。
原型鏈:app
當訪問某個實例屬性或方法時,會先自身對象中查找,查不到時再往當前對象原型上找; 若依然沒找到,則會繼續往原型對象的原型上找,一直到找到結果或者找到Object.prototype爲止也沒找到, 而後這時就會返回undefined,這麼一個鏈式查找過程造成的結構就叫原型鏈。
每一個對象都有一個__proto__屬性,函數也是對象,因此函數也有;
每一個函數都有一個prototype屬性,而實例對象沒有。
封裝,繼承,多態(指一個方法能夠有多種調用方式:例若有參或無參)
let obj={};
函數
let obj=new Object();
this
let obj=Object.create({}/null);
spa
注意!!前面這三種方式的的構造函數都是Object,而Object已是原型鏈的最頂端了,因此Object.prototype都爲undefined。能夠用實例的__proto__.constructor查看構造函數是否指向Object構造函數。prototype
function person(name,job){ let o={}; o.name=name; o.job=job; o.sayName=function(){ console.log(this.name); } return o; } let p1=person('nagi','sleep'); console.log(p1.constructor); // 指向Object p1 instanceof person; // false;
優缺點:指針
這種模式雖然解決了量產對象的問題,但卻沒法獲知當前對象是何類型 (例如類型:Array,Math等內置對象,或者BOM(window)/DOM的宿主對象,又或者自定義對象等)
注意點:函數首字母不用大寫。code
function Person(name,job){ this.name=name; this.job=job; this.sayName=function(){ console.log(this.name); } } let p1=new Person('nagi','sleep'); console.log(p1.constructor); // 指向Person p1 instanceof Person; // true
與工廠模式區別:對象
a.沒有顯式建立對象; b.直接將屬性和方法賦給了this對象 c.不有return語句;
拓展:new操做符作了些什麼?繼承
a.建立一個新對象; b.將構造函數的做用域賦給新對象(所以this就指向一這個新對象); c.執行構造函數中的代碼(爲這個新對象添加屬性); d.返回新對象(默認返回當前對象,除非有顯示返回某個對象)
優缺點:
首先是解決了工廠模式中不能判斷類型的問題; 但缺點是每實例一次的同時還會把方法從新建立一遍,形成內存資源浪費; 其次,因構造函數與其它函數的惟一區別就是調用方式不同,因此當被看成普通函數調用時,其內部的this會指向全局,引起做用域問題。
function Person(){}; // 寫法一: Person.prototype.name='nagi'; Person.prototype.job='sleep'; Person.prototype.sayName=function(){ console.log(this.name); }; // 寫法二:注意,這種直接更改原型指向的寫法,會改變constructor指向,指向Object構造函數 /* Person.prototype={ // constructor:Person, name:'nagi', job:'sleep', sayName:function (){ console.log(this.name) } }*/ let p1=new Person();
優缺點:
優勢:解決了上述構造函數的缺點,原型對象上的屬性和方法爲全部實例所共享。 缺點: a.缺點也是全部實例共享方法和屬性,所以其中一個實例更改了引用類型的屬性值時,其餘的實例也會被迫改變。(屬性) b.默認狀況下全部實例都取得相同的屬性值。
function Person(name,job){ this.name=name; this.job=job; } Person.prototype.sayName=function(){ console.log(this.name); } let p1=new Person('nagi','sleep');
function Parent(){ this.name='nagi'; this.colors=['red','blue','green']; } Parent.prototype.sayName=function(){ console.log(this.name); } function Child(){ this.job='sleep'; }; Child.prototype=new Parent(); // 要注意:這種重寫原型鏈的寫法是會切斷構造函數與最初原型之間的聯繫的, // 意味着此時Child.prototype.constructor指向Parent var child=new Child();
這種方式的基本思想就是利用原型讓一個引用類型繼承另外一個引用類型的屬性和方法。
問題點:
a.上述例子中,經過原型繼承方式繼承至關於專門建立了一個Child.prototype.colors的屬性, 由於引用類型的原型屬性會被全部實例共享,也就意味着Child的全部實例都會共享colors這一屬性, 當其中一實例修改它的值時,那麼其餘的實例的值也會跟着被改變。 b.在建立子類實例時,不能向超類型的構造函數中傳遞參數。鑑於此,實際不多單獨用原型鏈繼承。
超類型:好比Child類繼承了Parent類的屬性,那麼Parent就是Child的超類(也叫父類)。
function Parent(name){ console.log(`子類傳進的name值是:${name}`) this.name='nagi'; this.colors=['red','blue','green']; } Parent.prototype.sayName=function(){ console.log(this.name); } function Child(){ Parent.call(this,'Bob') this.job='sleep'; }; var child1=new Child(); var child2=new Child();
基本思想是在子類構造函數的內部調用超類(或父類)型構造函數。
優缺點:
優點:解決了原型鏈的兩個問題; 缺點: a. 方法都在構造函數定義的話,那函數複用就無從談起了。 b. 父類在原型中定義的方法,對於子類型來講是不可見的。鑑於此,構造函數也不多用。[捂臉]
function Parent(name){ this.name=name; this.job='sleep'; this.colors=['red','blue','green']; } Parent.prototype.sayName=function(){ console.log(this.name); } function Child(name){ Parent.call(this,name); this.job='eat'; } Child.prototype=new Parent(); Child.prototype.constructor=Child; let child1=new Child('nagi'); let child2=new Child('Bob');
其實現思路是用原型鏈實現對原型屬性和方法的繼承,而藉助構造函數實現對實例屬性的繼承。 優勢:在前二者基礎上,補足了構造函數和原型鏈的缺點,是比較經常使用的方式。
function Parent(name){ this.name=name; this.job='sleep'; this.colors=['red','blue','green']; } Parent.prototype.sayName=function(){ console.log(this.name); } function Child(){ Parent.call(this); // 保證構造函數指針指向Child } Child.prototype=Object.create(Parent.prototype,{name:{value:'nagi'},job:{value:'eat'}}) // 若是想同時繼承多個,還可以使用Object.assign()添加屬性 // Object.assign(A.prototype, B.prototype); let child=new Child();
類繼承,class A extends B