js中的继承与拷贝

91 阅读5分钟

认识继承

         function person(name){
            this.name = name
         }
         person.prototype.init = function(){
            console.log('我是 prototype 原型上的方法')
         }
         function Stu(age){
            this.age = age
         }
         Stu.prototype.sayHi = function (){
            console.log('你好')
         }
         const s1 = new Stu(18)
         console.log(s1)
         console.log(s1.__proto__)

如果 s1 对象内部 具有一个属性 name并且 s1 还可以使用 init 方法那么我们就可以说 Stu 这个构造函数 继承自 person 结构函数 Stu是 person 的子类 person 是 Stu 的父类

原型继承

利用自定义原型的方式实现继承关系

//构造函数1
         function person(name){
            this.name = name
         }
         person.prototype.init = function(){
            console.log('我是 prototype 原型上的方法')
         }

         //构造函数2 
         function Stu(age){
            this.age = age
         }
         Stu.prototype.sayHi = function (){
            console.log('你好')
         }

         console.log(Stu.prototype)

         Stu.prototype = new person('李四')

        //  const s1 = new Stu(18)
         console.log(Stu.prototype)
         
         console.log(person.prototype)

缺点:

1.原本原型上的函数方法无法使用, 因为原本的原型被替换了.

2.继承到的属性并不在自己的身上, 而是在 原型对象上 , 但不影响使用

借用构造函数继承

核心:把父类构造函数当作普通函数调用,利用call修改这个函数内部的this指向 (如果不修改, 函数的this指向就会指向到别的地方)

        //构造函数1
        function person(name ){
            this.name = name
         }
         person.prototype.init = function(){
            console.log('我是 prototype 原型上的方法')
         }

         //构造函数2
         function Stu(age , name){
            this.age = age
            person.call(this , name)
         }
         Stu.prototype.sayHi = function (){
            console.log('你好')
         }
         const s1 = new Stu(18 ,'李四' )
         console.log(s1)

优点:

把父类得到的全部属性继承到自己身上

缺点:

1.只能继承属性,不能调用方法

2.每次调用Stu时, Stu会自动调用一次 person 函数

组合继承

核心:把原型继承与借用结构继承结合起来使用

        //构造函数1
         function person(name){
            this.name = name
         }
         person.prototype.init = function(){
            console.log('我是 prototype 原型上的方法')
         }

         //构造函数2
         function Stu(age,name){
            this.age = age

            //1.利用构造函数继承, 得到 父类的属性 (放在了 对象上 , 并且没有继承父类原型上的方法)
            person.call(this,name)
         }

         Stu.prototype = new person('这个字符串无意义')

         //2.利用原型继承, 得到父类的属性(原型上)的方法
         Stu.prototype.sayHi = function (){
            console.log('你好')
         }

         const s1 = new Stu(18, '李四')
         console.log('Stu的实例化对象' ,s1)
         console.log('Stu的原型对象',s1.__proto__)

优点:

实例化对象具有继承到的属性,并且能够继承父类原型上的方法

缺点:

实例化对象上 与原型对象上,都有父类的属性(多了一套,但不影响使用)

拷贝继承

在子类构造函数实例化父类构造函数, 得到父类构造函数的实例化对象,然后利用 for...in 可以遍历到原型上的属性这个特点, 将实例化对象的属性与其原型上的方法一起拷贝到子类构造函数的原型中

        //结构函数1
        function person(name) {
            this.name = name
        }
        person.prototype.init = function () {
            console.log('我是 prototype 原型上的方法')
        }

        const p1 = new person('李四')
        //  for (let key in p1){
        //     console.log(key,p1[key])
        //  }

        //结构函数2
        function Stu(age) {
            this.age = age
            //将p1里的属性复制一个份放到Stu里
            for (let key in p1) {
                Stu.prototype[key] = p1[key]
            }

        }
        Stu.prototype.sayHi = function () {
            console.log('你好')
        }
        let s1 = new Stu()
        console.log('Stu的实例化对象', s1)
        console.log('Stu的原型对象', s1.__proto__)

**补充: **

for.....in 遍历可以遍历到对象的原型上的方法

ES6类的继承方法

语法要求:

1.书写子类的时候:class 子类类名 extends 父类类名 {}

