JS继承/原型/作用域

242 阅读4分钟

一、前置概念

  1. 对象:在js中一切function都是对象,function是构建对象的基本属性
  2. 实例:通过new关键字定义的变量称之为实例
  3. 显示原型:对象有显示原型,并且当前的显示原型指向的原型
  4. 隐示原型:实例都有隐示原型,隐示原型指向对象的显示原型*

二、继承实现的两种常见方法

1. 原型继承

  • 原型继承主要的实现原理是将子类的显示原型指向父类的实例
/**
         * 定义父类,一切继承都来源于对象,js的对象就是有function构成的
         */
        function Book(){
            this.bookname =arguments[0]
            this.author =arguments[1]
            this.getBookInfo=function(){
                console.log(`${this.bookname} 出版人 ${this.author},共计1本书籍`)
            }
        }
        /**
         * 定义子类,通过原型链继承父类
         */
         function EnBook(){
             this.readyEn = function(){
                 console.log(`${this.bookname} 出版人 ${this.author},可以读英语`)
             }
         }
         EnBook.prototype = new Book('','')
         EnBook.bookname="英语教科书1" // 无效操作
         EnBook.author="liuxinzhous1" // 无效操作
        //  EnBook.getBookInfo()
        const enbook =new  EnBook()
        enbook.bookname="英语教科书1"
        enbook.author="liuxinzhous1"
        console.log(enbook.bookname)
        console.log(enbook.author)
        enbook.getBookInfo()
        enbook.readyEn()
        // 实例永远是对象,而且一直指向最顶端的父类,只能按照对象来判断
        console.log(enbook instanceof EnBook)
        console.log(enbook instanceof Book)
        console.log(enbook instanceof Object)
        // 对象永远是方法,
        console.log(EnBook instanceof Function)
        console.log(Book instanceof Function)
        console.log(Object instanceof Function)

重要点:

  • 实例对象可以访问父类对象的属性和方法,也可以访问子子类对象的方法和属性
  • 也就是说实例对象既是子类的实例,也是父类的实例
  • 实例永远是对象,而且一直指向最顶端的父类,只能按照对象来判断
  • 对象永远是方法 缺点:
  • 没有构造函数,导致无法在构造方法中添加属性和方法
  • 无法实现多继承
  • 创建子类实例时,无法向父类构造函数传参,没有super方法

2. es6新语法实现继承

            constructor(name){
                this.name = name
            }
            sayHi(){
                console.log(`${this.name}:正在说话,我是老大`)
            }
        }
        class Student extends Person{
            constructor(name){
                super(name)
            }
            study(){
                console.log(`${this.name}:我是学生`)
            }
        }
        console.log(Person.prototype)
        console.log(Student.prototype)
        console.log(Person instanceof Function) // class 对象的本质是function
        console.log(Student instanceof Function) // class 对象的本质是function
        const xiaoming = new Student()
        // 实例对象的隐示原型于创建的对象的显示原型一致
        console.log(xiaoming.__proto__ === Student.prototype)
        // 对象的显示原型等于父类的隐示原型,再类型匹配上,对象的显示原型==== 对象的类型
        console.log(Student.prototype instanceof Person)

