Javascript中创建对象的几种方式

本文整理了JavaScript创建对象的七种模式,包括工厂模式、构造函数模式、原型模式等。工厂模式解决创建相似对象问题,但无法识别对象类型;构造函数模式可识别类型,但方法会重复创建;原型模式让方法和属性共享,但引用类型属性有问题,后续模式则对这些问题进行了改进。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近看网上各种文章 , 头异常的大 , 于是整理了一下

1. 工厂模式

单纯地创建对象, 使用Object构造函数, 或者字面量表示都 ok, 但是, 会产生大量重复代码, 每次都要写new Object()等等,
工厂模式可以解决这个问题

	function createPerson(name, job){
		// 创建一个Object
		var o = new Object()
		o.name = name
		o.job = job
		// 口吐芬芳
		o.sayFword= function(){
			alert('Funny')
		}
		return o
	}
	// 创建实例
	var person1 = createPerson('Tony Stark', 'Iron Man')
	var person2 = createPerson('进喜 王', '铁人')

工厂模式解决了创建多个相似对象的问题, 但是没有解决对象识别问题, 所有实例类型均为 object.
为了解决这个问题, 群众想出了构造函数模式

2. 构造函数模式
	function Person(name, job){
		this.name = name
		this.job = job
		// 口吐芬芳
		this.sayFword= function(){
			alert('Funny')
		}
	}
	// 创建实例
	var person1 = new Person('Tony Stark', 'Iron Man')
	var person2 = new Person('王进喜', '铁人')

这里, Person 取代了 createPerson函数, 而且, 有几个不同:

  • 没有显式创建对象
  • 直接将属性, 方法赋给了this
  • 没有显式return
  • 函数名以大写字母开头

这个时候, 就可以识别对象类型了

	person1.constructor === Person //true
	person2.constructor === Person //true

	person1 instanceof Person //true
	person2 instanceof Person //true

如果不用 new 会有什么后果 ?
后果是, this指向全局对象, 浏览器中, 传入的属性和方法都挂到window对象了

	var person3 = Person('王进喜', '铁人')
	window.name //'王进喜'
	window.job //'铁人'

我们可以用call或apply, 将this指向实例, 但这样标识不了类型, 构造函数模式便失去了意义

	var person4 = new Object()
	Person.call(person4, '王进喜', '铁人')
	person4.name //'王进喜'
	person4 instanceof Person //false

当然, 除非手动将__proto__指向Person.prototype, 这是后话

person4.__proto__ = Person.prototype
person4 instanceof Person // true

构造函数模式有一个问题 —— 每个方法都要在每个实例上创建一遍, 也就是说, 不同函数的同名方法实际上是不相等的

	// person1下的sayFword 和 person2下的sayFword 是两个不同的方法
	person1.sayFword === person2.sayFword //false

而我们想要让某一些方法成为该类型的所有实例共用的, 而不是每创建一个实例就去创建一个同样的方法/属性
于是, 群众又想出了下一个模式, 原型模式

3. 原型模式

原型模式的思想是, 给构造函数 (实际上只要是函数) 赋予一个prototype属性 , 这个属性的值是一个指针, 指向一个对象, 而这个对象包含其所有实例共享的属性和方法

	function Person(){
	}
	Person.prototype.name = 'Tony Stark'
	Person.prototype.job = 'Iron Man'
	// 口吐芬芳
	Person.prototype.sayFword= function(){
		alert('Funny')
	}
	// 创建实例
	var person1 = new Person()
	var person2 = new Person()
	person1.sayName === person2.sayName // true

默认情况下 prototype 会自动获得 constructor 属性, 该属性指向构造函数本身

	Person.prototype.constructor === Person  // true

如果实例中定义了跟prototype重名的属性, 会怎么样呢

	var person3 = new Person()
	person3.name = '灭霸'
	console.log(person3.name) //'灭霸'
	console.log(Person.prototype.name) // 'Tony Stark'

很明显, 原型上的同名属性并不会受到影响
我们可以用delete操作符删除对实例的属性, 从而重新访问原型中的属性

	delete person3.name
	console.log(person3.name) // 'Tony Stark'

