前言
ECMAScript与其他面向对象语言不同的是,它没有类的概念,因此它的对象也和基于类的语言中的对象有所不同,深入理解js的对象是每个前端工程师的基本素养,本文将就创建对象模式的方面对对象进行介绍
正文
我们可以通过Object构造函数或对象字面量构建对象,但是使用同一个接口创建很多对象时候,会产生大量重复代码,下面将介绍解决这个问题的各种模式
工厂模式
其实就是用函数来封装创建对象的细节来实现复用,但这样不能直接获得对象的类型
构造函数模式
自定义构造函数来定义一个类型的自定义对象类型的属性和方法来实现定义的复用
构造函数模式和工厂模式还是有着很大的区别的:
- 没有显式地创建对象
- 直接将属性和方法赋值给了this对象,这个this对象对指向新创建的对象
- 没有return实例
构造函数与其他函数不同的是,它的调用需要用过new操作符,如果不使用New操作符,则this会指向window对象
构造函数虽然好用,但是有个比较大的缺点,会将每个方法都在实例上重复创建。事实上,创建多个完成同个任务的Function实例是没有必要的,所以我建议大家将函数的定义转移到函数外面来解决这个问题
原型模式
我们创建的每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法,原型是什么,这里我就不再复述了,不懂的同学可以Bing或者谷歌,或者查看我后面写的关于原型的博客章节
像之前的写法一样,通过原型把创建对象的操作封装在函数中就是原型模式
特别的是,通过这种方法创建的新的对象,在修改属性的过程中,只会修改到实例的属性,而不会影响到原型
当然,这种最初始的写法是不优雅的,在上面的例子中每为Person创建一个属性,就需要写一次Person.prototype,产生了大量的不必要输入,常见的写法可以用一个包含所有属性和方法的字面量去重写prototype
这里要注意constructor属性,在默认情况下,所有原型对象都会自动获取一个constructor属性,这个属性是一个指向prototype属性所在函数的指针。如果在重写prototype的过程中忘记对constructor属性赋值,就没办法再通过constructor确认对象那个的类型
可以看到在原型忘记赋值constructor以后,Person定义的对象与Person之间的连接就断开了,没办法再用instanceof获取到他们的关系
同时由于在原型中查找值的过程是一次搜索,因此我们对原型对象所做的任何修改都能立刻从实例上反映出来–即使是先创建了实例后修改原型也照样如此。
可以看到即使在per创建之后再修改原型,仍然会影响到per,这就是原型的动态性,原型的获取是一次搜索,而不是静态的,会随时变化
但是对原型的重写就不一样了,要注意的是,原型修改为另一个对象就等于切断了构造函数与最初原型的联系,实例中的指针只指向原型,不指向构造函数
可以看到重写以后,Person指向的原型就不再是最初的原型,而per对象也不会再指向Person的原型,重写原型对象切断了现有原型与任何之前已经存在的对象实例之间的联系,它们引用的仍然是最初的原型
原型模式省略了构造函数传递初始化参数的环节,有一个致命的问题,引用对象的属性会被所有实例共享
可以看到对per1的friends修改会共享给per2,因为数组的修改并不会修改它的别人对它的引用,所以在原型上修改,意味着所有的对象都会共享这个对象的修改
组合使用构造函数模式和原型模式
之前介绍了工厂模式,构造函数模式和原型模式,都有它们的优势和缺陷,那么这里将介绍目前在ECMAScript使用最广泛,认同度最高的一种创建自定义类型的方法
构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。这样一来,每个实例都会有自己的一份实例属性的副本,但是同时又共享着对方法的引用,最大限度地节省了内存。
这样避免了构造函数方法需要创建多个重复Function实例的问题,又可以避免原型模式导致的非共享的引用属性共享给所有对象的问题
ES6 类
ES6中引用了类的对象,后面我会在专门的章节介绍ES6中类的部分,所以这里卖个关子,感兴趣的同学可以看我后面关于ES6类的介绍
小结
1、构造函数模式定义的类型的方法会因为this的关系导致不同的作用域链和标识符解析,所以即使是一样机制的同名函数却不相等
2、原型模式定义的引用类型的属性会在所有的对象中共享
3、构造函数适合定义实例属性,而原型模式适合定义方法和需要共享的属性,结合使用是认同度最高的写法
小伙伴们今天的学习就到这里了,如果觉得本文对你有帮助的话,欢迎转发,评论,收藏,点赞!!!