2.书写constructor 时,内部 第一行 需要书写super('父类需要的参数')

注意:

1.extends 和 super 必须同时出现 2.super必须书写在 constructor 第一行

        
         //ES6 父类写法
         class person{
            constructor(name){
                this.name = name
            }
            init(){
                console.log('我是 prototype 原型上的方法')
            }
           }
                      class Stu extends person{
            constructor(age,name){
                super(name)
                this.age = age
            }
            sayHi(){
                console.log('hhh')
            }
           }
           const s1 =  new Stu(18,'李四')
           const s2 =  new Stu(18,'小崔')
           console.log(s1)
           console.log(s2)

额外扩展:

ES6 类也可以 ES5 的构造函数 验证方法:

将将 Person ES6 类的写法更改为 ES5 的构造函数写法即可

        // ES5 父类写法
         function person(name){
            this.name = name
         }
         person.prototype.init = function(){
            console.log('我是 prototype 原型上的方法')
         }
         
                    class Stu extends person{
            constructor(age,name){
                super(name)
                this.age = age
            }
            sayHi(){
                console.log('hhh')
            }
           }
           const s1 =  new Stu(18,'李四')
           const s2 =  new Stu(18,'小崔')
           console.log(s1)
           console.log(s2)

拷贝

深浅拷贝

含义:

通常指将一个引用数据类型,拷贝到另一个变量中,但是根据拷贝的方法不同展示出的效果也有差异

注意:赋值不是拷贝

         let obj  = {
            name:'李四',
            age:18,
            info:{
                width:100,
                height:200
            }
         }
         let obj2 = obj
         obj2.age = 20
         console.log(obj) 

原因:因为是直接赋值, 所以修改obj2会影响obj,所以它的内部age的值会被更改

浅拷贝:

将一份数据拷贝到另一份变量中,修改第一层数据是互不影响的,但是修改第二层数据时会互相影响

         let obj  = {
            name:'李四',
            age:18,
            info:{
                width:100,
                height:200
            }
         }

         let obj2 = {}

         for(let key in obj){
            obj2[key] = obj[key]
         }
         obj2.age = 99 //修改外层不会修改原对象的值
        obj2.info.width = 500  //修改内层会修改原对象值

         console.log('原对象',obj)
         console.log('浅拷贝对象',obj2)

深拷贝:

将一份数据拷贝到另一个变量中,不管修改那一层数据,两个对象之间都不会互相影响

针对不同的场合选择不同的代码

针对面试

目的:炫技表现出自己的技术高超,给面试官一个良好的印象

       let obj  = {
            name:'李四',
            age:18,
            info:{
                width:100,
                height:200
            }
         }
        
        let obj2 = {}
         function deepClone(target,origin){
            //target : 目标对象
            //origin : 原始对象

            //需求:将原始对象 内部的所有属性 ,拷贝到目标对象中

            //1.通过 for..in遍历对象, 拿到对象的所有key与对应的value
            
            for(let key in origin){
            
                //2.根据遍历到的属性的属性值是什么类型的,决定执行什么代码.
                
                if(Object.prototype.toLocaleString.call(origin[key]) === '[Object Object]'){
                
                    //表明当前这个key的值一定是对象或
                    
                    target[key] = {}
                    
                    // deepClone('目标对象','原始对象')
                    
                    deepClone(target[key],origin[key])
                    
                }else if(Object.prototype.toLocaleString.call(origin[key]) === '[Object Array]'){
                
                    //表明当前这个key的值一定是数组
                    
                    target[key] = []
                    
                    // deepClone('目标对象','原始对象')
                    
                    deepClone(target[key],origin[key])
                    
                }else {
                
                    //表明当前这个key的值一定不是对象或者数组
                    
                    target[key] = origin[key]
                }
            }

         }

         deepClone(obj2,obj)
         console.log(obj)
         console.log(obj2)

针对工作

目的:节约时间,减少文件的内存占用,使之可以更好的完成需求.

 let obj  = {
            name:'李四',
            age:18,
            info:{
                width:100,
                height:200
            }
         }
 newObj = JSON.parse(JSON.stringify(obj))
 
 console.log(obj)
 console.log(newObj)