js对象的继承

一、函数与对象,原型

首先要理解函数与对象的关系
函数是对象的一种是没有错的,数组也是对象的一种
对象都是通过函数创建的。

//函数都有prototype属性指向原型对象,prototype都有一个constructor属性指向函数
func.prototype.constructor === func;//函数原型的构造函数是函数本身
//实例不指向构造函数,而是指向构造函数的原型对象
var fObj = new f();
fObj.__proto__.constructor === f;

判断是对象实例的属性还是原型的属性

function hasPrototypeProperty(obj, name){
   return name in obj && !obj.hasOwnProperty(name);
}
//Animal构造函数在下面
Animal.prototype.c=3;
var p = new Animal('小明')

console.log(p.hasOwnProperty("name")); //true
console.log(hasPrototypeProperty(p, 'c')); //true

JS实现继承的几种方式

JS作为面向对象的弱类型语言,继承也是其非常强大的特性之一。那么如何在JS中实现继承呢?让我们拭目以待~

既然要实现继承,那么首先我们得有一个父类,代码如下:

// 定义一个动物类
function Animal (name) {
  // 属性
  this.name = name || 'Animal';
  this.eat= ['肉']; // 引用类型属性
  // 实例方法
  this.sleep = function(){
    console.log(this.name + '正在睡觉!');
  }
}
// 原型方法和属性
Animal.prototype.eating = function(food) {
  console.log(this.name + '正在吃:' + food);
};
Animal.prototype.playing = '爱玩篮球'
一、对象冒充(很少用)

也叫借用构造函数(伪造对象)

原理:通过一个临时的属性,将父对象的属性和方法继承给子对象。

注意:只继承对象本身的属性和方法,不继承它的原型的属性和方法。

  1. 普通的对象冒充
function Person(username, password) {    
	//实现将Animal的属性和方法追加到Person中,从而实现继承
   //第一步:this.method是作为一个临时的属性,并且指向Animal所指向的对象,
   //第二步:执行this.method方法,即执行Animal所指向的对象函数
   //第三步:销毁this.method属性,即此时Person就已经拥有了Animal的所有属性和方法
	this.method = Animal;   
	this.method(username);
	delete this.method;
	/*新增属性和方法*/
	this.password = password;   
	this.world = function() {     
		console.log(this.password);   
	} 
};
var p1 = new Animal('二货');
var p2 = new Person('二货','yyyyyy');
console.log(p2)
console.log(p1.eating('香蕉')); //二货正在吃:香蕉
console.log(p2.eating('香蕉')); //报错:p2.eating is not a function

优点:父类有引用类型对象的属性时,继承后实例间也不会相互影响

缺点

  • 只能继承构造函数内部的属性和方法,无法继承父类原型的属性和方法
  1. call/apply方式

在子类型构造函数中执行父类构造函数,并将父类构造函数的this指向子类的new出来的对象上

在特定的作用域中调用函数,等于设置函数体内this对象的值,以扩充函数赖以运行的作用域。

  • apply()方法 接收两个参数,一个是函数运行的作用域(this),另一个是参数数组。
    语法:apply([thisObj [,argArray] ]);,调用一个对象的一个方法,2另一个对象替换当前对象。
  • call()方法 第一个参数和apply()方法的一样,但是传递给函数的参数必须列举出来。
    语法:call([thisObject[,arg1 [,arg2 [,…,argn]]]]);,应用某一对象的一个方法,用另一个对象替换当前对象。
  • 如果call和apply的第一个参数写的是null,那么this指向的是window对象

call方法

  • call方法是Function类中的方法
  • call方法的第一个参数的值赋值给类(即方法)中出现的this
  • call方法的第二个参数开始依次赋值给类(即方法)所接受的参数
function Person(username, password) {
	Animal.call(this, username);
	/*新增属性和方法*/
	this.password = password;
	this.world = function() {
		console.log(this.password);
	}
};
var p2 = new Person('二货','yyyyyy');
console.log(p2);

apply方法

function Person(username, password) {
	Animal.apply(this, new Arrar(username));
	//或者:Animal.apply(this, [username]);
	/*新增属性和方法*/
	this.password = password;
	this.world = function() {
		console.log(this.password);
	}
};
var p2 = new Person('二货','yyyyyy');
console.log(p2);

关于call/apply,可以看看这篇文章:https://www.jianshu.com/p/00dc4ad9b83f

二、原型链方式(很少用)

原理:让子类构造函数的原型指向父类型构造函数的一个实例

让新的构造函数的原型 继承父类构造函数创建的实例的属性和方法,供新的构造函数创建的实例使用。
这样的话,新的构造函数创建的实例对象,既继承了原构造函数创建的实例的属性和方法,也继承了其原型的属性和方法!

