JavaScript:原型模式

本文深入探讨JavaScript中的原型模式,解释了操作符new和构造函数的工作原理,包括内部属性new.target和constructor,以及如何通过实例对象访问和修改原型。此外,还介绍了Object相关API如isPrototypeOf(), getPrototypeOf(), setPrototypeOf(), create()和hasOwnProperty()等。" 112706918,10295016,排序算法解析:时间复杂度与稳定性,"['排序', '算法', '数据结构']
摘要由CSDN通过智能技术生成

ECMAScript 使用 原型模式 来实现多个对象共享某个对象的所有属性的操作。

当创建一个函数对象时,会为函数对象创建一个属性 prototype 引用一个对象,被称为 原型对象 ,而原型对象会有一个属性 constructor 引用该函数对象,即函数和原型之间相互引用,形成循环引用。

对象拥有一个内部特性 [[Prototype]] ,是一个指针,指向某个原型对象。

通过对象可以直接访问原型的属性。

在访问对象的属性时,会根据属性名搜索属性,首先在当前对象中进行搜索,如果没有找到属性,就会通过内部特性 [[Prototype]] 找到对象的原型,在对象的原型中继续进行搜索。

使用同一个原型的多个对象会共享该原型的属性。

示例:

  • 函数的属性 prototype 与原型的属性 constructor 。
    function func() {}
    const prototype = func.prototype,  // 函数的原型
    	constructor = prototype.constructor  // 函数原型的属性 constructor 
    
    console.log('prototype:', prototype) 
    console.log('constructor:', constructor) 
    console.log('constructor === func :', constructor === func)
    
    // 输出:
    // prototype: {constructor: ƒ}
    // constructor: ƒ func() {}
    // constructor === func : true
    

主要参考资料:

  • 《JavaScript 高级程序设计(第4版)》
    • P221(246/931)
    • P224(249/931)

操作符 new 与构造函数

ECMAScript 使用操作符 new 和构造函数来实现模板化创建对象的操作。

构造函数提供模板。

操作符 new 根据提供的模板创建实例对象。


操作符 new

使用操作符 new 调用函数,会执行如下操作:

  1. 在内存中创建一个新对象。
  2. 新对象的内部特性 [[Prototype]] 被赋值为函数的属性 prototype 。
  3. 函数的对象 this 被赋值为新对象。
  4. 执行函数的函数体。
  5. 如果函数有非空的返回值,则返回函数的返回值,否则返回函数的对象 this 。

示例:

function Entity() {
	console.log('execute Entity()')
}

const entity = new Entity()  // 使用操作符 new 调用函数
console.log('entity:', entity)

// 输出:
// execute Entity()
// entity: Entity {}

构造函数

使用操作符 new 调用的函数,被称为 构造函数

ECMAScript 的构造函数是被设计用于创建实例对象的普通函数。

构造函数与普通函数在本质上、语法上没有任何区别。

构造函数的函数名一般采用首字母大写的形式。

在构造函数中,可以使用对象 this 为使用该构造函数创建的对象添加属性。

示例:

  • 在构造函数中,使用对象 this 为使用该构造函数创建的对象添加属性。
function Entity() {
	this.attrEntity_01 = '1948'  // 为使用该构造函数创建的对象添加属性。
	this.attrEntity_02 = '2017'
}
const entity = new Entity()

console.log('entity:', entity)
// 输出:
// entity: Entity {attrEntity_01: '1948', attrEntity_02: '2017'}

函数的内部属性 new.target

函数拥有一个内部属性 new.target ,值为布尔值,表示调用函数时是否使用了操作符 new 。

示例:

  • 函数的内部属性 new.target
    function Test() {
    	if(new.target) {
    		console.log('invoked with new')
    	}
    	else {
    		console.log('invoked commonly')
    	}
    }
    const test = new Test()
    Test()
    
    // 输出:
    // invoked with new
    // invoked commonly
    

对象的属性 constructor

通过对象可以访问对象原型的属性 constructor ,引用创建该对象时使用的构造函数。

示例:

  • 对象的属性 constructor
    function Entity() {
    	console.log('instantiate Entity')
    }
    const entity = new Entity()
    console.log('entity.constructor:', entity.constructor)  // 对象的属性 constructor
    
    // 输出:
    // instantiate Entity
    // entity.constructor: ƒ Entity() {
    //---- console.log('instantiate Entity')
    //-- }
    

实例对象

使用操作符 new 调用函数创建一个新对象的过程,被称为 实例化

使用构造函数创建的对象,被称为 实例对象

在实例对象上添加与原型属性同名的属性,会 遮蔽(shadow) 相应的原型属性,屏蔽对原型属性的访问,但不会覆盖原型属性。

示例:

  • 在实例对象上添加与原型属性同名的属性,遮蔽相应的原型属性。
    function Entity() {}
    const prototype = Entity.prototype
    prototype.attr_01 = 'prototype attr_01'
    prototype.attr_02 = 'prototype attr_02'
    
    const instance = new Entity()
    instance.attr_01 = 'instance attr_01'  // 添加与原型的属性同名的属性
    
    console.log('prototype:', prototype)
    console.log('instance:', instance)
    console.log('instance.attr_01:', instance.attr_01)
    console.log('instance.attr_02:', instance.attr_02)
    
    // 输出:
    // prototype: {attr_01: 'prototype attr_01', attr_02: 'prototype attr_02', constructor: ƒ}
    // instance: Entity {attr_01: 'instance attr_01'}
    // instance.attr_01: instance attr_01
    // instance.attr_02: prototype attr_02
    

