在es6之前,是沒有extends繼承的,我們都是通過構造函數,原型對象來模擬,實現了繼承.
爲什麼這麼說呢?
這是因爲我們是利用構造函數來繼承屬性,利用原型對象來繼承方法的
.現在我們就可以通過call( ),apply( ),bind( )這三個方法也可以實現繼承.
call( );
用法
fn.call(thisArg, arg1, arg2, ...)
thisArg : 當前調用函數this的指向對象;
arg : 傳遞的參數
call( )方法的作用
- 可以調用函數
function fn() {
console.log("123");
}
fn.call(); //123
這裏我利用call方法也可以正常的調用函數
- 可以修改函數裏面的this指向
首先我先看一下函數fn裏面的this指向的是window
function fn() {
console.log("123");
console.log(this); //window{....}
}
var obj = {
name: '小明'
}
fn.call(); //123
fn.call(obj) //{name: "小明"}
這時 f n 這個函數裏面的this就指向了obj這個對象
call()裏面的第一個參數是指向的對象
如果我要傳遞參數的時候就可以這樣做
function fn(a, b, c) {
console.log(a + b + c);
}
var obj = {
name: '小明'
}
fn.call(obj, 1, 2, 3) //6
直接跟普通函數一樣傳參就好了.
call( )裏面的第一個參數是不參與傳遞的,只是改變this的指向
利用構造函數來繼承屬性
這兩個構造函數裏面的this指向
Cat構造函數裏面的this指向的是它的對象實例
Dog構造函數裏面的this指向的是它的對象實例
function Cat(name, age) {
this.name = name;
this.age = age;
}
function Dog(namem, age) {
}
如果我的Dog構造函數需要使用到Cat構造函數裏面的屬性,那我就可以把Cat裏面的屬性繼承過來
這時候我可以這樣
function Cat(name, age) {
this.name = name;
this.age = age;
}
function Dog(namem, age) {
//這裏的this指向的是Dog構造函數的實例
Cat.call(this,name,age)
}
var dog = new Dog('小白', 2)
console.log(dog); //Dog{name:"小白",age:2}
在Dog構造函數裏面通過call( )方法調用了Cat構造函數,然後把Cat構造函數的this改成了Dog構造函數的this,這樣Dog構造函數就可以使用Cat構造函數裏面的屬性了. 然後把需要的參數傳進去.
驗證一下可以看到成功了, 這樣就借用了構造函數來實現繼承屬性
核心原理:在子構造函數裏面調用父構造函數,同時把父構造函數的this指向子構造函數的this,
利用原型對象來繼承方法
function Cat(name, age) {
this.name = name;
this.age = age;
}
Cat.prototype.eat = function() {
console.log('我會吃飯');
}
function Dog(name, age) {
Cat.call(this, name, age)
}
var dog = new Dog('小白', 2)
console.log(dog);
現在我在Cat原型對象上添加了一個eat的方法,這時我Dog構造函數需要繼承我這個方法,那我應該怎麼做呢???
那我能不能這樣呢,把我Dog構造函數的原型對象指向Cat的原型對象呢? 那我試試
function Cat(name, age) {
this.name = name;
this.age = age;
}
Cat.prototype.eat = function() {
console.log('我會吃飯');
}
function Dog(name, age) {
Cat.call(this, name, age);
}
//這一步
Dog.prototype = Cat.prototype;
var dog = new Dog('小白', 2)
console.log(dog);
寫完後我驗證一下,可以看到是可以拿到這個方法的
好了,那就實現了方法的繼承,是不是很美好.
問題來了
那如果Dog裏面有自己獨享的方法呢?我再來看看
我在Dog原型對象上面寫了一個Dog獨享的方法
Dog.prototype.eatBone = function() {
console.log('我會啃骨頭');
}
毫無疑問,Dog的實例對象可以訪問到這個方法,那我Cat構造函數能不能訪問到這個方法呢.我打印一下,看看有木有問題哇
那我去Cat構造函數的原型對象上看看哈…
console.log(Cat.prototype);
咦…看到了吧,本來我Dog構造函數裏面的eatBone是自己獨享的
可是我打印出來的時候,這個方法也被Cat構造函數共享了.
這就是大問題了,也就是說,我不想給你的你卻也能得到,是不是很傷心呢?
接下來就解決這個問題;
原因就是我那一步操作
Dog.prototype = Cat.prototype;
也就相當於我把Dog構造函數的原型對象指向了Cat構造函數的原型對象雖然可以把Cat裏面原型對象上的方法繼承過來,但是反過來說Dog裏面原型對象上的一些自己的方法也被傳過去了;
所以我們千萬不能這樣做,因爲如果修改了子原型對象的話,父原型對象也會跟着變
那我們該怎麼解決這個問題???
我們可以這樣
Dog.prototype = new Cat();
然後我再驗證
console.log(Cat.prototype)
看成功了,給Dog原型對象上添加方法,對Cat原型對象沒有任何影響
我們來看看原理這一步操作的原理
Dog.prototype = new Cat();
我 new Cat( ),就相當於創建了一個實例對象(實例化一個對象).
然後把實例化的對象賦值給了Dog.prototype,就相當於把Dog的原型對象指向了剛剛創建的實例對象
那我在問你,我Cat的實例對象能訪問到我Cat的原型對象嗎?
那肯定是可以的對不對;說到這相信大家都明白怎麼回事了吧
我畫個圖給你們瞧瞧,
這樣大家都明白了吧, 再捋一捋
我 new Cat( ),就相當於創建了一個實例對象(實例化一個對象).
然後把實例化的對象賦值給了Dog.prototype,就相當於把Dog的原型對象指向了剛剛創建的實例對象
又因爲實例對象裏面有__proto__原型,通過這個原型我們就可以訪問原型對象.而這個原型對象上有eat( )這個需要被繼承的方法.就這樣,我Dog的原型對象就可以間接的訪問到Cat的原型對象,就可以拿到那裏面的方法.
我Dog裏面私有的方法還是自己的,就算改變了自己的原型對象,對Cat的原型對象也沒有任何影響;
這樣做法還是有一個問題的,不知道大家有沒有發現
Dog.prototype = new Cat();
我這樣是不是一個賦值操作呀,這樣寫過後,我Dog.prototype裏面的constructor屬性呢???(上一篇文章說道)
是吧,所以我們到最後還是要把它加上
//重新指回Dog構造函數
Dog.prototype.constructor = Dog;
最後完美成功實現繼承了
function Cat(name, age) {
this.name = name;
this.age = age;
}
Cat.prototype.eat = function() {
console.log('我會吃飯');
}
function Dog(name, age) {
Cat.call(this, name, age)
}
Dog.prototype = new Cat();
//重新指回Dog
Dog.prototype.constructor = Dog;
// Dog.prototype = Cat.prototype; //不能這樣做
Dog.prototype.eatBone = function() {
console.log('我會啃骨頭');
}
var dog = new Dog('小白', 2)
console.log(dog);
console.log(Cat.prototype);