ECMAScript创建对象2 —— 原型模式

  • 原型对象的概念

    ECMAScript中,用户每创建一个新函数,都会根据一组特定的规则给这个函数创建一个prototype属性,它指向这个函数的原型对象,这个原型对象包含了这个函数所有实例共享的属性和方法。这里就解决了构造函数模式所存在的最大的问题。

  • 构造函数、原型对象、实例之间的关系
    • 构造函数的prototype属性指向原型对象,原型对象的constructor属性指向构造函数;
    • 实例的有一个指针指向原型对象,ECMA-262第五版中叫做[[prototype]],在Firefox、chorme、Safari浏览器中,实例有一个__ proto__属性指向原型对象,在其他的实现中,脚本是没有直接访问这个指针的方法;
    • 实例和原型对象之间并没有直接联系,但值得注意的是,原型对象的constructor属性也会共享给所有实例,所以,每个实例都可以通过constructor属性访问构造函数,即判断实例的类型
  • 与原型对象相关的方法
    • Person.prototype.isPrototypeOf(person1):判断实例的[[prototype]]指针是否指向构造函数的原型对象;
    • Object.getPrototypeOf(person1):返回实例的[[prototype]]指针的值,也就是对应构造函数的原型对象,这是ECMAScript5提供的方法,ie9+
    • person1.hasOwnProperty():判断属性是否存在于实例中,而不是实例的原型中;
    • Object.getOwnPropertyDescriptor(person1,‘name’):当传入的name属性是person1的原型属性时,这样只能得到undefined,此时需要使用Object.getOwnPropertyDescriptor(Person.prototy, ‘name’),直接在原型对象上调用此方法;
    • Object.keys():获取对象的可枚举的实例属性;
    • Object.getOwnPropertyNames():获取对象的所有实例属性,包括不可枚举的实例属性。
  • 与原型对象相关的操作符
    • delete:原型对象中的属性对于实例来说,名义上只可读不可改(其实,如果属性的值是基本类型的值,这样说是正确的;当属性的值是引用类型的时候,可以修改原型的属性值,这种修改并不会给实例增加实例属性,只是不能修改该属性指向的地址。这里的原理类似ES6新增的const),当在实例对象中重写了同名属性的值时(person1.name=“s”),并不会改变原型对象上对应属性的值,而是会给实例创建一个新的实例属性。经过这样的操作后,即使我们把这个属性的值设为null,也无法访问到原型中的属性,delete操作符就是用来完全删除实例属性的,这样我们才能重新访问原型中的属性;
    • in:in操作符单独使用时,用于判断对象是否拥有某个属性,不管这个属性是实例属性还是原型属性,返回一个Boolean值;还可以使用for-in遍历对象的所有可枚举的属性,若是实例重写了原型中不可枚举的属性,for-in也能遍历到(ie8以及更早版本无法成功遍历)。
  • 更改简单的原型语法

    为了避免多次书写Person.prototype,我们可以采用更简单的原型语法——直接把包含我们需要的属性的字面量对象赋值给原型对象,这样操作之后,我们依然能通过instanceof判断对象的类型,但是此时原型对象的constructor属性已经指向了Object构造函数;虽然我们可以将constructor手动设置为Person,但同时也让constructor属性变为了可枚举属性,此时又需要重新将constructor设置为不可枚举属性,具体实现如下:

    function Person () {
           }
    // 使用简单原型语法添加原型属性
    Person.prototype = {
           
        name: 'nicho',
        sayname: function () {
           
            console.log(this.name)
        }
    }
    // 重设原型对象的constructor属性
    Object.defineProperty(Person.prototype, 'constructor', {
           
        enumerable: false,
        value: Person
    })
    

    通过上面的方式,我们可以解决简单语法带来的constructor属性的问题。但对于下面person1的情况,我们就无能为力了:

    function Person () {
           }
    // 这里先创建了实例
    var person1 = new Person()
    // 使用简单原型语法添加原型属性,为了更好的说明,这里声明变量存储字面量对象
    var proto = {
           
        name: 'nicho',
        sayname: function () {
           
            console.log(this.name)
        }
    }
    Person.prototype = proto
    // 重设原型对象的constructor属性
    Object.defineProperty(Person.prototype, 'constructor', {
           
        enumerable: false,
        value: Person
    })
    // 后创建实例则能访问
    var person2 = new Person()
    person2.sayname() // nicho
    person1.sayname() // Uncaught TypeError: person1.sayname is not a function
    

    person1创建的时候,Person的原型对象还没发生改变,此时person1的[[prototype]]指针指向的最初的原型对象,我们改变了Person.prototype指向的对象后,person1并没有指向新的原型对象proto,也就无法访问新的原型对象上定义的属性和方法了。无论何时,请记住实例的[[prototype]]指针指向的是原型对象,而不是构造函数

  • 原型模式的问题

    原型模式省略了给构造函数传参并初始化相关属性的步骤,导致所有的实例默认的属性值都相同;另一个更大的问题则是共享性引起的,当属性的值是引用类型时,会导致一个实例修改了这个属性,会反映到所有的实例中去。为了解决这两个问题,我们可以组合使用构造函数模式和原型模式。

  • 组合使用构造函数模式和原型模式

    在这个模式中,我们使用构造函数模式定义实例属性,使用原型模式定义方法及其它共享的属性:

    function Person (name, height) {
           
        // 实例属性定义在构造函数中
        this.name = name
        this.height = height
    }
    // 方法和共享的属性定义在原型对象中
    Person.prototype = {
           
        sayname: function () {
           
            console.log(this.name)
        }
    }
    // 重设原型对象的constructor属性
    Object.defineProperty(Person.prototype, 'constructor', {
           
        enumerable: false,
        value: Person
    })
    

你可能感兴趣的:(web前端,javascript,前端,原型对象)