js中面向对象编程(part3)

论坛 期权论坛 期权     
暴躁的耳朵   2019-7-20 10:22   2206   0
继承
面向对象的继承方式有很多种,原型链继承、借用构造函数继承、组合继承、原型式继承、寄生式继承、寄生式组合继承、深拷贝继承
原型链继承
利用原型链的特性,当在自身找不到时,会沿着原型链往上找。
  1. function Person(){
复制代码
  1.   this.name = name,
复制代码
  1.   this.pets = ["小黄","小黑"]
复制代码
  1. }
复制代码
  1. Person.prototype.eat = function(){
复制代码
  1.   console.log("吃饭");
复制代码
  1. }
复制代码
  1. function Student(){
复制代码
  1.   this.num = "001230"
复制代码
  1. }
复制代码
  1. [/code][code]let student = new Student();
复制代码
  1. console.log(student.num) // 001230
复制代码
  1. console.log(student.name) // undefind
复制代码
  1. console.log(student.pets) // undefind
复制代码
  1. student.eat() //报错
复制代码
从上面我们可以看到,Student并没有继承Person,此时他们之间的联系是这样的。



既然要让实例student访问到Person的原型对象属性方法,

我们可以想到,Student.prototype = Person.prototype

  1. function Person(){
复制代码
  1.   this.name = "xiaoming",
复制代码
  1.   this.pets = ["小黄","小黑"]
复制代码
  1. }
复制代码
  1. [/code][code]Person.prototype.eat = function(){
复制代码
  1.   console.log("吃饭");
复制代码
  1. }
复制代码
  1. [/code][code]function Student(){
复制代码
  1.   this.num = "001230"
复制代码
  1. }
复制代码
  1. [/code][code]//改写Student.prototype的指针指向
复制代码
  1. Student.prototype = Person.prototype;
复制代码
  1. [/code][code]let student = new Student();
复制代码
  1. console.log(student.num) // '030578000'
复制代码
  1. console.log(student.name) // undefined
复制代码
  1. console.log(student.pets) // undefined
复制代码
  1. student.eat() // * '吃饭
复制代码
此时的关系图为




现在修改了Student.prototype指针方向为Person.prototype后,可以访问Person.prototype上的eat方法,但是student还不能继承Person.name和Person.pets,会想到,Person的实例,才会同时拥有实例属性方法和原型属性方法。

  1. function Person(){
复制代码
  1.   this.name = "xiaoming",
复制代码
  1.   this.pets = ["小黄","小黑"]
复制代码
  1. }
复制代码
  1. [/code][code]Person.prototype.eat = function(){
复制代码
  1.   console.log("吃饭");
复制代码
  1. }
复制代码
  1. [/code][code]function Student(){
复制代码
  1.   this.num = "001230"
复制代码
  1. }
复制代码
  1. [/code][code]// new一个Person的实例,同时拥有其实例方法和原型属性方法
复制代码
  1. let p = new Person();
复制代码
  1. [/code][code]// 把Student的原型对象指向实例p
复制代码
  1. Student.prototype = p;
复制代码
  1. [/code][code]// 把Student的原型对象的constructor指向Student,解决类型判断问题
复制代码
  1. Student.prototype.constructor = Student
复制代码
  1. [/code][code]let student = new Student();
复制代码
  1. console.log(student.num) // '030578000'
复制代码
  1. console.log(student.name) // * '邵威儒'
复制代码
  1. console.log(student.pets) // * '[ '旺财', '小黄' ]'
复制代码
  1. student.eat() // '吃饭'
复制代码
因为实例p是由Person构造函数实例化出来的,所以同时拥有其实例属性方法和原型对象方法,并且把这个实例p作为Student的原型对象,此时的关系如下




这种成为原型链继承,到此为止原型链继承就结束了

借助构造函数继承
通过这样的方式,会有一个问题,原型对象类似一个共享库,所有实例共享原型对象同一个属性方法,如果原型对象上有引用类型,那么会被所有实例共享,也就是某个实例更改了,则会影响其他实例,我们可以看一下
  1. function Person(){
复制代码
  1.     this.name = '邵威儒'
复制代码
  1.     this.pets = ['旺财','小黄']
复制代码
  1. }
