第二十一节:JS中的继承

本文详细介绍了JavaScript中的原型链、对象的属性访问、面向对象与基于对象的区别,以及原型链继承、借用构造函数继承、组合式继承、原型式继承、寄生式继承和组合寄生式继承等继承方式。同时,还探讨了浅克隆与深克隆的概念及实现方法。
摘要由CSDN通过智能技术生成

上节回顾

1、所有 函数 都有一个特殊属性(prototype),prototype指向一个对象,称之为原型对象,原型对象上只有一个属性(constructor),constructor又指向了构造函数,形成了一个闭环。

2、所有 对象 都有一个隐藏的属性proto,大部分浏览器可以使用两个下划线进行访问;

//创建一个构造函数
function Fun(name,age){
	this.name=name;
	this.age=age;
}
Fun.prototype; //{constructor: ƒ}
var peo=new Fun('张三',23); //基于构造函数创建一个对象
peo; //Fun {name: '张三', age: 23}
peo.__proto__;  //{constructor: ƒ}

3、访问 对象的属性 时,查看对象上是否有此属性,有则可直接调用,没有则顺着proto找到原型对象,查看原型对象是否有此属性,如果有则调用原型对象的属性,如果没有则会顺着原型对象隐藏的属性proto向上查找,此查找proto的过程形成的链条称之为原型链,原型链的最后指向一个null。

4、添加和修改则直接对当前对象进行添加和修改。

面向对象 和 基于对象的区别

面向对象:必然有三大特点(封装,继承,多态)
基于对象:是使用对象, 就是无法利用现有的对象产生新的对象类型,继而产生新的对象,也就是说基于对象没有继承的特点!

因为JavaScript没有继承的概念,进而也没有多态的概念,缺少了继承和多态的特性,所以JavaScript就只是个基于对象的语言

JS中的继承

虽然js没有继承,但可以用一些手段模拟出来继承。继承简单来说就是让一个对象拥有另一个对象的属性和方法

1、原型链继承

//父类构造函数
function Afun(){
   this.fname='li';
}
//给父类原型上添加一个getFname的方法
Afun.prototype.getFname = function (){
	return this.fname;
}
//子类构造函数
function Bfun(){
	this.name = 'alvin';
}
//定义子类的prototype指向父类实例
Bfun.prototype = new Afun();
//给B添加一个获取name的方法
Bfun.prototype.getName= function(){
	return this.name;
}
var son = new Bfun();
son;//Bfun {name: 'alvin'}
son.getName();//'alvin'
son.getFname();//'li'
//给父类A的prototype增加属性和方法,son依然可以继承
Afun.prototype.teacher='吴磊';
son.teacher;//'吴磊'

原型链继承的缺点:
①书写先后问题,先给子类指向父类实例,再给子类设置新的方法,否则会被覆盖

function Afun(){
   this.fname='li';
}
Afun.prototype.getFname = function (){
	return this.fname;
}
function Bfun(){
	this.name = 'alvin';
}
Bfun.prototype.getName= function(){
	return this.name;
}
Bfun.prototype = new Afun();
var son=new Bfun();
son.getName();//报错,son.getName is not a function

②无法实现多继承(构造函数A和构造函数B,当创建构造函数C时,C的prototype要不指向A,要不指向B,不可能即指向A又指向B)
③所有属性和方法都是共享的,一旦修改则影响所有基于构造函数创建的对象;
④无法传参

2、借用构造函数方式

在子类构造函数中使用call()或者apply()方法,把父类构造函数在子类重新运行一遍。

function Product(name, price) {
  this.name = name;
  this.price = price;
}
function Father(address) {
  this.address= address;
}
function Food(name, price,address) {
  //重定向构造函数Product的this,Product的this
  //Product.call(this, name, price);
  Product.apply(this, [name, price]);
  Father.apply(this, [address]);
  this.category = 'food';
}
var son = new Food('蛋糕',139,'西三环99号');
son; //Food {name: '蛋糕', price: 139, address: '西三环99号', category: 'food'}

注意: call()方法的语法和作用与 apply() 方法类似,只有一个区别,就是 call() 方法接受的是一个参数列表,而 apply() 方法接受的是一个包含多个参数的数组。

优点:
①可以实现多继承
②解决了共享的问题,子类可以重定向多个父类
③可以传参
缺点:
①创造的实例(例如son)只是子类的实例,不是父类的实例
②只能继承构造函数内的属性和方法,不能继承原型的属性和方法

function Product(name, price) {
  this.name = name;
  this.price = price;
}
Product.prototype.getPrice=function(){
return this.price;
}
function Father(address) {
  this.address= address;
}
function Food(name, price,address) {
  Product.apply(this, [name, price]);
  Father.apply(this, [address]);
  this.category = 'food';
}
var son = new Food('蛋糕',139,'西三环99号');
son.getPrice(); //报错 son.getPrice is not a function

③所有属性都是在构造函数中运行的,无法进行复用

3、组合式继承

function a(name, price) {
  this.name = name;
  this.price = price;
}
a.prototype.getPrice=function(){
	return this.price;
}
function b(name, price) {
  a.apply(this, [name, price]);
  this.category = 'food';
}
b.prototype=new a();
b.prototype.constructor = b;
var food1=new b('蛋糕',139);
food1.getPrice(); //139

