JavaScript对象继承 - ES5构造函数

Prototype:js继承机制的设计思想就是,原型对象的所有属性和方法,都能被实例对象共享,不仅节省了内存,还体现了实例对象之间的联系。

JS中规定,每一个函数都有一个prototype属性,指向一个对象。

我们来看下下边代码:

function test(){}
console.log(test.prototype) //{constructor: ƒ}

打印结果:

上边的代码,函数test默认具有prototype属性,指向一个对象。

对于普通函数来说,该属性基本无用,但是对于构造函数来说,它生成实例的时候,该属性会自动成为实例对象的原型。

function Test(){
    this.name = "lxc";
}
Test.prototype.age = 20;
var test1 = new Test();
var test2 = new Test();

console.log(test1.age); //20
console.log(test2.age); // 20

在Test构造函数的原型上添加属性age,两个实例对象都共享了该属性,当实例对象本身没有某个属性或方法的时候,它会到原型上边找此属性和方法。

总结下:

原型对象的作用,就是定义所有实例对象共享的属性和方法。这也是它被称为原型对象的原因,而实例对象可以视作从原型对象衍生出来的子对象。

原型链:

js规定,所有对象都有自己的原型对象。一方面,任何一个对象,都可以充当其他对象的原型;另一方面,由于原型对象也是对象,所以它也有自己的原型。因此,就会形成一个“原型链”,对象到原型,在到原型的原型··· ···

如果层层上溯,所有对象的原型最终都可以上溯到Object.prototype,既Object构造函数的prototype属性。也就是说所有的对象都继承了Object.prototype的属性。这就是所有对象都有valueOf和toString方法的原因,因为都是从Object.prototype继承的。

注意:原型链的尽头,也就是Object.prototype的原型是null。null没有任何属性和方法,没有自己的原型。

Object.getPrototype(Object.prototype)//null

Object.getPrototypeOf()返回参数对象的原型。

读取对象的某个属性时,js引擎先寻找对象本身的属性,如果找不到,就到它的原型上边找,如果还找不到,就到原型的原型上边找,如果直到最顶层Object.prototype还是找不到,则返回undefined。如果对象自身和它的原型,都定义了一个同名属性,那么优先读取对象自身的属性。

注意,一级级向上,在整个原型链上找某个属性,对性能是有影响的,‘所寻找的属性在越上层的原型对象,对性能的影响越大。如果寻找某个不存在的属性,将会便利整个原型链。

eg:让构造函数的原型指向数组,那么实例对象可以调用数组的方法。

function Test(){
}
Test.prototype = new Array();
Test.prototype.constructor = Test;
var test = new Test();
test.push(1,2,3,4)
console.log(test)//[1,2,3,4]
test instanceof Array // true

上边代码,test是构造函数Test的实例对象,构造函数的原型指向了一个数组的实例,使得test可以调用数组的方法,最后一行instanceof表达式,用来比较一个对象是否为某个构造函数的实例。

constructor属性:

prototype对象有一个constructor属性,默认指向prototype对象所在的构造函数。

function Test(){
}
var test = new Test();
console.log(Test.prototype.constructor === Test)//true 

由于constructor属性定义在prototype对象上边,意味着可以被所有实例对象继承。

function Test(){
}
var test = new Test();
console.log(Test.prototype.constructor === Test)//true 
console.log(test.constructor === Test.prototype.constructor)//true 
console.log(test.hasOwnProperty("constructor"))//false 

constructor属性可以判断到底是哪一个构造函数产生的。

function Test(){
}
Test.prototype.age = 20;
var test = new Test();
var newTest = new test.constructor();
console.log(newTest.age)//20

上边代码,test是Test构造函数的实例对象,因为test.constructor指向Test构造函数,所以我们利用这一点可创建一个新的实例对象。

下边我们来封装一个调用实例方法来创建另一个实例方法:

function Test(){
}
Test.prototype.myName = "lxc";
Test.prototype.createCopy = function(){
    return new this.constructor()
}
var test = new Test();
var newTest = test.createCopy();
console.log(newTest.myName)  // "lxc"