需要注意的是, 使用 in 操作符, 即使属性是在原型上, 也会返回 true (for in 亦是如此)

	"job" in person3  //true

单纯的原型模式, 也是有缺点的, 例如

	function Person(){
	}
	Person.prototype = {
		friends: [1,2]
	}
	// 创建实例
	var person1 = new Person()
	var person2 = new Person()
	person1.friends.push(3)
	console.log(person2.friends)  // 1,2,3

如果共享的属性为引用类型, 修改实例该属性时, 会影响到原型上的该属性, 从而影响到其他实例
这个问题可以用 组合构造函数和原型模式 来解决

4. 组合构造函数和原型模式

构造函数模式用于定义实例的属性, 而原型模式用于定义方法和共享属性 , 集两家之所长

	function Person(name, job, friends){
		this.name = name
		this.job = job
		this.friends = friends
	}
	// 口吐芬芳
	Person.prototype.sayFword = function(){
		alert('Funny')
	}
	// 创建实例
	var person1 = new Person('Tony Stark', 'Iron Man', [1, 2])
	var person2 = new Person('王进喜', '铁人', [3, 4])
	person1.sayFword === person2.sayFword // true
	person1.friends.push(3)
	console.log(person2.friends)  // 3, 4
5. 动态原型模式

这种模式多了一层判断

	function Person(name, age) {
	    this.name = name;
	    this.age = age;
	    if (typeof this.sayFword != "function") {
	        Person.prototype.sayFword = function() {
	            alert(this.name);
	        };
	    }
	}

那么, 这样做的优点在哪呢 ?

想想看, 如果没有 if 的话,每 new 一次,都会重新定义一个新的函数,然后挂到Person.prototype.sayFword 属性上, 这就造成没必要的时间和空间浪费 。加上 if 后,只在new第一个实例时才会定义sayFword 方法,之后就不会了 。
并且 ————
if语句检查的可以是初始化之后应该存在的任何属性或方法 —— 不必用一大堆if语句检查每个属性和每个方法,只要检查其中一个即可。
———— 什么意思呢 ?
是这样的:假设除了sayFword 方法外,还定义了其他方法,比如sayHi、sayBye等等。此时只需要把它们都放到对sayFword 判断的if块里面就可以了

	if (typeof this.sayFword != "function") {
	    Person.prototype.sayFword = function() {...};
	    Person.prototype.sayHi = function() {...};
	    Person.prototype.sayBye = function() {...};
	    ...
	}

这样一来,要么它们全都还没有定义(new第一个实例时),要么已经全都定义了(new其他实例后),即它们的存在性是一致的,用同一个判断就可以了,而不需要分别对它们进行判断。

6. 寄生构造函数模式
	function SpecialArray() {
	    var values = new Array();
	    // 结构 arguments 拼接数组 ,在es6中不用这样写, 直接 value.push(...arguments)
	    values.push.apply(values, arguments);
	    // 此方法 将数组用|分隔拼接成字符串返回
	    values.toPipedString = function() {
	        return this.join('|');
	    };
	    return values;
	}

虽然它写的和工厂模式一样,但是创建时用了new,因此使得实现的过程不一样(但是实现过程不重要)
作用嘛, 比如创建具有额外方法的已有类型(如数组,Date类型等,但是又想不污染原有的类型 ;
例如上面如果直接在Array中定义新的方法,会污染其它的数组对象, 于是创建了一个 SpecialArray 类型, 它包含了Array 的所有方法, 又有自己的方法 toPipedString ;

7. 稳妥构造函数模式

稳妥对象指没有公共属性,而且其他方法也不引用this的对象。稳妥对象最适合在一些安全的环境中(这些环境中会禁止使用this和new),或者防止数据被其他应用程序改动时使用 .

	function Persion(name, age, job) {
	  // 创建要返回的对象
	  var o = new Object();
	  // 添加方法
	  o.sayName = function() {
	    alert(name);
	  }
	  return o;
	}
	var p1 = Persion('bill', 23, 'FE');
	p1.sayName() // bill;

除了了调用sayName()外,没有别的方式可以访问其他数据成员。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值