复制代码
  1. [/code][code]Person.prototype.eat = function(){
复制代码
  1.     console.log('吃饭')
复制代码
  1. }
复制代码
  1. [/code][code]function Student(){
复制代码
  1.     this.num = "030578000"
复制代码
  1. }
复制代码
  1. let p = new Person()
复制代码
  1. Student.prototype = p //让Student的原型同时拥有Person的实例属性方法,和原型属性方法
复制代码
  1. Student.prototype.constructor = Student
复制代码
  1. [/code][code]let student = new Student()
复制代码
  1. let student2 = new Student() // * new多一个实例
复制代码
  1. console.log(student.num) // '030578000'
复制代码
  1. console.log(student.name) // '邵威儒'
复制代码
  1. console.log(student.pets) // '[ '旺财', '小黄' ]'
复制代码
  1. student.eat() // '吃饭'
复制代码
  1. [/code][code]// 此时我们修改某一个实例,pets是原型对象上的引用类型 数组
复制代码
  1. student.pets.push('小红')
复制代码
  1. [/code][code]console.log(student.pets) // * [ '旺财', '小黄', '小红' ]
复制代码
  1. console.log(student2.pets) // * [ '旺财', '小黄', '小红' ]
复制代码
从上面可以看出来,student的pets(实际上就是原型上的pets)被修改后,相关实例student2也会受到影响

那么我们能不能把Person上的属性方法,添加到Student上呢?以防都存在原型对象上,会被所有实例共享,特别是引用类型的修改,会影响所有相关实例。

可以利用call实现

  1. function Person(){
复制代码
  1.     this.name = '邵威儒'
复制代码
  1.     this.pets = ['旺财','小黄']
复制代码
  1. }
复制代码
  1. [/code][code]Person.prototype.eat = function(){
复制代码
  1.     console.log('吃饭')
复制代码
  1. }
复制代码
  1. [/code][code]function Student(){
复制代码
  1.   Person.call(this);//利用call调用Person上的属性方法拷贝一份放到Student中
复制代码
  1.   this.name = "001230"
复制代码
  1. }
复制代码
  1. [/code][code]let p = new Person();
复制代码
  1. Student.prototype = p;
复制代码
  1. Student.prototyoe.constructor = Student;
复制代码
  1. [/code][code]let student = new Student();
复制代码
  1. let student2 = new Studetn();
复制代码
  1. console.log(student.num) // '030578000'
复制代码
  1. console.log(student.name) // '邵威儒'
复制代码
  1. console.log(student.pets) // '[ '旺财', '小黄' ]'
复制代码
  1. student.eat() // '吃饭'
复制代码
  1. [/code][code]//此时我们修改某一个实例,pets是原型对象上的引用类型 数组
复制代码
  1. student.pets.push["小红"];
复制代码
  1. [/code][code]console.log(student.pets);//["旺财","小黄","小红"]
复制代码
  1. console.log(student2.pets);//["旺财","小黄"]
复制代码
上面在子构造函数(Student)中利用call调用父构造函数(Person)的方式,叫做借助构造函数继承

结合上面所看,使用原型链继承和借助构造函数继承,两者结合起来叫做组合继承,关系如下图




还有个问题,当父构造函数需要接受参数时,怎么处理?

  1. function Person(name,pets){
复制代码
  1.   this.name = name,  
复制代码
  1.   this.pets = pets
复制代码
  1. }
复制代码
  1. [/code][code]Person.prototype.eat = function(){
复制代码
  1.   console.log("吃法");
复制代码
  1. }
复制代码
  1. [/code][code]function Student(num,name,pets){// 子构造函数中也要接受参数
复制代码
  1.   Person.call(this,name,pets);//  把name和pets传参数
复制代码
  1.   this.num = num;
复制代码
  1. }
复制代码
  1. [/code][code]let p = new Person();
复制代码
  1. Student.prototype = p;
复制代码
  1. Student.prototype.constructor = Student;
