1.構(gòu)造函數(shù) constructor
使用構(gòu)造函數(shù)就可以實現(xiàn)代碼的復(fù)用拯坟,創(chuàng)建具有相似的特征(屬性)和行為(方法)的對象。
構(gòu)造函數(shù)必須通過關(guān)鍵字new
調(diào)用韭山,會創(chuàng)建一個新的內(nèi)存空間郁季,函數(shù)體內(nèi)部的 this 指向該內(nèi)存。構(gòu)造函數(shù)的最后一步默認(rèn)(隱式)返回this钱磅,如果手動添加返回值梦裂,若為基本數(shù)據(jù)類型則依然為this,如為引用類型(對象/數(shù)組)則返回該引用盖淡。
function Person(age) {
this.age = age;
return age
}
var p = new Person(18);//{age:18}
console.log(p.constructor);//function Person
2.原型鏈
每個對象具有內(nèi)部原型__proto__
年柠,每個函數(shù)具有構(gòu)造器原型prototype
。
讀取對象屬性時褪迟,如屬性不存在冗恨,則會在其__proto__
上尋找,如還不存在則繼續(xù)往上一級尋找味赃。
函數(shù)默認(rèn)的prototype
是一個對象掀抹,它僅有constructor
屬性,并指向該函數(shù)自身心俗。因此其實例對象也會繼承該constructor
屬性(詳見下文 2.4 構(gòu)造函數(shù))傲武。
obj.__proto__ === obj.constructor.prototype === Fn.prototype
既然Fn.prototype
是一個對象,顯然對象的構(gòu)造函數(shù)為Object
另凌,因此有:
Fn.prototype.__proto__ === Object.prototype
除了直接讀取或?qū)懭?code>__proto__谱轨、prototype
外,也可以使用以下方法:Object.setPrototypeOf()
(寫操作)吠谢、Object.getPrototypeOf()
(讀操作)土童、Object.create()
(生成操作)。
//以下均為===(全等)關(guān)系
new Person().__proto__
=== Person.prototype
=== Object.getPropertyOf(new Person())
console.log(Person.prototype); // {constructor:function Person(){}}
console.log(p.prototype); // undefined
函數(shù)也是對象的一種工坊。對于函數(shù)作為對象來說献汗,上面的規(guī)則同樣適用敢订,函數(shù)對象都是由Function函數(shù)生成的:
function fn(){}
fn.__proto__ === Function.prototype;//true
Function.__proto__ === Function.prototype;//true
Object.__proto__ === Function.prototype;//true
2.1 原型可以共享屬性和方法
原型鏈上的屬性會被實例所繼承,且為全等關(guān)系罢吃。這是原型鏈最大的作用楚午。
function Person() {}
Person.prototype.arr = [0,1,2];
var p1 = new Person() , p2 = new Person();
p1.arr === p2.arr;//true
function Animal(){
this.arr = [0,1,2];
}
var a1 = new Animal() , a2 = new Animal();
a1.arr === a2.arr;//false
實例中繼承的引用類型修改會導(dǎo)致整個原型鏈的變動。
p1.arr.push(3);
p2.arr//[0,1,2,3]
2.2 判斷屬性類型
- hasOwnProperty()
用于判斷屬性是否是實例屬性尿招。true說明是實例屬性矾柜,false說明不是實例屬性。 - in
對象能夠訪問給定屬性時返回true,無論該屬性存在于實例中還是原型中就谜。-
for ... in ...
遍歷同樣不會區(qū)分實例屬性和原型屬性
-
2.3 instanceof 和 typeof
-
obj instanceof Object
檢測Object.prototype是否存在于obj的原型鏈上怪蔑。-
null instanceof null
會報錯:Right-hand side of 'instanceof' is not an object
-
-
typeof XXX
返回XXX類型的字符串,可以為"number", "string", "object", "boolean", "function", "undefined", "symbol"-
type of null
會返回"object"
-
- 直接使用
Object.prototype.toString.call(XXX)
可以得到比較滿意的結(jié)果
Object.prototype.toString.call('hi') // "[object String]"
function Person() {
this.age = 20;
};
var a = Person;
function Student() { };
Student.prototype = new Person();//繼承原型
var s = new Student();
console.log(Person instanceof Function);//true
//s.__proto__===Student.prototype===new Person()
//new Person.__proto__=== Person.prototype
console.log(s.__proto__.__proto__ === Person.prototype);//true
console.log(s instanceof Person);//true
console.log(s.__proto__ === Student.prototype);//true
console.log(s instanceof Student);//true
2.4 constructor
constructor存在于每一個函數(shù)的 prototype 中丧荐,指向函數(shù)自身缆瓣。
//以下均為===(全等)關(guān)系
Person
Person.prototype.constructor
p.constructor
p.__proto__.constructor
//function Function(){} 每個函數(shù)都是通過new Function()構(gòu)造的,包括構(gòu)造函數(shù)
console.log(Person.constructor);
console.log(Function.constructor);
console.log(Object.constructor);
// function Object(){} 每個對象都是通過new Object()構(gòu)造的
console.log({}.constructor);
//function Array(){} 每個數(shù)組都是通過new Array()構(gòu)造的
console.log([].constructor);
2.5 特例 Function 與 Object
- Object的prototype也是一個類型為"object"的對象虹统,但比一般函數(shù)的默認(rèn)prototype多了一大堆方法弓坞,這些方法都是JavaScript對象的系統(tǒng)默認(rèn)方法。
Object.prototype.__proto__ === null
车荔,這就是JavaScript原型鏈的終點渡冻。(否則若按照通用規(guī)則Object.prototype.__proto__ === Object.prototype
會造成無限遞歸) - Function
不同于type of 一般函數(shù).prototype === 'object'
,為一個包含constructor
的對象夸赫,type of Function.prototype === 'function'
菩帝。
規(guī)定Function.prototype.__proto__ === Object.prototype
。(這是因為如果按照通用規(guī)則Function.prototype.__proto__ === Function.prototype
會造成無限遞歸茬腿,且可以讓__proto__
構(gòu)成的原型鏈指向了唯一的終點:Object.prototype.__proto__ === null
)
console.log(typeof Function.prototype);// "function"
console.log(Function.prototype);// ? () { [native code] } 系統(tǒng)編譯好的二進(jìn)制代碼
- Function instanceof Object //true
Function.__proto__ == Function.prototype => Function.prototype.__proto__ == Object.prototype - Object instanceof Object //true
Object.__proto__ == Function.prototype => Function.prototype.__proto__ == Object.prototype - Function instanceof Function //true
Function.__proto__ == Function.prototype - Object instanceof Function //true
Object.__proto__ == Function.prototype
2.6 復(fù)寫原型 & 原型鏈繼承
原型和實例是動態(tài)關(guān)聯(lián)的呼奢,因此先生成實例再修改原型俺泣,實例依然可以繼承修改結(jié)果固额。
當(dāng)原型被復(fù)寫(本質(zhì)上只是prototype指向一個新的對象,原原型對象依然存在)扫尖,原有實例依然繼承原原型悴品,新實例繼承新原型禀综。此時constructor也為新原型的constructor。
function Animal(){}
var dog = new Animal();
Animal.prototype.age = 18;
console.log(dog.age,dog.constructor);//18 Animal
//通過原型繼承,peter可以拿到Human中的實例屬性,以及Animal和Animal.prototype中的原型屬性
function Human(){}
Human.prototype = new Animal();
var peter = new Human();
console.log(peter.age,peter.constructor);//18 Animal
Animal.prototype = {};
var cat = new Animal();
console.log(dog.age,dog.constructor);//18 Animal
console.log(peter.age,peter.constructor);//18 Animal
console.log(cat.age,cat.constructor);//undefined Object
Animal.prototype = {constructor:Animal};
var duck = new Animal();
console.log(duck.age,duck.constructor);//undefined Animal
- 缺點 :
如 2.1 中所說苔严,原型對象上引用類型的值可以通過實例進(jìn)行修改定枷,致使所有實例的該引用類型值隨之改變,這是原型鏈繼承的弊端之一届氢。另一弊端是無法通過子類向父類中傳參欠窒。
2.7 借用構(gòu)造函數(shù)繼承
借用構(gòu)造函數(shù)繼承,是在子類的構(gòu)造函數(shù)中通過 apply ()
或 call ()
調(diào)用父類構(gòu)造函數(shù)退子,以實現(xiàn)繼承岖妄。
function Animal(age) {
this.age = age;
this.friends = ["A"]
}
Animal.prototype.shout = "wowow";
function Human({name, age}) {
this.name = name;
Animal.call(this, age);
}
var peter = new Human({name:"peter", age:18});
peter.friends.push("B");
var tom = new Human({name:"tom", age:22});
console.log(peter.shout);//undefined
console.log(peter.name, peter.age, peter.friends, peter.constructor);//peter 18 [A,B] Human
console.log(tom.name, tom.age, tom.friends, tom.constructor);//tom 22 [A] Human
- 缺點:
這種形式的繼承型将,每個子類實例都會拷貝一份父類構(gòu)造函數(shù)中的方法,作為實例自己的方法荐虐,因此每個引用類型也是獨立的而非指針七兜,不會相互影響。但相對的福扬,占用內(nèi)存大腕铸,復(fù)用性差,且實例一旦生成就和父類無關(guān)忧换,父類的修改只能影響到修改之后生成的實例恬惯。
2.8 組合繼承
將兩者結(jié)合到一起
- 將引用類型 / 待傳參的方法放到父類中,通過
.call
進(jìn)行拷貝,獲取獨立內(nèi)存,不會相互影響 - 將基本類型 / 無參數(shù)方法放到父類的原型鏈上,將父類的實例作為子類的原型,并最終通過原型鏈繼承給實例,占用內(nèi)存小
function Animal(age) {
if (age) this.age = age;
this.friends = ["A"]
}
Animal.prototype.shout = "wowow";
function Human(name, age) {
this.name = name;
Animal.call(this, age);
}
Human.prototype = new Animal(5);//繼承父類原型
Human.prototype.constructor = Human;//讓子類原型對象的`constructor`屬性指向子類自身,因為在上一步中亚茬,`constructor`屬性被覆蓋為父類的構(gòu)造函數(shù)
var peter = new Human("peter", 18);
peter.friends.push("B");
var tom = new Human("tom");
console.log(peter.shout);//wowow
console.log(peter.name, peter.age, peter.friends, peter.constructor);//peter 18 Human [A,B]
console.log(tom.name, tom.age, tom.friends, tom.constructor);//tom 5 Human [A]
console.log(peter instanceof Human);//true
console.log(peter instanceof Animal);//true
3.Class
ES6引入了Class(類)這個概念,通過class關(guān)鍵字可以定義類浓恳,使得語法上更類似面向?qū)ο笳Z言刹缝。但本質(zhì)上類就是一個構(gòu)造函數(shù),因此其各方面性質(zhì)和構(gòu)造函數(shù)相同颈将。
class不存在變量提升梢夯,所以需要先定義再使用
類中默認(rèn)為嚴(yán)格模式,其this
不會指向window
3.0 類的定義及成員
class A {};
var B = class {};
var C = class D {
log() {
console.log(D.prototype == this.__proto__);
}
}
new C().log();//true
需要注意的是晴圾,這個類的名字是C而不是D颂砸,D只在Class的內(nèi)部代碼可用,指代當(dāng)前類死姚。
-
constructor
- 此構(gòu)造方法非彼構(gòu)造方法人乓,不是
Person.constructor
。 - 如沒有顯式定義都毒,會隱式生成一個constructor方法色罚。
- constructor方法默認(rèn)(隱式)返回實例對象this,也可以手動添加對象/數(shù)組作為返回值账劲。
- 此構(gòu)造方法非彼構(gòu)造方法人乓,不是
實例成員
constructor 中通過 this 聲明的成員都稱為實例成員戳护,只能通過實例訪問
ES7(ES2016)起類中聲明的不加static
的成員都視為實例成員-
原型成員(在ES7(ES2016)中廢棄)
一種特殊的成員,僅在ES6中存在瀑焦,在類中聲明但不加static
腌且,既不是實例屬性,也不是靜態(tài)屬性榛瓮。
原型成員定義在class的prototype上铺董,可同時被類和實例訪問(類似python)。- 屬性和方法可直接被類調(diào)用榆芦,可以通過繼承被實例調(diào)用
- 方法不需要使用 function 關(guān)鍵字柄粹,且不使用逗號分隔
- 由于引用類型的值不會出現(xiàn)在原型屬性上喘鸟,避免了原型鏈繼承的實例引用值修改影響原型鏈問題
class Person{//定義了一個名字為Person的類
value = 600;
constructor(name,age){//構(gòu)造方法,用來接收參數(shù)
this.name = name;//this代表的是實例對象
this.age = age;
this.showAge= function(){
console.log(this.age);
}
}
say(){//這是一個類的方法驻右,注意千萬不要加上function
return "我的名字叫" + this.name+"今年"+this.age+"歲了";
}
}
var obj=new Person("laotie",18);
console.log(obj.say());//我的名字叫l(wèi)aotie今年18歲了
console.log(obj.value);//600
obj.showAge();//18
console.log(Person.constructor);//Function
console.log(Person.prototype.constructor);//Person
Person.prototype.say = function(){
return "我被復(fù)寫了"
};
console.log(obj.say());//我被復(fù)寫了
類的本質(zhì)是將非方法屬性添加到實例什黑,將方法添加到原型。
以下類A和構(gòu)造函數(shù)B等價:
class A {
age = 18
fn() {
console.log(this.age)
}
}
let a = new A()
console.log(a) //{age:18}
console.log(a.__proto__, a.__proto__ === A.prototype) //{constructor: A, fn: ?} , true
function B() {
this.age = 18
}
B.prototype.fn = function() {
console.log(this.age)
}
let b = new B()
console.log(b) //{age:18}
console.log(b.__proto__, b.__proto__ === B.prototype) //{constructor: B, fn: ?} , true
3.1 類的繼承
通過extends
關(guān)鍵字繼承堪夭。
子類沒有自己的this對象愕把,必須在constructor方法中調(diào)用super
方法繼承父類的this對象。
- 子類實例化時,先執(zhí)行子類構(gòu)造函數(shù),再執(zhí)行父類構(gòu)造函數(shù)
- 子類中默認(rèn)(隱式)存在如下構(gòu)造函數(shù),通過
super
將子類的參數(shù)傳給父類
constructor(){
super(...arguments)
}
- 在子類中可以通過
super
訪問父類的原型對象(__proto__
) - 通過
super
調(diào)用父類的方法時森爽,會綁定子類的this
(即super.fn.call(this)
)
class Animal{
constructor(age){
this.age = age;
}
shout(){
return "wowow"
}
say(){
console.log(1,this.shout());
}
jump(){
console.log('father jump');
}
}
class Person extends Animal{
//constructor(){
// super(...arguments)
//}
shout(){
return "meomeo"
}
say(){
console.log(2,this.shout());
this.jump();
super.jump();
}
jump(){
console.log('child jump');
}
}
var peter = new Person(5);
console.log(peter);//{age: 5}
peter.say();//2 meomeo child jump father jump
console.log(peter.constructor);//Person
console.log(typeof peter);//object
console.log(peter instanceof Person);//true
console.log(peter instanceof Animal);//true
- 類繼承的原型鏈
繼承包含三種內(nèi)容:實例成員恨豁、靜態(tài)成員、函數(shù)
class A{
static bar = "bar"
foo = "foo"
show_foo(){
console.log(this.foo)
}
}
class B extends A{
}
let b = new B()
console.log(b) //{foo: 'foo'}
console.log(B.bar) //bar
b.show_foo() //foo
等價于
function A() {
this.foo = "foo"
}
A.bar = "bar"
A.prototype.show_foo = function() {
console.log(this.foo)
}
function B() {
A.call(this) // 繼承實例成員
}
B.prototype.__proto__ = A.prototype // 繼承函數(shù)
B.__proto__ = A // 繼承靜態(tài)成員
let b = new B()
console.log(b) //{foo: 'foo'}
console.log(B.bar) //bar
b.show_foo() //foo
顯然以下內(nèi)容恒成立:
B.prototype.__proto__ === A.prototype
a.constructor === A.prototype.constructor === A
a.__proto__ === A.prototype
A.__proto__ // Native Code
B.__proto__ === A
//綜上
B.prototype.__proto__ === B.__proto__.prototype
3.2 靜態(tài)屬性和靜態(tài)方法
靜態(tài)屬性/方法指的是 Class 本身的屬性/方法爬迟, 而不是定義在實例對象上的屬性/方法橘蜜。不需要實例化類,即可直接通過該類來調(diào)用付呕。
ES6中计福,static
只能修飾方法,不能修飾屬性徽职。class內(nèi)直接定義的屬性都是原型屬性象颖。
ES7中,static
可以修飾屬性姆钉,class內(nèi)直接定義的屬性都是實例屬性
- 靜態(tài)關(guān)鍵字
static
说订,或直接在class外自行添加。 - 靜態(tài)屬性/方法不會被實例繼承潮瓶,僅會被子類繼承陶冷,且依然為靜態(tài)。
- 子類的靜態(tài)方法中筋讨,可以通過
super
調(diào)用父類的靜態(tài)屬性/方法埃叭。
class Box {
static a() {
return 100;
}
static value = 500
}
Box.b = 1;
class Desk extends Box {
static a(){
return super.a() + super.b;
}
}
console.log(Desk.a()); //101
3.3 類的this指向
- 類中默認(rèn)為嚴(yán)格模式,即使匿名函數(shù)悉罕,其
this
也不會指向window
- 靜態(tài)成員中的
this
指向類赤屋,實例成員中的this
指向?qū)嵗贤ㄓ玫膖his規(guī)則(指向調(diào)用者)壁袄。同理类早,如將其賦值給一個變量,再調(diào)用該變量時嗜逻,this指向會改變:
class Logger {
printName(name = 'there') {
this.print(`Hello ${name}`);
}
}
const logger = new Logger();
const { printName } = logger;
printName(); // TypeError: Cannot read property 'print' of undefined
因此可以采用如下方式保證指向
- 在構(gòu)造方法中綁定
this
class Logger {
constructor() {
this.printName = this.printName.bind(this);
}
// ...
}
- 構(gòu)造函數(shù)內(nèi)使用箭頭函數(shù)定義方法
class Obj {
constructor(){
this.printName=(name = 'there')=>{
this.print(`Hello ${name}`);
}
}
}
- 構(gòu)造函數(shù)外使用箭頭函數(shù)定義方法(
class fields
中的實驗性語法涩僻,可能有兼容問題)
class Logger {
printName = (name = 'there') => {
this.print(`Hello ${name}`);
}
}