Javascript 构造函数和类

1.构造函数

  • 含义:所谓”构造函数”,就是专门用来生成实例对象的函数。它就是对象的模板,描述实例对象的基本结构。一个构造函数,可以生成多个实例对象,这些实例对象都有相同的结构
  • 写法:构造函数的名称一般都是首字母大写,用来表明这是一个构造函数,其内部通过this给实例挂载属性和值,通过关键字new调用该方法,来生成一个对应的对象
<script>
    // 创建构造函数
    function Person(name,age){
        // 挂载在this上面的都是实例属性
        this.name = name
        this.age = age
    }

    // 创建实例
    var p1 = new Person("张三",20)
    var p2 = new Person("李四",18)
</script>

2.关键字new

使用new命令时,它后面的函数依次执行下面的步骤:

  • 创建一个空对象,作为将要返回的对象实例
  • 将这个空对象的原型,指向构造函数的prototype属性
  • 将这个空对象赋值给函数内部的this关键字
  • 开始执行构造函数内部的代码
<script>
    // 创建构造函数
    function Person(name,age){
        // 挂载在this上面的都是实例属性
        this.name = name
        this.age = age
    }

    // 模拟new关键字(传入构造函数和其参数)
    function _new(){
        //所有参数
        var args = Array.from(arguments)
        //目标构造函数(第一个参数),剩下的args就是参数
        var constructor = args.shift()
        // 创建一个要返回对象,并继承构造函数的 prototype 属性
        var context = Object.create(constructor.prototype)
        //执行构造函数,并绑定this,传入参数
        var result = constructor.apply(context,args)
        //判断构造函数是否有返回值,且是对象结构,如果是,则返回构造函数原来的返回值,否则返回当前创建的对象context
        if(typeof result === 'object' && result != null){
            return result
        }else{
            return context
        }
    }

    //创建实例
    var p1 = _new (Person,"张三",20)
    var p2 = _new (Person,"李四",18)
    console.log(p1) //Person {name: '张三', age: 20}
    console.log(p2) //Person {name: '李四', age: 18}
    
</script>
  • 如果没有使用new进行调用,构造函数就变成了普通函数,并不会生成实例对象,且此时的this指向全局,对this的属性赋值会污染全局变量
<script>
    //全局变量
    var name = "张三"
    var age = 20

    // 创建构造函数
    function Person(name,age){
        // 挂载在this上面的都是实例属性
        this.name = name
        this.age = age
    }

    //不使用new直接调用构造函数
    Person("李四",18)

    //全局变量被污染
    console.log(name,age) //李四 18
</script>
  • 为了防止这种情形,可以在构造函数第一行加上"use strict",这样的话,一旦忘了使用new命令,直接调用构造函数就会报错
<script>
    // 创建构造函数
    function Person(name,age){
        "use strict"
        // 挂载在this上面的都是实例属性
        this.name = name
        this.age = age
    }
</script>
  • 判断构造函数是否由new调用:在函数内部,通过new.target的值来判断,如果直接调用,则返回undefined,否则返回构造函数本身
<script>
    // 创建构造函数
    function Person(name,age){
        //输出new.target
        console.log(new.target)
        // 挂载在this上面的都是实例属性
        this.name = name
        this.age = age
    }

    //不使用new直接调用构造函数
    Person("李四",18) //undefined
    //使用new直接调用构造函数
    new Person("李四",18) //=>Person
</script>

3.原型链

  • 在JavaScript中,所有数据(对象)都是居于构造函数构建的,这些构建的实例之间的数据都是彼此独立的,JavaScript需要一个机制来让这些实例共享一些数据和方法,以此节省资源和性能,这个机制就继承
  • JavaScript 规定,所有对象都有自己的原型对象(prototype,只有函数才可以直接访问并修改这个属性,普通对象不能直接访问),指向一个对象,凡事通过它创建的实例就可以以继承的形式访问这个属性,以此达到各个实例共享的效果
  • 所有对象都自己的原型对象(prototype),而且任何一个对象,都可以充当其他对象的原型,彼此引用嵌套,因此,就会形成一个“原型链”(prototype chain),通俗的说,就是构造函数可以指定其他对象为自己的prototype,以此来访问这个对象乃至这个对象上一级原型的属性和方法
  • 构造函数的prototype默认有一个属性constructor指向其自身,且可以对prototype进行操作,以此扩展子类的功能,所以通过他创建的实例都可以访问这个prototype上面的属性和方法
<script>
    // 创建构造函数
    function Person(name,age){
        // 挂载在this上面的都是实例属性
        this.name = name
        this.age = age
    }

    // 创建实例
    var p1 = new Person("张三",20)
    var p2 = new Person("李四",18)
    // 访问实例属性
    console.log(p1.name,p1.age) //张三 20
    console.log(p2.name,p2.age) //李四 18

    // 添加原型链属性/方法(所有实例共享)
    Person.prototype.sayHello = function(){
        console.log(`我的名字是${this.name},今年${this.age}岁`)
    }
    // 访问原型链方法
    p1.sayHello() //我的名字是张三,今年25岁
    p2.sayHello() //我的名字是李四,今年23岁

    // 类方法/静态属性(只能由构造函数本身访问)
    Person.title = "这是构造函数的标题"
    console.log(Person.title) //"这是构造函数的标题"
    console.log(p1.title) //undefined
    console.log(p2.title) //undefined

    // 动态创建实例方法(与原型链方法重名)
    p1.sayHello = function(){
        console.log("我是p1的sayHello()")
    }
    // 优先从实例属性中读取
    p1.sayHello() //我是p1的sayHello()
    // p2没有相关实例属性,所以访问原型链方法
    p2.sayHello() //我的名字是李四,今年23岁
</script>
  • prototype和constructor属性的关系:构造函数自带prototype,而prototype默认的constructor属性执行构造函数本身
<script>
    // 创建构造函数
    function Person(name,age){
        // 挂载在this上面的都是实例属性
        this.name = name
        this.age = age
    }

    //构造函数的prototype默认带constructor属性,执行其自身
    console.log(Person.prototype.constructor) //==>Person
    //添加原型链方法
    Person.prototype.sayHello = function(){
        console.log(`我的名字是${this.name},今年${this.age}岁`)
    }

    //创建实例
    var p1 = new Person("张三",20)
    //输出其构造函数
    console.log(p1.constructor) //==>Person
    //p1本身没有constructor这个属性
    console.log(p1.hasOwnProperty("constructor")) //false
    //指向同一个地址
    console.log(Person.prototype.constructor === p1.constructor) //true
    //结论:p1本身没有constructor这个属性,接着遍历原型链,在其构造函数的原型中找到了这个constructor进行访问

    //强制修改构造函数原型链
    Person.prototype.constructor = "new - constructor"
    //跟着发生改变
    console.log(p1.constructor) //new - constructor

</script>

4.构造函数的继承

  • 继承实例属性
  • 继承原型链
<script>
    // 创建父类构造函数
    function Person(name = "",age = 0){
        //保存参数
        this.name = name
        this.age = age
    }
    //原型方法
    Person.prototype.sayHello = function(){
        console.log(`我的名字是${this.name},今年${this.age}岁`)
    }

    // 创建继承Person的构造函数
    function Student(name,age,school){
        // 挂载实例属性
        this.school = school
        // 调用Person()并强制绑定this,为实例挂载属性
        Person.call(this,name,age)
    }

    // 绑定原型链为Person的匿名实例
    //Student.prototype = new Person()
    //或者用下面这两句
    //Object.create() 方法用于创建一个新对象,使用现有的对象来作为新创建对象的原型
    Student.prototype = Object.create(Person.prototype)
    Student.prototype.constructor = Student

    // 为Student添加新的原型方法
    Student.prototype.motto = function(){
        console.log("好好学习,天天向上")
    }

    // 创建实例
    var s1 = new Student("李四",25,"蓝翔")
    // 访问实例属性
    console.log(s1.name,s1.age,s1.school) //李四 25 蓝翔
    // 访问Person原型上的方法
    s1.sayHello() //我的名字是李四,今年25岁
    // 访问Student原型上的方法
    s1.motto() //好好学习,天天向上
</script>

5.class类

  • 由来:在ES5的构造函数中,原型链属性和方法,类方法需要写在构造函数外部,这样的写法不够清晰明了,也不便于管理维护,所以在ES6中诞生了class类的概念
  • 含义:class类只是一个语法糖,它的绝大部分功能,ES5 都可以做到,但是他的写法更加清晰、更像面向对象编程,一个类(构造函数)的所有代码都放入同一段代码块中,更加容易管理维护,他的特点如下:
    (1)通过class进行声明,在类内部默认的constructor()方法中设定实例属性
    (2)类里面的其他方法和属性一律默认挂载到原型链中,供所有实例访问,如果是静态属性,则使用static关键字进行声明
    (3)类的数据类型就是函数,类本身就指向构造函数