写一个例子!

//instanceof 在继承中判断实例是否属于它的父类
//这里,Student和Person都在s的原型链中

function Person(){};
function Student(){};
var p =new Person();
Student.prototype=p;//继承原型
var s=new Student();
console.log(s instanceof Student);//true
console.log(s instanceof Person);//true

缺点:

  • 子类原型的构造函数成了父类构造函数
  • 当父类有引用类型属性的时候,子类原型继承后,所有的实例共用同一个属性,一个实例修改了属性,所有实例的属性均受影响。(父构造函数中的属性都会成为共享属性,当父构造函数中有引用类型属性时,继承后实例间会相互影响)
function Cat() {}
Cat.prototype = new Animal();
var cat1 = new Cat();
var cat2 = new Cat();
cat1.eat.push('蔬菜');
console.log(cat1.eat); // ['肉','蔬菜']
console.log(cat2.eat);// ['肉','蔬菜'] 受到实例cat1的影响
三、经典继承(规范化的原生式继承)(少用!)

Object.create()方法接受两个参数:Object.create(obj,propertiesObject) ;

  • obj:一个对象,应该是新创建的对象的原型。值只能是对象或者null,否则会报错!
  • propertiesObject:可选。该参数对象是一组属性与值,该对象的属性名称将是新创建的对象的属性名称,值是属性描述符。

Object.create(null) 创建的对象是一个空对象,在该对象上没有继承 Object.prototype 原型链上的属性或者方法,例如:toString(), hasOwnProperty()等方法;与{},new Object()不同,创建空对象的三种方式如下(它们三者的原型__proto__是相等的):

  • Object.create(Object.prototype)
  • { }
  • new Object()

例子

使用Object.create()是将对象继承到prototype原型上,本质是置换原型

var a = { person : { name: 'kelen' } };
var b = Object.create(a);
console.log(b);
console.log(a.hasOwnProperty('person'))
console.log(b.hasOwnProperty('person'))
b.person.name = 'kobe';
console.log( a.person.name ); // kobe

Object.create的兼容写法:

if(!Object.create){
	Object.create = function(parent){
		function F(){};
		F.prototype = parent;
		return new F;
	}
}

优点:不需要兴师动众使用构造函数
缺点:对象中有引用类型值属性时,实例间会相互影响

四、混合方式 – 推荐使用!!!

混合了对象冒充、原型继承方式

这种方式解决了原型链继承和对象冒充继承两种方式带来的问题,融合了两种方式的优点,目前最常用的方式!
存在一个小问题,此时子类的constructor不再是自身构造函数了

function Cat(name) {
	Animal.call(this, name); //新的构造函数创建的实例中可以传任意参数
}
Cat.prototype = new Animal();

var cat1 = new Cat('cat1');
var cat2 = new Cat('cat2');
cat1.eating('苹果');//cat1正在吃:苹果 -- 继承了父类原型的属性和方法
console.log(cat1.playing);//爱玩篮球 -- 继承了父类原型的属性和方法
cat1.eat.push('蔬菜');
console.log(cat1.eat);//["肉", "蔬菜"]
console.log(cat2.eat);//["肉"] -- 实例之间的属性不会相互影响
五、寄生组合式继承(完美!)

在组合继承的前提下,子类原型指向父类原型的一个副本而不是父类的实例
这种继承方式解决了组合方式的子类原型构造函数指向问题

// 定义猫咪构造函数
    function Cat(name) {
        this.name = name;
        // 继承Animal 将Animal绑定到this对象上并执行!!!
        Animal.call(this,'red');
    }
    function inheritPrototype(son, father) {
        var prototype = Object(father.prototype); // 将父类的原型创建一个副本
        prototype.constructor = son; //给副本指定constructor为子类构造函数
        son.prototype = prototype; //将子类原型指向这个副本
    }
    inheritPrototype(Cat,Animal);
    // 实例化Cat
    var c1 = new Cat('咪咪');
    var c2 = new Cat('泡泡');
    c1.eat.push('猫薄荷');
    c1.eating(); //我能吃肉,猫薄荷
    console.log(c1.constructor); // cat函数体,
    console.log(c1.eat); // 肉,猫薄荷
    console.log(c2.eat); // 肉  不受实例c1的影响
六、混入式继承

实质是遍历对象属性,将原对象上的所有成员复制一份,添加到当前对象上

o1.extend = function(){
	//arguments是实参
	var i=0,
	k,
	len = arguments.length,
	args = arguments;
	for(;i<args.length;i++){
		for(k in args[i]){
			this[k]=args[i][k];
		}
	}
}
o1.extend(o2,o3......);
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值