复制代码
  1. [/code][code]let student = new Student("030578000","邵威儒",["旺财","小黄"])
复制代码
  1. let student2 = new Student("030578001","iamswr",["小红"])
复制代码
  1. console.log(student.num) // '030578000'
复制代码
  1. console.log(student.name) // '邵威儒'
复制代码
  1. console.log(student.pets) // '[ '旺财', '小黄' ]'
复制代码
  1. student.eat() // '吃饭'
复制代码
  1. [/code][code]student.pets.push('小红')
复制代码
  1. [/code][code]console.log(student.pets) // * [ '旺财', '小黄', '小红' ]
复制代码
  1. console.log(student2.pets) // * [ '小红' ]
复制代码


这样我们就可以在构造函数中给父构造函数传参了,而且我们也发现上图中,2两个红圈的地方,代码是重复的,那么怎么解决?

能否在子构造函数设置原型对象的时候,只要父构造函数的原型对象属性方法呢?
当然是可以的,接下来的寄生式组合继承,也是目前程序员认为解决继承问题最好的方案

寄生式组合继承
  1. function Person(name,pets){
复制代码
  1.   this.name = name,
复制代码
  1.   this.pets = pets
复制代码
  1. }
复制代码
  1. [/code][code]Person.prototype.eat = function(){
复制代码
  1.   console.log("吃饭");
复制代码
  1. }
复制代码
  1. [/code][code]function Student(num){
复制代码
  1.   Person.call(this,name,pets);
复制代码
  1.   this.num = num
复制代码
  1. }
复制代码
  1. [/code][code]//寄生式继承
复制代码
  1. function Temp(){}  //*声明一个空的构造函数,用于桥梁作用
复制代码
  1. Temp.prototype = Person.prototype;//*把Temp构造函数的原型对象指向Person的原型对象
复制代码
  1. let temp = new Temp();//* 用构造函数Temp创建一个temp实例
复制代码
  1. Student.prototype = temp;//* 把子构造函数的原型对象指向temp
复制代码
  1. temp.constructor = Student;// *把temp的constructor指向Student
复制代码
  1. [/code][code]let student1 = new Student("001230","xiaoming",["旺财","小黄"]);
复制代码
  1. console.log(student1);//Student{name:xiaoming,pets:["旺财","小黄"],num:001230}
复制代码
  1. let student2 = new Student("002340","xiaohong",["小黑"]);
复制代码
  1. console.log(student2);//Student{name:"xiaohong",pets["小黑"],num:"002340"}
复制代码
至此为止,我们就完成了寄生式组合继承了,主要逻辑就是用一个空的构造函数,来当做桥梁,并且把其原型对象指向父构造函数的原型对象,并且实例化一个temp,temp会沿着这个原型链,去找到父构造函数的原型对象



原型式继承
  1. //原型式继承
复制代码
  1. function createObjwithObj(obj){
复制代码
  1.   function Temp(){},
复制代码
  1.   Temp.prototype = obj,
复制代码
  1.   let o =new Temp();
复制代码
  1.   return 0;
复制代码
  1. }
复制代码
  1. [/code][code]// 把Person的原型对象当做temp的原型对象
复制代码
  1. let temp = creatObjwithObj(Person.prototype);
复制代码
  1. [/code][code]//也可以使用Object.create实现
复制代码
  1. //把Person的原型对象当做temp2的原型对象
复制代码
  1. let temp2 = Object.create(Person.prototype);
复制代码
寄生式继承

  1. // 寄生式继承
复制代码
  1. // 我们在原型式的基础上,希望给这个对象新增一些属性方法
复制代码
  1. // 那么我们在原型式的基础上扩展
复制代码
  1. function createNewObjWithObj(obj) {
复制代码
  1.     let o = createObjWithObj(obj)
复制代码
  1.     o.name = "邵威儒"
复制代码
  1.     o.age = 28
复制代码
  1.     return o
复制代码
  1. }
复制代码
深拷贝继承

  1. //方法一:利用JSON.stringify和JSON.parse
复制代码
  1. let swr = {
复制代码
  1.   name:"xiaoming",
复制代码
  1.   age:23
复制代码
  1. }