ECMAScript 没有提供访问对象内部特性 [[Prototype]] 的标准方式。

但一些浏览器会在对象添加一个属性 __proto__ 用于访问对象的原型,例如:Firefox、Safari、Chrome 等。

示例:

  • 使用浏览器提供的属性 __proto__ 访问对象的原型。
    function Entity() {
    	this.attr = '1948'
    }
    const entity = new Entity()
    console.log('entity.__proto__:', entity.__proto__)  // 使用属性 __proto__ 访问对象的原型
    
    // 输出:
    // entity.__proto__: {constructor: ƒ}
    

可以通过对属性名使用操作符 in ,判断对象是否可以访问指定的属性。

示例:

  • 对属性名使用操作符 in ,判断对象是否可以访问指定的属性。
    function Entity() {
    	this.instAttr = '2017'
    }
    Entity.prototype.protoAttr = '2021'
    Entity.entityAttr = '2035'
    
    const entity = new Entity()
    console.log('protoAttr in entity:', 'protoAttr' in entity)  // 判断对象是否可以访问指定的属性
    console.log('instAttr in entity:', 'instAttr' in entity)
    console.log('entityAttr in entity:', 'entityAttr' in entity)
    console.log('entityAttr in Entity:', 'entityAttr' in Entity)
    
    // 输出:
    // protoAttr in entity: true
    // instAttr in entity: true
    // entityAttr in entity: false
    // entityAttr in Entity: true
    

图示:

  • 原型模式:“原型 - 构造函数 - 实例”

原型模式:“原型 - 构造函数 - 实例”


动态设置构造函数的属性 prototype

在动态设置构造函数的属性 prototype 时,可以使用对象字面量对构造函数的属性 prototype 进行赋值。

但使用对象字面量创建对象时,实际调用的是 new Object() ,所以使用对象字面量创建的对象的原型的属性 constructor 是 Object() 。

当将该对象作为构造函数的属性 prototype 时,意味着构造函数原型的属性 constructor 是 Object()。

如果希望修正构造函数的原型的属性 constructor ,可以使用对象字面量创建对象时,为对象添加自身的属性 constructor 来遮蔽对象的原型的属性 constructor 。

但使用对象字面量定义的属性 constructor 的内部特性 [[Enumerable]] 为 true ,而 ECMAScript 默认原型的属性 constructor 是不可枚举的。

如果希望遵循原生的属性 constructor 的设定,则需要单独使用方法 Object.defineProperty() 来重新定义构造函数原型的属性 constructor 。

示例:

  • 使用对象字面量对构造函数的属性 prototype 进行赋值,并遮蔽原型的属性 constructor 。

    function Entity() {
    	this.instanceAttr = '1948'
    }
    Entity.prototype = {
    	constructor: Entity,  // 遮蔽原型的属性 constructor  
    	prototypeAttr: '2017'
    }  // 使用对象字面量对构造函数的属性 prototype 进行赋值
    const entity = new Entity()
    
    console.log('Entity.prototype:', Entity.prototype)
    console.log('Entity.prototype.constructor:', Entity.prototype.constructor)
    console.log('entity:', entity)
    console.log('entity.prototypeAttr:', entity.prototypeAttr)
    
    // 输出:
    // Entity.prototype: {prototypeAttr: '2017', constructor: ƒ}
    // Entity.prototype.constructor: ƒ Entity()
    // entity: Entity {instanceAttr: '1948'}
    // entity.prototypeAttr: 2017
    
  • 使用对象字面量对构造函数的属性 prototype 进行赋值,并使用方法 Object.defineProperty() 重新定义构造函数原型的属性 constructor 。

    function Entity() {
    	this.instanceAttr = '1948'
    }
    Entity.prototype = { 
    	prototypeAttr: '2017'
    }  // 对构造函数的属性 prototype 进行赋值
    Object.defineProperty(
    	Entity.prototype,
    	'constructor',
    	{
    		configurable: true,
    		enumerable: false,
    		writable: false,
    		value: Entity
    	}
    )  // 重新定义构造函数原型的属性 constructor 
    const entity = new Entity()
    
    console.log('Entity.prototype:', Entity.prototype)
    console.log('Entity.prototype.constructor:', Entity.prototype.constructor)
    console.log('entity:', entity)
    console.log('entity.prototypeAttr:', entity.prototypeAttr)
    
    // 输出:
    // Entity.prototype: {prototypeAttr: '2017'}
    // Entity.prototype.constructor: ƒ Entity()
    // entity: Entity {instanceAttr: '1948'}
    // entity.prototypeAttr: 2017
    

主要参考资料:

  • 《JavaScript 高级程序设计(第4版)》- P234(259/931)

相关 API