上边代码,我们在Test构造函数的原型上封装了一个方法,里边this指向调用createCopy方法的实例,实现了调用实例对象的属性来创建另外一个实例对象。

如果将修改了原型对象,一般会同时把原型上边的constructor属性也修改了,防止引用时出错。

function Test(){
}
Test.prototype = {
    name:"lxc"
};
var test = new Test();
console.log(Test.prototype.constructor === Test) // false
console.log(Test.prototype.constructor === Object) // true

上边代码,我们把Test构造函数的原型改成一个对象,在调用constructor时,他不会指向Test了,而是指向一个对象,因为普通对象的构造函数是Object。

 

所以,当我们现在该原型上边加方法的时候,不要去覆盖原型,而是用点的形式添加:

function Test(){}
Test.prototype.say = function(){
    alert("我的名字叫lxc")
}
var test = new Test();
test.say()//我的名字叫lxc

instanceof运算符:

instanceof运算符返回一个布尔值,表示对象是否为某个构造函数的实例。

var date = new Date();
console.log(date instanceof Date) //true

利用instanceof运算符,还可以巧妙地解决,调用构造函数时,忘了加new命令的问题:

function Test(){
    if(this instanceof Test){
        this.name = "lxc";
        this.age = 20;
    }else{
        return new Test();
    }
}
var test = Test();
console.log(test.name) //lxc

上边的代码使用instanceof运算符,在函数体内判断this关键字是否为构造函数Test的实例,如果不是,就表明忘了加new命令了。

构造函数的继承:

让构造函数继承另一个构造函数,是非常常见的需求。这可以分成三步实现。

第一步:是在子类的构造函数中,调用父类的构造函数。

第二部:让子类的原型指向父类的原型。

第三部:子类构造函数原型的constructor属性指向子类构造函数。(如果不手动修改,子类构造函数原型的constructor会指向父类Father,也就是说子类的实例对象的构造函数会发生改变)

function Father(){
    this.name = "lxc";
    this.age = 20;
}
Father.prototype.say = "hehe";
var father = new Father();


function Son(){
    this.a = "1";
    Father.call(this);
}
Son.prototype = Object.create(Father.prototype)
Son.prototype.constructor = Son;
Son.prototype.method = fucntion(){}
var son = new Son();
console.log(son.say)//"hehe"

上边代码,在子类构造函数中,通过call来借用父类的构造函数中的属性,然后让子类的原型赋值为Object.create(Father.prototype),而不是直接等于Father.prototype,否则后边两行的操作对父类构造函数有影响,会把Father.prototype一起改掉。。。

多重继承:

js中不提供多重继承功能,既不允许一个对象同时继承多个对象。但是可以通过变通方法,实现这个功能:

function Father1(){
    this.name = "lxc";
    this.age = 20;
}
Father1.prototype.say = function(){
    console.log("呵呵")
}
// var father1 = new Father1();
function Father2(){
    this.height = "170cm";
}
Father2.prototype.weight = function(){
    console.log("60kg")
}
// var father2 = new Father2();
function Son(){
    Father1.call(this);
    Father2.call(this);
}
Son.prototype = Object.create(Father1.prototype);
Object.assign(Son.prototype,Father2.prototype);
Son.prototype.constructor = Son;
var son = new Son();
son.say() // "呵呵"
son.weight() // "60kg"

上边代码,子类Son同时继承了父类Fahter1和Father2,跟构造函数的继承差不多,唯一的区别在于用到了Object.assign()方法,浅层拷贝对象;此方法在前几篇文章中介绍过。。。

补充:

在ES6中新增了一个方法,new.target ,判断是否使用new来创建一个构造函数,成立则返回该构造函数本身,否则返回undefined。

function Person(name,age){
    if(new.target === 'undefined'){
        throw new Error('must return new')
    }
    this.name = name;
    this.age = age;
}
var person = new Person('lxc',20)

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值