复制代码
  1. [/code][code]let sercopy = JSON.parse(JSON.stringify(swr));
复制代码
  1. console.log(swrcopy) //  {name:"xiaoming",age:23}
复制代码
  1. //此时我们修改swr属性
复制代码
  1. swr.age = 29
复制代码
  1. console.log(swr)  // {name:"xiaoming",age:29}
复制代码
  1. //但是swrcopy却不会收到影响
复制代码
  1. console.log(swrcopy);  //{name:"xiaoming",age23}
复制代码
  1. //这种方法进行深拷贝,只针对json数据这样的键值对有效
复制代码
  1. //对于函数等反而无效,不好用。
复制代码
  1. //方法二
复制代码
  1. function deepCopy(formObj,toObj){ //深度拷贝
复制代码
  1.   //容错
复制代码
  1.   if(fromObj === null) return null //当fromObj为null时
复制代码
  1.   if(fromObj instanceof RegExp) return new RegExp(fromObj) //当fromObj为正则
复制代码
  1.   if(fromObj instanceof Data) return new Date(fromObj)  //当fromObj为Date
复制代码
  1.   
复制代码
  1.   toObj = toObj || {}
复制代码
  1.   
复制代码
  1.   for(let key in fromObj){
复制代码
  1.     if(typeof fromObj[key] !== 'object'){ // 是否为对象
复制代码
  1.       toObj[key] = fromObj[key] // 如果为原始数据类型,则直接赋值
复制代码
  1.     }else{
复制代码
  1.       toObj[key] = new fromObj[key].constructor // 如果为object,则new这个object指向的构造函数
复制代码
  1.       deepCopy(fromObj[key],toObj[key]) // 递归
复制代码
  1.     }
复制代码
  1.   }
复制代码
  1.   return toObj
复制代码
  1. }
复制代码
  1. let dog = {
复制代码
  1.   name:"小白",
复制代码
  1.   sex:"公",
复制代码
  1.   firends:[
复制代码
  1.     {
复制代码
  1.       name:"小黄",
复制代码
  1.       sex:"母"
复制代码
  1.     }
复制代码
  1.   ]
复制代码
  1. }
复制代码
  1. [/code][code]let dogcopy = deepCopy(dog)
复制代码
  1. // 此时我们把dog的属性进行修改
复制代码
  1. dog.firends[0].sex = '公'
复制代码
  1. console.log(dog) // { name: '小白',
复制代码
  1.                       sex: '公',
复制代码
  1.                       firends: [ { name: '小黄', sex: '公' }] }
复制代码
  1. // 当我们打印dogcopy,会发现dogcopy不会受dog的影响
复制代码
  1. console.log(dogcopy) // { name: '小白',
复制代码
  1.                           sex: '公',
复制代码
  1.                           firends: [ { name: '小黄', sex: '母' } ] }
复制代码
  1. [/code][list][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][/list][code]// 方法三:
复制代码
  1. let dog = {
复制代码
  1.   name:"小白",
复制代码
  1.   sex:"公",
复制代码
  1.   firends:[
复制代码
  1.     {
复制代码
  1.       name:"小黄",
复制代码
  1.       sex:"母"
复制代码
  1.     }
复制代码
  1.   ]
复制代码
  1. }
复制代码
  1. [/code][code]function deepCopy(obj) {
复制代码
  1.   if(obj === null) return null
复制代码
  1.   if(typeof obj !== 'object') return obj
复制代码
  1.   if(obj instanceof RegExp) return new RegExp(obj)
复制代码
  1.   if(obj instanceof Date) return new Date(obj)
复制代码
  1.   let newObj = new obj.constructor
复制代码
  1.   for(let key in obj){
复制代码
  1.     newObj[key] = deepCopy(obj[key])
复制代码
  1.   }
复制代码
  1.   return newObj
复制代码
  1. }
复制代码
  1. [/code][code]let dogcopy = deepCopy(dog)
复制代码
  1. dog.firends[0].sex = '公'
复制代码
  1. console.log(dogcopy)
复制代码
分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:
帖子:
精华:
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP