ES5.1和ES6中创建对象的区别

582 阅读4分钟

本文根据《JavaScript高级程序设计(第四版)》以及本人理解整理而成,如有不当之处,希望指正~

一、概述

ES5.1并没有正式支持面向对象的结构(类和继承),但是巧妙地运用原型式继承可以模拟与类和继承一样的行为;

ES6正式开始支持类和继承,其实际是封装了ES5.1构造函数和原型继承的语法糖。

二、ES5.1中的伪面向对象

(1)工厂模式

  • 特点:能够创建多个类似对象,但是没有解决对象标识问题,即新创建的对象实例是什么类型。(无constructor属性)

(2)构造函数模式

  • 定义:构造函数是用来创建特定类型的实例对象。

  • 像Object、Array、Set、Map等原生构造函数,运行时可以直接创建实例。

  • 特性:①:首字母大写  ②:内部使用this对象 ③:使用new操作符

  • 可以自定义构造函数,以函数的形式为对象类型定义属性和方法        

            function Person(name, age, job) {            this.name = name;            this.age = age;            this.job = job;            this.sayname = function () {                console.log(this.name)            }        }        let person1 = new Person("xiaohong", 18, "docotor");        let person2 = new Person("qiushui", 18, "teacher");        console.log(person1);        console.log(person2);
    
  • 上例中person1和person2分别保存了Person的不同实例,这两个对象实例都有一个constructor属性指向Person,表示实例对象类型是Person。

  • 因此与工厂模式相比,构造函数模式能够解决对象标识问题。

  • 但是构造函数任然存在问题,问题在于构造函数定义的方法会在每个实例上都创建一遍,造成相同逻辑的重复定义问题,导致不同实例上的函数虽然同名但不相等。

(3)原型模式

  • 引入原型对象的概念,是通过构造函数创建实例对象所用到的原型对象,在上面定义的属性和方法可以被对象实例共享。

            function Person() {}        Person.prototype.name = "xiaohong";        Person.prototype.age = 18;        Person.prototype.job = "doctor";        Person.prototype.sayName = function () {            console.log(this.name);        }        let person1 = new Person();        console.log(person1.sayName()) //xiaohong        let person2 = new Person();        console.log(person2.sayName()) //xiaohong
    
  • 以上代码中,构造函数里没有属性和方法,将所有属性和方法都直接添加到了Person的prototype属性上,从而实现各个实例对象共享。

  • 构造函数的prototype属性指向原型对象;原型对象中的constructor属性指向构造函数;实例中的[[Prototype]]指针被赋值为构造函数的原型对象。

  • Person.prototype.isPrototypeOf(person1)     //true

  • Object.getPrototypeOf()  //返回实例对象中内部特性[[Prototype]]指针的值

  • Object.setPrototypeOf()  //可以向[[Prototype]]指针写入一个新值,不过该方法可能会严重影响代码性能。

  • 实例中的同名属性会覆盖原型对象上的同名属性,调用时,先从实例对象查找属性或方法,没有再从原型对象上查找。

  • hasOwnProperty( )作用是确定某个属性或者方法是在实例上还是在原型对象上

三、ES5.1中的伪面向对象继承

  • 原型链继承,问题在于当属性值定义在原型对象中时,属性值在各个实例间共享,会造成“属性值污染”。

  • 盗用构造函数是指利用call( )、apply( )将执行上下文改为当前对象,问题在于需要在构造函数中定义方法,从而造成相同逻辑的重复定义问题。

  • 组合继承: 弥补了原型链和盗用函数的不足,如下例所示:

        function Father(name) {            this.name = name;            this.colors = ["red", "yellow", "blue"]        };        Father.prototype.sayName = function () {            console.log(this.name);        };        function Son(name, age) {            Father.call(this, name); //盗用构造函数继承属性            this.age = age;        }        Son.prototype = new Father();  //利用原型链继承方法        Son.prototype.sayAge = function () {            console.log(this.age);        }
    

四、ES6中的面向对象

ES6中主要利用类进行进行对象创建,其中需要借助constructor构造函数器。

        class Person {            constructor(name, age, job) {                this.name = name;                this.age = age;                this.job = job;            }            sayName() {                console.log(this.name);            }        }        let person1 = new Person("xioahong", 18, "docotor");        person1.sayName()

上例中sayName()方法是定义在原型对象上的。

五、ES6中的面向对象继承

(1)extends( )

  • ES6支持单继承,使用extends关键字,可以继承一个类,也可以继承普通的构造函数。

    class Vehicle{}
    class Bus extends Vehicle{} //继承类Vehicle
    

(2)super( )  super可以调用父类构造函数

  • super()只能在**派生类构造函数(即子类构造函数中)**和静态方法中使用

  • 继承中,如果实例化子类输出一个方法,先看子类是否有这个方法,如果有就先执行系类的,如果子类没有,就去查找父类中有没有这个方法,如果有就执行父类的这个方法(就近原则)

  • 子类在构造函数中使用super(),必须将super( )放在this之前调用

            class Son extends Father {            constructor(x, y) {                super(x, y)                this.x = x;                this.y = y;            }            subtract() {                console.log(this.x - this.y);            }        };        let son = new Son(5, 3);        son.subtract()   //2        son.sum()     //8