三、原型与原型链

  1. 类对象的原型实际指的是该对象的prototype对象
  2. 子类对象的prototype原型永远指向父类
  3. 实例对象的__proto__指向对象的原型prototype
           constructor(name){
            this.name = name
           }
       }
       class EnBook extends Book{
           constructor(name,school){
                super(name)
                this.school = school
           }

       }
       class ClassEN extends EnBook{
           constructor(name,school ,classNo){
                super(name,school)
                this.classNo = classNo
           }
       }
    //    const xiaoxue = new ClassEN('英语练习册', '小学','一年级')
    //    console.log(xiaoxue.name)
    //    console.log(xiaoxue.school)
    //    console.log(xiaoxue.classNo)


    //           // 类型可以继承显示,但是原型不可以继承显示
    //           console.log(xiaoxue instanceof  ClassEN) //  true
    //    console.log(xiaoxue instanceof EnBook) // true
    //    console.log(xiaoxue instanceof Book) // true
    //    // 实例对象的隐示原型指向类对象的显示原型
    //    console.log(xiaoxue.__proto__ ===ClassEN.prototype) // 只对象当前创建对象
    //    console.log(xiaoxue.__proto__ ===EnBook.prototype) // false
    //    console.log(xiaoxue.__proto__ ===Book.prototype) // false
    //    console.log("--------------------------")
    //    console.log(xiaoxue.prototype) // 实例没有prototype对象
    //    console.log(ClassEN.prototype) // 对象的显示对象指向父对象,构造方法是自己
    //    console.log(EnBook.prototype) // 对象的显示对象指向父对象,构造方法是自己
    //    console.log(Book.prototype) // 对象的显示对象指向父对象,构造方法是自己
    //    console.log("--------------------------")


    //    console.log(xiaoxue.__proto__) // 实例没有prototype对象
    //    console.log(ClassEN.prototype) // 对象的显示对象指向父对象,构造方法是自己
    //    console.log(ClassEN.prototype.__proto__) // 对象的显示对象指向父对象,构造方法是自己
    // //    console.log(ClassEN.prototype.__proto__) // 对象的显示对象指向父对象,构造方法是自己
    const xiaoming  = new EnBook('英语词典','大学')
    // 子类对象的prototype原型永远指向父类
    // console.log(ClassEN.prototype) // EnBook
    // console.log(EnBook.prototype) // Book
    // console.log(Book.prototype) // Object

    console.log(xiaoming.__proto__ == EnBook.prototype) // Book

    console.log(EnBook.__proto__) 
    console.log(ClassEN.__proto__) 

image.png

四、作用域和闭包的关系

js改变作用域的三种方式进行理解改造

1. bind函数的改造

     * 重新写bind函数
     * 要点利用数组的slice函数对参数进行分解,
     * 然后返回一个内部函数
     * */
    Function.prototype.bind = function () {
        // 利用slice函数进行类数组转换为真实数组
        let args = Array.prototype.slice.call(arguments)
        // 获取arguments的第一个对象为最新的this对象
        let that = args.shift()
        const sefl = this
        // 返回一个新的方法
        return function () {
            // 获取bing函数时的参数,并合并到bing调用时的参数
            let applyArgs = Array.prototype.slice.call(arguments)
            applyArgs.shift()
            // 利用apply进行数据传递
            sefl.apply(that, args.concat(applyArgs))
            // 或者利用call进行散列参数传递
            sefl.call(that,...args,...applyArgs)
        }
    }

2. call函数的改造

     * 参数为当前的要修改的this,已经要传递的参数,传递的参数需要用...进行解析
     * 解析参数以后给第一个context添加一个this对象,此时的this指向的就是这个方法
     * 然后调用这个方法
     * */
    Function.prototype.call = function (context, ...args) {
        //这里默认不传就是给window,也可以用es6给参数设置默认参数
        context = context || window
        args = args ? args : []
        //给context新增一个独一无二的属性以免覆盖原有属性
        const key = Symbol()
        context[key] = this
        //通过隐式绑定的方式调用函数
        const result = context[key](...args)
        //删除添加的属性
        delete context[key]
        //返回函数调用的返回值
        return result
    }

3. apply函数改造

     * context ,要改变的this对象
     * args,要调用调用的参数
     * 和call一样,只是接受的参数不同
    */
    Function.prototype.apply = function (context, args) {
        // 获取参数
        context = context || window
        args =args
        let key = Symbol()
        context[key] = this
       const  result  =  context[key](...args)
       delete context[key]
       return result
    }

3. apply加强函数改造可以传递多个数组

     * context ,要改变的this对象
     * args,要调用调用的参数
     * 主要是拼接多个参数
    */
    Function.prototype.applyPlus = function (context, ...args) {
        let newArrs =[]
        // 获取参数
        context = context || window
        args = Array.prototype.slice.call(args)
        for (let index = 0; index < args.length; index++) {
            const element = args[index];
            newArrs = newArrs.concat(element)
        }
        let key = Symbol()
        context[key] = this
       const  result  =  context[key](...newArrs)
       delete context[key]
       return result
    }