<script>
    // 创建一个类
    class Person{
        // constructor内部的数据是每个实例有独有一份
        constructor(name,age){
            this.name = name
            this.age = age
        }
        // 原型链方法
        sayHello(){
            console.log(`我的名字是${this.name},今年${this.age}岁`)
        }
        // 静态方法(只能由类本身访问)
        static foo(){
            console.log("我是类的静态方法")
        }
        // 静态属性(只能由类本身访问)
        static title = "这是类的标题"
    }
    // 创建实例
    var p1 = new Person("张三",20)
    var p2 = new Person("李四",18)
    // 访问实例属性
    console.log(p1.name,p1.age) //张三 20
    console.log(p2.name,p2.age) //李四 18
    // 修改实例属性
    p1.age = 25
    p2.age = 23
    console.log(p1.name,p1.age) //张三 25
    console.log(p2.name,p2.age) //李四 23

    // 访问原型链方法
    p1.sayHello() //我的名字是张三,今年25岁
    p2.sayHello() //我的名字是李四,今年23岁
    // 类方法/静态属性(只能由类本身访问)
    Person.foo() //"我是类的静态方法"
    console.log(Person.title) //"这是类的标题"
    console.log(p1.title) //undefined
    console.log(p2.title) //undefined

    // 类的本质
    console.log(typeof Person) //function
    console.log(p1.sayHello === Person.prototype.sayHello) //true
</script>

6.extends 继承

  • 含义:在创建类的时候,可以继承其他其他类的属性和方法,它有以下几个特点:
    (1)通过关键字 extends 进行继承,且在constructor()中必须调用super()方法
    (2)子类可以设定可以的原型链方法,还可以访问父类的原型链方法
<script>
    // 创建一个类
    class Person{
        // constructor内部的数据是每个实例有独有一份
        constructor(name,age){
            this.name = name
            this.age = age
        }
        // 原型链方法
        sayHello(){
            console.log(`我的名字是${this.name},今年${this.age}岁`)
        }
        // 静态方法(只能由类本身访问)
        static foo(){
            console.log("我是类的静态方法")
        }
        // 静态属性(只能由类本身访问)
        static title = "这是类的标题"
    }
    // Student类继承Person
    class Student extends Person {
        // name,age,school就是创建时传入的参数
        constructor(name,age,school){
            // super()是父类的构造器,将name和age传递给他
            super(name,age)
            // 添加新的实例属性,接收school参数(学生这个类有自己的实例属性)
            this.school = school
        }
        // 添加原型链方法
        motto(){
            console.log("好好学习,天天向上")
        }
        // 添加类属性
        static title = "我是Studnet类的标题"
    }
    // 创建实例
    var stu1 = new Student("小明",16,"蓝翔")
    var stu2 = new Student("小强",17,"新东方")
    // 访问实例属性
    console.log(stu1.name,stu1.age,stu1.school) //小明 16 蓝翔
    console.log(stu2.name,stu2.age,stu2.school) //小强 17 新东方
    // 修改实例属性
    stu1.age = 20
    stu2.age = 21
    console.log(stu1.name,stu1.age,stu1.school) //小明 20 蓝翔
    console.log(stu2.name,stu2.age,stu2.school) //小强 21 新东方

    // 访问原型链方法
    stu1.sayHello() //我的名字是小明,今年20岁
    stu2.sayHello() //我的名字是小强,今年21岁
    stu1.motto() //好好学习,天天向上
    stu2.motto() //好好学习,天天向上

    // 类方法/静态属性(只能由类本身访问)
    console.log(Student.title) //"我是Studnet类的标题"
    console.log(stu1.title) //undefined
    console.log(stu2.title) //undefined

    // 类的本质
    console.log(typeof Student) //function
    console.log(stu1.motto === Student.prototype.motto) //true

    // 子类的原型链指向了父类
    console.log(stu1)
    // Student {name: "小明", age: 20, school: "蓝翔"}
    // age: 20
    // name: "小明"
    // school: "蓝翔"
    // __proto__: Person
</script>
posted @ 2019-09-24 20:04  ---空白---  阅读(3013)  评论(1编辑  收藏  举报