在ES5中
我们想创建一个对象,人。
创建一个人,定义名字和技能我们想克隆多个相似的人怎么办?需要一个函数(资源)创造一个 对象模版(对象制造机器),想克隆时候调用它就好了。
用函数(机器)造一个人的模版,想造人时调用它行试想一下,我们用资源制造出一个造人机器,一个造猪机器。我们用造人机器造了很多人,用造猪机器造了很多猪,我们有了很多人和猪。虚拟世界不像真实世界,我们一眼能辨别出人和猪,我们希望能确定一个对象的类型(即它由哪个机器制造出来)。
于是,我们找出一种特别资源,叫构造资源。用构造资源制造出不同类型 对象制造机器,由这些对象制造机器制造出的 对象 会打上对应机器的出产标签,以此来确定对象的类型,我们(上帝)可以通过instanceof来确定这种联系。我们来看看构造资源怎么制造 对象制造机器 进而制造对象的:
构造机器的使用构造函数和普通函数的区别
1、构造函数调用需要new,函数名开头大写。
2、没有return的话会默认返回一个全新的对象实例。
3、构造函数生成的实例会有[[proto]]属性指向原型对象。(每个函数(不管普通/构造)都有prototype属性指向他所对应的原型对象。)
4、构造函数内的this指向新创建的实例。
制造机器模式区别:普通资源显式创造一个对象,然后通过这个对象上的实例属性定义对象的名字、技能,最后返回这个对象;而构造资源不创造对象,直接在机器的实例属性(通过this定义)定义对象名字,技能。即构造机器少了一个创建对象的过程,把机器需要的东西全部放在实例属性上,而不是创建的对象上。
最终导致区别,构造资源制造的实例可以确定类型,而普通资源不行。
构造资源出现问题
慢慢的,我们用构造资源 造出 机器,进而用机器生产实例时我们发现,造实例的技能(skill)时,特别耗费资源。因为一个机器制造的实例的技能是相同,我们每造一个实例,对于它的技能都需要new Fun,会导致不同作用域链和标识符解析,达不到资源复用,浪费内存消耗。
开始想解决办法,我们把技能Fun写在外部,在构造资源内部通过实例属性调用外部技能Fun。这样解决了两个实例的技能不重复new Fun问题。但是,一旦外部Fun多了,我们的制造机器就没有封装性可说了。
不可行的解决方法于是,我们开始寻找其他模式。
原型模式出现
研究发现,我们制造的每个机器都有一个 prototype(原型) 属性,这个属性是一个指针,指向一个对象,这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。
也就是说我们每台制造机器都有一块属于自己的共享区域,里面的属性和方法可以被这台机器创造的所有对象使用到。我们来试试使用它吧:
原型模式我们了解到,每台制造机器都有一个prototype属性(指针)指向原型对象,每台制造机器的原型对象都有一个constructor属性(指针)指向制造机器,每台机器制造的实例上都有一个[[prototype]]属性(指针)指向机器的原型对象(这个联系存在于实例和原型对象之间),可以通过_proto_访问。
实际上,我们在访问一个实例对象的某个属性时,JS解析器会先去问实例"你有这个属性吗",如果没有,他会再去问原型。这就是实力对象在访问属性时进行的搜索过程。所以,如果我们在实例中和原型中有同一个属性的话,我们会默认使用实例中的属性,屏蔽原型中的属性。如果我们想要删除掉实例属性进而访问原型属性可以使用delete进行删除。
好麻烦,每次写原型上的属性都需要敲一边xx.prototype.xxx。其实我们可以用对象字面量法来重写整个原型对象。就像这样:
对象字面量法写原型但是,这样会造成一个严重问题。重写原型对象,切断了原型和构造函数(constructor),原型和之前建立的实例对象的指针([[proto]])的联系。但之后的实例对象还是指向新原型的。constructor指针我们有办法改变,[[proto]]就没办法了。所以我们在初次定义原型的时候可以用这个方法,改写原型一般不用对象字面量法重写原型对象。
constructor指针的重写,其内的constructor指针不再指向原构造函数Person而是Object构造函数。尽管此时instanceof还能返回正确结果,但通过constructor已经无法确定对象的类型了。解决,我们重写一下cnstructor指针就好了。直接这样重写会使constructor属性的[[enumerable]]特性被设置为true,变为可枚举属性(原生不可枚举)。我们可以通过object.defineProperty(Person.prototype, "constructor", { enumerable: false,})来设置。
重写consturctor原型模式的缺点呢?它的优点“共享”也正是它的缺点,这种共享对于函数来说非常合适,对于基本类型值也问题不大,因为实例上定义相同属性值会覆盖原型。但对于引用类型值就有问题了,我们在一个实例对象上修改 引用类型属性 会影响到其他实例对象上。如果你说,引用类型也可以像基本类型一样,在实例属性上定义进行覆盖啊。但这就不符合我们的初衷了,因为现在共享原型对象内储存着本属于实例的属性。
后来,我们有了构造函数模式+原型模式 来制造机器创建对象。
构造函数模式+原型模式构造函数用于定义实例属性,而原型用于定义方法和共享属性。每个实例对象都有着自己的一份实例属性的副本,但同时由存放着对方法的引用,最大限度节省了内存。另外这种混存模式,还支持向构造函数传递参数。集两种模式之长。
当有OO语言经验的开发人员看到独立的构造函数和原型时,会感到困惑。于是有了 动态原型模式,把所有信息都装在构造函数内,而通过在构造函数内初始化原型(必要情况下),又保持了同时使用构造函数和原型的优点。换句话说,可以通过检查某个应该存在的方法是否有效,来决定是否需要初始化原型。下面来看看:
动态原型模式注意:这种模式内初始化原型时不能使用 对象字面量法重写原型,因为它不一定什么时候初始化,且会初始化多次,会切断之前实例与原型对象的联系。
到现在,创建对象的实现已经非常完美了。如果还对你不适用的话,你可以看看寄生构造函数模式。这种模式的基本思想是创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回新创建的对象。我们来看看:
寄生构造函数模式这个模式其实和工厂模式很像,但它用的是构造函数不是普通函数,因为作者希望像使用原生Array对象一样优雅的使用SpecialArray。构造函数在不返回值的情况下默认会返回新对象实例。而通过在构造函数的末尾添加一个return语句,可以重写 调用构造函数时返回的值。它可以在特殊情况下,用来为对象创建构造函数。假设我们想创建一个具有额外方法的特殊数组,由于不能直接修改Array构造函数,可以使用这个模式。
通过这个构造函数给原生Array数组加了一个方法注意:这种模式,返回的对象与构造函数或者构造函数的原型属性之间没有关系。也就是说,构造函数返回的对象与在构造函数外部创建的对象没有什么不同。为此,不能依赖instanceof操作符来确定对象类型。所以,我们不建议在可以使用其他模式情况下,不要使用这种模式。
稳妥构造函数模式
所谓稳妥对象,指没有公共属性,而且其它方法也不引用this的对象。稳妥对象最适合在一些安全的环境中(这些环境中会禁止使用this和new),或者防止数据被其他应用程序改动时使用。稳妥构造函数遵循与寄生构造函数类似的模式。但两者有不同,一是新创建对象的实例方法不引用this;二是不使用new操作符调用构造函数。按照稳妥构造函数的要求,可以将前面的Person构造函数重写。
在以这种模式创建的对象中,除了使用sayName()方法之外,没有其他方法访问name值。ES5创建对象 到这里就结束了!
用ES6的Class类语法创建对象更加简便,大家可以关注一下哦。
网友评论