原型:JavaScript 中,萬(wàn)物皆對(duì)象窗宇!但對(duì)象也是有區(qū)別的措伐。分為普通對(duì)象和函數(shù)對(duì)象
原型的概念:每一個(gè)javascript對(duì)象創(chuàng)建的時(shí)候,就會(huì)與之關(guān)聯(lián)另一個(gè)對(duì)象军俊,這個(gè)對(duì)象就是我們所說(shuō)的原型侥加,每一個(gè)對(duì)象都會(huì)從原型中“繼承”屬性。
構(gòu)造函數(shù)
function Star(age,name) {
this.age = age
this.name = name
this.sing = function(){
console.log("我可以唱歌")
}
}
let ldh = new Star(35,"劉德華")
console.log(ldh)//35,劉德華
console.log(Star.sing)//undefind|實(shí)例成員不能通過(guò)構(gòu)造函數(shù)訪問(wèn)
Star.sex='男';
console.log(Star.sex)//男|靜態(tài)成員只能通過(guò)構(gòu)造函數(shù)訪問(wèn)
console.log(ldh.sex)//undefind|靜態(tài)成員只能通過(guò)構(gòu)造函數(shù)訪問(wèn)
構(gòu)造函數(shù)里面的內(nèi)容粪躬,構(gòu)造函數(shù)里面是有靜態(tài)成員和實(shí)例成員
實(shí)例成員就是構(gòu)造函數(shù)內(nèi)部通過(guò)this添加的成員:age,name,sing都是實(shí)例成員担败,不能通過(guò)構(gòu)造函數(shù)訪問(wèn)
靜態(tài)成員,就是直接在函數(shù)本身上面添加的成員镰官,只能通過(guò)構(gòu)造函數(shù)訪問(wèn)
內(nèi)存浪費(fèi)問(wèn)題:當(dāng)我們使用構(gòu)造函數(shù)創(chuàng)建多個(gè)實(shí)例的時(shí)候提前,如果這個(gè)函數(shù)里面有多個(gè)實(shí)例成員的函數(shù),那么我們每一次創(chuàng)建實(shí)例對(duì)象泳唠,構(gòu)造函數(shù)下的方法都會(huì)重新開辟一個(gè)內(nèi)存空間去存放這些方法岖研。但是明明這些方法都是一個(gè)構(gòu)造函數(shù)里面的,干嘛要這么麻煩警检,還要占用內(nèi)存孙援,所以出現(xiàn)了原型。
一:prototype
在JavaScript中扇雕,每一個(gè)構(gòu)造函數(shù)都有一個(gè) prototype 屬性拓售,該屬性指向函數(shù)的原型對(duì)象。
我們可以把那些不變的方法镶奉,直接定義在prototype對(duì)象上這樣所有的實(shí)例都可以共享這個(gè)方法
function Star(age,name) {
this.age = age
this.name = name
}
Star.prototype.sing = function(){
console.log("我會(huì)唱歌")
}
let ldh = new Star(35,"劉德華")
ldh.sing//我會(huì)唱歌
因?yàn)閜rototype是一個(gè)對(duì)象础淤,所以我們稱構(gòu)造函數(shù)的prototype為原型對(duì)象
function Person(){}
Person.prototype.name = "protoName"
var person = new Person()
console.log(person.name) // protoName
解析:person對(duì)象沒(méi)有name屬性,他將去他的構(gòu)造函數(shù)(Person)里面去找哨苛,構(gòu)造函數(shù)里面沒(méi)有鸽凶,就去構(gòu)造函數(shù)的原型(Person.prototype)里面找,如果沒(méi)有找到返回值為undefined建峭。
二:proto[注解:是兩個(gè)_,而不是一個(gè)]
每一個(gè)對(duì)象都有一個(gè)proto屬性玻侥,指向構(gòu)造函數(shù)的prototype原型對(duì)象,他指向該對(duì)象的原型亿蒸。
function Person(){}
var person = new Person()
console.log(person.__proto__ === Person.prototype) // true
三:constructor(構(gòu)造函數(shù))
每個(gè)原型都有一個(gè)constructor屬性(也就是說(shuō)凑兰,constructor是存在于prototype上面的屬性),指向該關(guān)聯(lián)的構(gòu)造函數(shù)边锁。
function Star(){}
var ldh = new Star()
console.log(Star.prototype.constructor === Star) // true
console.log(Star.prototype.constructor === ldh.constructor) // true
console.log(ldh.__proto__.constructor === Star.prototype.constructor) // true
解析:ldh沒(méi)有constructor屬性 -> 查找Star的constructor屬性姑食,Star沒(méi)有constructor屬性 -> 查找Star.prototype的constructor屬性
function Star(age, name) {
this.age = age
this.name = name
}
Star.prototype = {
constructor:Star,//
sing:function(){
console.log("我會(huì)唱歌")
},
movie:function(){
console.log("我會(huì)演電影")
}
}
let ldh = new Star(35, "劉德華")
console.log(ldh.__proto__.constructor)//function Object() { [native code] }
解析:為何不是Star,Star.prototype本身就是一個(gè)對(duì)象茅坛,你給Star.prototype賦值一個(gè)對(duì)象音半,那就會(huì)把原來(lái)prototype上面的對(duì)象給覆蓋了,所以這個(gè)時(shí)候就可以使用constructor了
整合:
function Animal(name) {
this.name= "animal";
this.age= 18;
this.setName= function (name) {
this.name = name
}
}
Animal.prototype.setAge =function(age){
this.age = age
}
var dog = new Animal("dog")
var cat = new Animal("cat")
Animal是構(gòu)造函數(shù),dog曹鸠、cat是構(gòu)造函數(shù)創(chuàng)建的對(duì)象
繼承:JavaScript 只有一種結(jié)構(gòu):對(duì)象煌茬。每個(gè)實(shí)例對(duì)象(object)都有一個(gè)私有屬性(稱之為 proto )指向它的構(gòu)造函數(shù)的原型對(duì)象(prototype)。該原型對(duì)象也有一個(gè)自己的原型對(duì)象(proto)物延,層層向上直到一個(gè)對(duì)象的原型對(duì)象為 null宣旱。null 沒(méi)有原型,并作為這個(gè)原型鏈中的最后一個(gè)環(huán)節(jié)叛薯。
給dosomething的原型加上屬性
function doSomething(){}
doSomething.prototype.foo = "bar";
console.log( doSomething.prototype );
運(yùn)行結(jié)果
{
constructor: ? doSomething(),
foo: "bar",
__proto__: {
constructor: ? Object(),
hasOwnProperty: ? hasOwnProperty(),
isPrototypeOf: ? isPrototypeOf(),
propertyIsEnumerable: ? propertyIsEnumerable(),
toLocaleString: ? toLocaleString(),
toString: ? toString(),
valueOf: ? valueOf()
}
}
原型鏈
1:首先說(shuō)好了的每一個(gè)實(shí)例都有proto浑吟,而這個(gè)就指向的是構(gòu)造函數(shù)的對(duì)象原型,所以ldh.proto和Star.prototype是等價(jià)的耗溜,即ldh.proto`能夠只會(huì)Star這個(gè)構(gòu)造函數(shù)呢组力,本質(zhì)上就是通過(guò)Star.prototype指回去的。
2:那么我們就會(huì)想到Star.prototype的proto指向的會(huì)是誰(shuí)呢抖拴,一打印就能看的出指向的是Object.prototype燎字,所以不用想也知道他的構(gòu)造函數(shù)肯定是Object了
3:那Object.prototype還有沒(méi)有proto,打印一看就知道是為null阿宅,所以我們知道原型的終點(diǎn)就是null
js的成員查找機(jī)制
當(dāng)訪問(wèn)一個(gè)對(duì)象的屬性或者方法的時(shí)候候衍,首先查找這個(gè)對(duì)象自身有沒(méi)有這個(gè)屬性,如果沒(méi)有就去找他的原型洒放,也就是proto指向的prototype原型對(duì)象蛉鹿,如果還沒(méi)有就去查找原型對(duì)象的原型,即Object的原型對(duì)象以此類推一直找到null為止
javascript是面向?qū)ο缶幊趟枷胪嫦驅(qū)ο笏枷胗腥筇匦裕悍庋b妖异、繼承、多態(tài)领追。
封裝
es5沒(méi)有class概念他膳,類就是一個(gè)函數(shù)有自己的方法和屬性,將方法和屬性組成一個(gè)類的過(guò)程就是封裝绒窑。
封裝有3種:構(gòu)造函數(shù)棕孙、prototype、.
1:構(gòu)造函數(shù)
·首字母大寫
·內(nèi)部使用this
·使用new生成實(shí)例
function Cat(name,color){
this.name = name;
this.color = color;
this.eat = function(){alert("吃老鼠")};
}
缺點(diǎn):通過(guò)this定義的屬性和方法回论,我們實(shí)例化對(duì)象的時(shí)候都會(huì)內(nèi)存中重新復(fù)制一份散罕,造成內(nèi)存的浪費(fèi)。
2:prototype
function Cat(name,color){
this.name = name;
this.color = color;
}
Cat.prototype.type = "貓科動(dòng)物";
Cat.prototype.eat = function(){alert("吃老鼠")};
優(yōu)點(diǎn):所有的實(shí)例的type傀蓉,eat方法指向的同一個(gè)內(nèi)存地址,提高了效率职抡。
缺點(diǎn):編寫麻煩
3:.
function Cat(){}
Cat.type = "貓科動(dòng)物";
Cat.eat = function(){alert("吃老鼠")};
var blueCat=new Cat();
console.log(Cat.type)//貓科動(dòng)物
console.log(blueCat.type)//undefined
缺點(diǎn):只能通過(guò)該類訪問(wèn)
javascript也有private public protected
var Book = function (id, name, price) {
//private(在函數(shù)內(nèi)部定義葬燎,函數(shù)外部訪問(wèn)不到,實(shí)例化之后實(shí)例化的對(duì)象訪問(wèn)不到)
var num = 1;
var id = id;
function checkId() {
console.log('private')
}
//protected(可以訪問(wèn)到函數(shù)內(nèi)部的私有屬性和私有方法,在實(shí)例化之后就可以對(duì)實(shí)例化的類進(jìn)行初始化拿到函數(shù)的私有屬性)
this.getName = function () {
console.log(id)
}
this.getPrice = function () {
console.log(price)
}
//public(實(shí)例化的之后谱净,實(shí)例化的對(duì)象就可以訪問(wèn)到了~)
this.name = name;
this.copy = function () {
console.log('this is public')
}
}
//在Book的原型上添加的方法實(shí)例化之后可以被實(shí)例化對(duì)象繼承
Book.prototype.proFunction = function () {
console.log('this is proFunction')
}
//在函數(shù)外部通過(guò).語(yǔ)法創(chuàng)建的屬性和方法窑邦,只能通過(guò)該類訪問(wèn),實(shí)例化對(duì)象訪問(wèn)不到
Book.setTime = function () {
console.log('this is new time')
}
var book1 = new Book('111','悲慘世界','$99')
book1.getName(); // 111 getName是protected壕探,可以訪問(wèn)到類的私有屬性冈钦,所以實(shí)例化之后也可以訪問(wèn)到函數(shù)的私有屬性
book1.checkId(); //報(bào)錯(cuò)book1.checkId is not a function
console.log(book1.id) // undefined id是在函數(shù)內(nèi)部通過(guò)定義的,是私有屬性李请,所以實(shí)例化對(duì)象訪問(wèn)不到
console.log(book1.name) //name 是通過(guò)this創(chuàng)建的瞧筛,所以在實(shí)例化的時(shí)候會(huì)在book1中復(fù)制一遍name屬性,所以可以訪問(wèn)到
book1.copy() //this is public
book1.proFunction(); //this is proFunction
Book.setTime(); //this is new time
book1.setTime(); //報(bào)錯(cuò)book1.setTime is not a function
new的實(shí)質(zhì)
var o = new Object()导盅;
1:新建一個(gè)對(duì)象o
2:o. proto = Object.prototype 將新創(chuàng)建的對(duì)象的proto屬性指向構(gòu)造函數(shù)的prototype
3:將this指向新創(chuàng)建的對(duì)象
4:返回新對(duì)象较幌,但是這里需要看構(gòu)造函數(shù)有沒(méi)有返回值,如果構(gòu)造函數(shù)的返回值為基本數(shù)據(jù)類型string,boolean,number,null,undefined,那么就返回新對(duì)象白翻,如果構(gòu)造函數(shù)的返回值為對(duì)象類型乍炉,那么就返回這個(gè)對(duì)象類型
繼承
js繼承有8類:原型鏈繼承、借用構(gòu)造函數(shù)繼承滤馍、組合繼承岛琼、原型式繼承、寄生式繼承巢株、寄生組合式繼承槐瑞、混入方式繼承多個(gè)對(duì)象、ES6類繼承extends
1:原型鏈繼承[SubType.prototype = new SuperType(); ]
function SuperType(){
this.property = true;
this.colors = ["red", "blue", "green"];
}
function SubType(){
this.subproperty = false;
}
// 這里是關(guān)鍵纯续,創(chuàng)建SuperType的實(shí)例随珠,并將該實(shí)例賦值給SubType.prototype
SubType.prototype = new SuperType();
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
var instance2 = new SubType();
alert(instance2.colors); //"red,blue,green,black"
缺點(diǎn):原型鏈方案存在的缺點(diǎn):多個(gè)實(shí)例對(duì)引用類型的操作會(huì)被篡改。
2:借用構(gòu)造函數(shù)繼承[子類里面SuperType.call(this);]
function SuperType(){
this.color=["red","green","blue"];
}
function SubType(){
//繼承自SuperType
SuperType.call(this);
}
var instance1 = new SubType();
instance1.color.push("black");
alert(instance1.color);//"red,green,blue,black"
var instance2 = new SubType();
alert(instance2.color);//"red,green,blue"
核心代碼是SuperType.call(this)猬错,創(chuàng)建子類實(shí)例時(shí)調(diào)用SuperType構(gòu)造函數(shù)窗看,于是SubType的每個(gè)實(shí)例都會(huì)將SuperType中的屬性復(fù)制一份。
缺點(diǎn):
只能繼承父類的實(shí)例屬性和方法倦炒,不能繼承原型屬性/方法显沈,無(wú)法實(shí)現(xiàn)復(fù)用,每個(gè)子類都有父類實(shí)例函數(shù)的副本逢唤,影響性能
3:組合繼承
// 組合繼承: 借用構(gòu)造函數(shù)繼承 + 原型鏈繼承 一起使用
// 借用構(gòu)造函數(shù)繼承 :可以繼承構(gòu)造函數(shù)的屬性
// 原型鏈繼承 :可以繼承原型鏈的成員
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
};
function SubType(name, age){
// 繼承屬性拉讯, 構(gòu)造函數(shù)繼承,第二次調(diào)用SuperType()
SuperType.call(this, name);
this.age = age;
}
// 繼承方法鳖藕,構(gòu)建原型鏈繼承魔慷,第一次調(diào)用SuperType()
SubType.prototype = new SuperType();
// 重寫SubType.prototype的constructor屬性,指向自己的構(gòu)造函數(shù)SubType
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
alert(this.age);
};
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
instance1.sayName(); //"Nicholas";
instance1.sayAge(); //29
//
var instance2 = new SubType("Greg", 27);
alert(instance2.colors); //"red,blue,green"
instance2.sayName(); //"Greg";
instance2.sayAge(); //27
缺點(diǎn):
第一次調(diào)用SuperType():給SubType.prototype寫入兩個(gè)屬性name著恩,color院尔。
第二次調(diào)用SuperType():給instance1寫入兩個(gè)屬性name蜻展,color。
實(shí)例對(duì)象instance1上的兩個(gè)屬性就屏蔽了其原型對(duì)象SubType.prototype的兩個(gè)同名屬性邀摆。所以纵顾,組合模式的缺點(diǎn)就是在使用子類創(chuàng)建實(shí)例對(duì)象時(shí),其原型中會(huì)存在兩份相同的屬性/方法栋盹。
4:原型式繼承
利用一個(gè)空對(duì)象作為中介施逾,將某個(gè)對(duì)象直接賦值給空對(duì)象構(gòu)造函數(shù)的原型。object()對(duì)傳入其中的對(duì)象執(zhí)行了一次淺復(fù)制例获,將構(gòu)造函數(shù)F的原型直接指向傳入的對(duì)象汉额。
function object(obj){
function F(){}
F.prototype = obj;
return new F();
}
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = object(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");
var yetAnotherPerson = object(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
alert(person.friends); //"Shelby,Court,Van,Rob,Barbie"
缺點(diǎn):
原型鏈繼承多個(gè)實(shí)例的引用類型屬性指向相同,存在篡改的可能躏敢。
無(wú)法傳遞參數(shù)
另外闷愤,ES5中存在Object.create()的方法,能夠代替上面的object方法件余。
5:寄生式繼承
function object(obj){
function F(){}
F.prototype = obj;
return new F();
}
function createAnother(original){
var clone = object(original); // 通過(guò)調(diào)用 object() 函數(shù)創(chuàng)建一個(gè)新對(duì)象
//var clone = Object.create(original); es5中和上面等價(jià)
clone.sayHi = function(){ // 以某種方式來(lái)增強(qiáng)對(duì)象
alert("hi");
};
return clone; // 返回這個(gè)對(duì)象
}
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi(); //"hi"
缺點(diǎn)(同原型式繼承):
原型鏈繼承多個(gè)實(shí)例的引用類型屬性指向相同讥脐,存在篡改的可能。
無(wú)法傳遞參數(shù)
6:寄生組合式繼承
結(jié)合借用構(gòu)造函數(shù)傳遞參數(shù)和寄生模式實(shí)現(xiàn)繼承
function inheritPrototype(subType, superType){
var prototype = Object.create(superType.prototype); // 創(chuàng)建對(duì)象啼器,創(chuàng)建父類原型的一個(gè)副本
prototype.constructor = subType; // 增強(qiáng)對(duì)象旬渠,彌補(bǔ)因重寫原型而失去的默認(rèn)的constructor 屬性
subType.prototype = prototype; // 指定對(duì)象,將新創(chuàng)建的對(duì)象賦值給子類的原型
}
// 父類初始化實(shí)例屬性和原型屬性
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
};
// 借用構(gòu)造函數(shù)傳遞增強(qiáng)子類實(shí)例屬性(支持傳參和避免篡改)
function SubType(name, age){
SuperType.call(this, name);
this.age = age;
}
// 將父類原型指向子類
inheritPrototype(SubType, SuperType);
// 新增子類原型屬性
SubType.prototype.sayAge = function(){
alert(this.age);
}
var instance1 = new SubType("xyc", 23);
var instance2 = new SubType("lxy", 23);
instance1.colors.push("2"); // ["red", "blue", "green", "2"]
instance1.colors.push("3"); // ["red", "blue", "green", "3"]
這個(gè)例子的高效率體現(xiàn)在它只調(diào)用了一次SuperType 構(gòu)造函數(shù)端壳,并且因此避免了在SubType.prototype 上創(chuàng)建不必要的告丢、多余的屬性。于此同時(shí)损谦,原型鏈還能保持不變岖免;因此,還能夠正常使用instanceof 和isPrototypeOf()
這是最成熟的方法照捡,也是現(xiàn)在庫(kù)實(shí)現(xiàn)的方法
7:混入方式繼承多個(gè)對(duì)象
function MyClass() {
SuperClass.call(this);
OtherSuperClass.call(this);
}
// 繼承一個(gè)類
MyClass.prototype = Object.create(SuperClass.prototype);
// 混合其它
Object.assign(MyClass.prototype, OtherSuperClass.prototype);
// 重新指定constructor
MyClass.prototype.constructor = MyClass;
MyClass.prototype.myMethod = function() {
// do something
};
Object.assign會(huì)把 OtherSuperClass原型上的函數(shù)拷貝到 MyClass原型上颅湘,使 MyClass 的所有實(shí)例都可用 OtherSuperClass 的方法。
8:ES6類繼承extends
extends關(guān)鍵字主要用于類聲明或者類表達(dá)式中栗精,以創(chuàng)建一個(gè)類闯参,該類是另一個(gè)類的子類。其中constructor表示構(gòu)造函數(shù)悲立,一個(gè)類中只能有一個(gè)構(gòu)造函數(shù)鹿寨,有多個(gè)會(huì)報(bào)出SyntaxError錯(cuò)誤,如果沒(méi)有顯式指定構(gòu)造方法,則會(huì)添加默認(rèn)的 constructor方法薪夕,使用例子如下脚草。
class Rectangle {
// constructor
constructor(height, width) {
this.height = height;
this.width = width;
}
// Getter
get area() {
return this.calcArea()
}
// Method
calcArea() {
return this.height * this.width;
}
}
const rectangle = new Rectangle(10, 20);
console.log(rectangle.area);
// 輸出 200
-----------------------------------------------------------------
// 繼承
class Square extends Rectangle {
constructor(length) {
super(length, length);
// 如果子類中存在構(gòu)造函數(shù),則需要在使用“this”之前首先調(diào)用 super()原献。
this.name = 'Square';
}
get area() {
return this.height * this.width;
}
}
const square = new Square(10);
console.log(square.area);
// 輸出 100
extends繼承的核心代碼如下玩讳,其實(shí)現(xiàn)和上述的寄生組合式繼承方式一樣
function _inherits(subType, superType) {
// 創(chuàng)建對(duì)象涩蜘,創(chuàng)建父類原型的一個(gè)副本
// 增強(qiáng)對(duì)象嚼贡,彌補(bǔ)因重寫原型而失去的默認(rèn)的constructor 屬性
// 指定對(duì)象熏纯,將新創(chuàng)建的對(duì)象賦值給子類的原型
subType.prototype = Object.create(superType && superType.prototype, {
constructor: {
value: subType,
enumerable: false,
writable: true,
configurable: true
}
});
if (superType) {
Object.setPrototypeOf
? Object.setPrototypeOf(subType, superType)
: subType.__proto__ = superType;
}
}
來(lái)源:https://juejin.cn/post/6844903696111763470
https://juejin.cn/post/6844903480868470798