缺点:
①父类构造函数被调用了2次;第一次是在使用call()或者apply()方法时,第二次是设置子类prototype指向父类实例时。如果父类构造函数较大,则会比较影响性能。
②基于子类创建的实例对象,对象中的name和price在prototype中也有一个同名属性,存在同名覆盖问题。

4、原型式继承

类似原型链继承,原型链继承继承的是一个构造函数,原型式继承继承的是一个普通的对象
对象直接量创建的对象,怎么让一个对象继承另一个对象的属性(Object对象)

var a={name:'张三'};
var b={age:30};
b.__proto__=a;

注 意:__proto__是一些浏览器定义的特殊属性,不是标准化当中存在的语法,高版本浏览器不会报错,但一些ie浏览器会报错。

var a={name:'张三'};
function F(){};
F.prototype=a;//Object { name: "张三" }
var b=new F();
b.__proto__;//Object { name: "张三" }
b.age=13;
b.age;//13
b.name;//"张三"
ES5创建对象 新增方法Object.create()

Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__

var x={name:'张三'};
x.getName=function(){
	return '你的名字是:'+this.name;
}
var y=Object.create(x);
y.__proto__; //Object { name: "张三" }

注 意: 当创建的新对象需要继承另一个对象的属性和方法时,最简单的就是使用create方法

5、寄生式继承

将创建空的构造函数的过程封装成一个函数,使其看起来更像继承。

function creatFun(obj){
	function F(){};
	F.prototype=obj;
	return new F();
}
var a={name:'张三'};
var b=creatFun(a);
b.name;//'张三'

6、组合寄生式继承

为了解决组合式继承的缺点,所以将寄生式继承和组合式继承结合到一起

function a(name, price) {
  this.name = name;
  this.price = price;
}
a.prototype.getPrice=function(){
	return this.price;
}
function b(name, price) {
  a.apply(this, [name, price]);
  this.category = 'food';
}
//将b的原型对象指向a的原型对象,解决了属性重复的问题,也解决了父类执行两次的问题
//b.prototype.__proto__=a.prototype;//用此行代码替换掉 b.prototype=new a();,因为__proto__是浏览器的特殊定义,所以需要经过下面的改造
//function creatFun(obj){
//	function F(){};
//	F.prototype=obj;
//	return new F();
//}
//var p = creatFun(a.prototype);
//利用ES5 create方法简化写法
var p = Object.create(a.prototype);
p.constructor=b;
b.prototype = p;
var food1=new b('蛋糕',139);
food1.getPrice(); //139

00:40
其他继承介绍文章参考

扩展:深克隆与浅克隆

当对象都是原始数据类型时的拷贝

var a={name:'张三',age:50,sex:'男',id:10524};
var b={};
for(let i in a){
  b[i]=a[i];
}
b;//{name: '张三', age: 50, sex: '男', id: 10524}

这种拷贝被称之为浅克隆;
浅克隆:直接拷贝原始数据类型或者不是原始数据类型的引用;
深克隆:拷贝属性时,属性的值是否为原始数据类型,如果不是原始数据类型,则再进行一个循环进行拷贝,依次类推(也就是常见的递推);

关于赋值和浅拷贝

// 对象赋值
var obj1 = {
   'name' : 'zhangsan',
   'age' :  '18',
   'language' : [1,[2,3],[4,5]],
};
var obj2 = obj1;
obj2.name = "lisi";
obj2.language[1] = ["二","三"];
console.log('obj1',obj1)
console.log('obj2',obj2)

在这里插入图片描述

// 浅拷贝
var obj1 = {
   'name' : 'zhangsan',
   'age' :  '18',
   'language' : [1,[2,3],[4,5]],
};
var obj3 = shallowCopy(obj1);
obj3.name = "lisi";
obj3.language[1] = ["二","三"];
function shallowCopy(src) {
   var dst = {};
   for (var prop in src) {
       if (src.hasOwnProperty(prop)) {
           dst[prop] = src[prop];
       }
   }
   return dst;
}
console.log('obj1',obj1)
console.log('obj3',obj3)

在这里插入图片描述

浅拷贝的实现方式

var obj = {name:'lily', obj: {a: "kobe", b: 39} };
var initalObj = Object.assign({}, obj);
initalObj.obj.a = "wade";
initalObj.name='修改名字';
console.log(initalObj);
console.log(obj);
let arr = [1, 3, {
   username: 'kobe'
}];
let arr2=arr.concat();    
arr2[2].username = 'wade';
console.log(arr);
let arr = [1, 3, {
   username: ' kobe'
}];
let arr3 = arr.slice();
arr3[2].username = 'wade'
console.log(arr);

深拷贝的实现方式

let arr = [1, 3, {
   username: ' kobe'
}];
let arr4 = JSON.parse(JSON.stringify(arr));
arr4[2].username = 'duncan';
console.log(arr, arr4)

原理: 用JSON.stringify将对象转成JSON字符串,再用JSON.parse()把字符串解析成对象,一去一来,新的对象产生了,而且对象会开辟新的栈,实现深拷贝。

这种方法虽然可以实现数组或对象深拷贝,但不能处理函数

深拷贝的实现参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值