对象的方法 isPrototypeOf()

对象的方法 isPrototypeOf() :

  • 功能:
    判断指定对象的内部特性 [[Prototype]] 是否指向当前对象。

  • 接收一个参数:
    对象,目标对象。

  • 返回值:
    布尔值,目标对象的内部特性 [[Prototype]] 是否指向当前对象。

示例:

  • 使用对象的方法 isPrototypeOf() ,判断指定对象的内部特性 [[Prototype]] 是否指向当前对象。
    function Entity() {}
    const entity = new Entity()
    
    console.log('Entity.prototype is protoype of entity:', Entity.prototype.isPrototypeOf(entity))  // 判断对象的内部特性 [[Prototype]] 的指向
    
    // 输出:
    // Entity.prototype is protoype of entity: true
    

Object.getPrototypeOf()

方法 Object.getPrototypeOf() :

  • 功能:
    获取指定对象的内部特性 [[Prototype]] 指向的原型。

  • 接收一个参数:
    对象,目标对象。

  • 返回值:
    对象,目标对象的内部特性 [[Prototype]] 指向的原型。

示例:

  • 使用方法 Object.getPrototypeOf() ,获取指定对象的内部特性 [[Prototype]] 指向的原型。
    function Entity() {}
    Entity.prototype.type = 'Entity'
    const entity = new Entity()
    
    console.log('protoype of entity:', Object.getPrototypeOf(entity))  // 获取对象的内部特性 [[Prototype]] 指向的原型
    
    // 输出:
    // protoype of entity: {type: 'Entity', constructor: ƒ}
    

Object.setPrototypeOf()

方法 Object.setPrototypeOf() :

  • 功能:
    动态设置指定对象的内部特性 [[Prototype]] 。

  • 接收两个参数:

    1. 对象
      目标对象。

    2. 对象
      目标对象的内部特性 [[Prototype]] 需要指向的对象。

  • 返回值:
    对象,动态设置内部特性 [[Prototype]] 后的目标对象。

但不建议在编程过程中重写实例的原型,因为在重写实例的原型之前,可能已经在很多地方使用了该实例,如果此时重写实例的原型,将会影响此前所有引用该实例的代码,造成严重的性能问题。

示例:

  • 使用方法 Object.setPrototypeOf() ,动态设置指定对象的内部特性 [[Prototype]] 。
    function EntityA() {}
    EntityA.prototype.type = 'EntityA'
    
    function EntityB() {}
    EntityB.prototype.type = 'EntityB'
    
    const entity = new EntityA()
    Object.setPrototypeOf(entity, EntityB.prototype)  // 动态设置指定对象的内部特性 [[Prototype]] 
    
    console.log('protoype of entity:', Object.getPrototypeOf(entity))
    
    // 输出:
    // protoype of entity: {type: 'EntityB', constructor: ƒ}
    

Object.create()

方法 Object.create() :

  • 功能:
    创建一个内部特性 [[Prototype]] 指向指定对象的新对象。

  • 接收两个参数:

    1. 对象
      目标对象。

    2. 对象(可选)
      属性定义对象,为新对象定义的属性。

  • 返回值:
    对象,一个内部特性 [[Prototype]] 指向目标对象的新对象。

示例:

  • 使用方法 Object.create() ,创建一个内部特性 [[Prototype]] 指向指定对象的新对象。
    const prototypeObj = {
    		prototypeAttr: '1948'
    	},
    	definePropertyObj = {
    		instanceAttr: {
    			configurable: true,
    			enumerable: true,
    			writable: true,
    			value: '2017'
    		}
    	},  // 属性定义对象
    	obj = Object.create(prototypeObj, definePropertyObj)  // 创建一个内部特性 [[Prototype]] 指向指定对象的新对象
    
    console.log('obj:', obj)
    console.log('obj.prototypeAttr:', obj.prototypeAttr)
    console.log('prototypeObj is protoype of obj:', prototypeObj.isPrototypeOf(obj))
    
    // 输出:
    // obj: {instanceAttr: '2017'}
    // obj.prototypeAttr: 1948
    // prototypeObj is protoype of obj: true
    

对象的方法 hasOwnProperty()

对象的方法 hasOwnProperty() :

  • 功能:
    判断当前对象是否存在指定属性名的属性。

  • 接收一个参数:
    字符串 | 符号,属性名。

  • 返回值:
    布尔值,当前对象是否存在指定属性名的属性。

示例:

  • 使用对象的方法 hasOwnProperty() ,判断当前对象是否存在指定属性名的属性。
    function Entity() {
    	this.instAttr = '2045'
    }
    Entity.entityAttr = '2049'
    const entity = new Entity()
    
    console.log('entity has instAttr attribute:', entity.hasOwnProperty('instAttr'))  // 判断当前对象是否存在指定属性名的属性
    console.log('entity has entityAttr attribute:', entity.hasOwnProperty('entityAttr'))
    console.log('Entity has entityAttr attribute:', Entity.hasOwnProperty('entityAttr'))
    
    // 输出:
    // entity has instAttr attribute: true
    // entity has entityAttr attribute: false
    // Entity has entityAttr attribute: true
    

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值