基于JS对象创建常用方式及原理分析
前言
俗话说“在js语言中,一切都对象”,而且创建对象的方式也有很多种,所以今天我们做一下梳理
最简单的方式
javascript创建对象最简单的方式是:对象字面量形式或使用object构造函数
对象字面量形式
var person = new object(); person.name = "jack"; person.sayname = function () { alert(this.name) }
使用object构造函数
var person = { name: "jack"; sayname: function () { alert(this.name) } }
明显缺点:创建多个对象时,会出现代码重复,于是乎,‘工厂模式'应运而生
工厂模式
通俗一点来理解工厂模式,工厂:“我创建一个对象,创建的过程全由我来负责,但任务完成后,就没我什么事儿了啊o(∩_∩)o哈哈~”
function createperson (name) { var o = new object(); o.name = name; o.sayname = function () { alert(this.name) } return o } var p1 = new createperson("jack");
明显缺点:所有的对象实例都是`object`类型,几乎类型区分可言啊!你说无法区分类型,就无法区分啊,我偏不信!那咱们就来看代码吧:
var p1 = new createperson("jack"); var p2 = new createperson("lucy"); console.log(p1 instanceof object); //true console.log(p2 instanceof object); //true
你看,是不是这个理儿;所以为了解决这个问题,我们采用‘构造函数模式'
构造函数模式
构造函数模式,就是这个函数我只管创建某个类型的对象实例,其他的我一概不管(注意到没有,这里已经有点类型的概念了,感觉就像是在搞小团体嘛)
function person (name) { this.name = name; this.sayname = function () { alert(this.name) } } function animal (name) { this.name = name; this.sayname = function () { alert(this.name) } } var p1 = new person("jack") p1.sayname() //"jack" var a1 = new animal("doudou") a1.sayname() //"doudou" console.log(p1 instanceof person) //true console.log(a1 instanceof animal) //true console.log(p1 instanceof animal) //false(p1显然不是animal类型,所以是false) console.log(a1 instanceof person) //false(a1也显然不是person类型,所以同样是false)
上面这段代码证明:构造函数模式的确可以做到对象类型的区分。那么该模式是不是已经完美了呢,然而并不是,我们来一起看看下面的代码:
//接着上面的代码 console.log(p1.sayname === a1.sayname) //false
发现问题了吗?`p1`的`sayname`竟然和`a1`的`sayname`不是同一个,这说明什么?说明‘构造函数模式'根本就没有‘公用'的概念,创建的每个对象实例都有自己的一套属性和方法,‘属性是私有的',这个我们可以理解,但方法你都要自己搞一套,这就有点没必要了
明显缺点:上面已经描述了,为了解决这个问题,又出现了一种新模式‘原型模式',该模式简直就是一个阶段性的跳跃,下面我们来看分一下‘原型模式'
原型模式
这里要记住一句话:构造函数中的属性和方法在每个对象实例之间都不是共享的,都是各自搞一套;而要想实现共享,就要将属性和方法存到构造函数的原型中。这句话什么意思呢?下面我们来详细解释
当建立一个构造函数时(普通函数亦然),会自动生成一个`prototype`(原型),构造函数与`prototype`是一对一的关系,并且此时`prototype`中只有一个`constructor`属性(哪有,明明还有一个`__proto__`呢,这个我们先不在此讨论,后面会有解释)
这个`constructor`是什么?它是一个类似于指针的引用,指向该`prototype`的构造函数,并且该指针在默认的情况下是一定存在的
console.log(person.prototype.constructor === person) //true
刚才说过`prototype`是`自动生成`的,其实还有另外一种手动方式来生成`prototype`:
function person (name) { this.name = name } person.prototype = { //constructor: person, age: 30 } console.log(person.prototype) //object {age: 30} console.log(person.prototype.constructor === person) //false
tips:为了证明的确可以为构造函数手动创建`prototype`,这里给`prototype`加了`name`属性。
可能你已经注意到了一个问题,这行代码:
console.log(person.prototype.constructor === person) //false
结果为什么是`false`啊?大哥,刚才的`prototype`是默认生成的,然后我们又用了另外一种方式:手动设置。具体分析一下手动设置的原理:
1.构造函数的`prototype`其实也是一个对象
2.当我们这样设置`prototype`时,其实已经将原先`person.prototype`给切断了,然后又重新引用了另外一个对象
3.此时构造函数可以找到`prototype`,但`prototype`找不到构造函数了
person.prototype = { //constructor: person, // 因为constructor属性,我没声明啊,prototype就是利用它来找到构造函数的,你竟然忘了声明 age: 30 }
4.所以,要想显示手动设置构造函数的原型,又不失去它们之间的联系,我们就要这样:
function person (name) { this.name = name } person.prototype = { constructor: person, //constructor一定不要忘了!! age: 30 }
画外音:“说到这里,你还没有讲原型模式是如何实现属性与方法的共享啊”,不要急,马上开始:
对象实例-构造函数-原型,三者是什么样的关系呢?
看明白这张图的意思吗?
1.当对象实例访问一个属性时(方法依然),如果它自身没有该属性,那么它就会通过`__proto__`这条链去构造函数的`prototype`上寻找
2.构造函数与原型是一对一的关系,与对象实例是一对多的关系,而并不是每创建一个对象实例,就相应的生成一个`prototype`
这就是原型模式的核心所在,结论:在原型上声明属性或方法,可以让对象实例之间共用它们
然后原型模式就是完美的吗?并不是,它有以下两个主要问题:
问题1:如果对象实例有与原型上重名的属性或方法,那么,当访问该属性或方法时,实例上的会屏蔽原型上的
function person (name) { this.name = name } person.prototype = { constructor: person, name: 'lucy' } var p1 = new person('jack'); console.log(p1.name); //jack
问题2:由于实例间是共享原型上的属性和方法的,所以当其中一个对象实例修改原型上的属性(基本值,非引用类型值或方法时,其他实例也会受到影响
原因就是,当实例自身的基本值属性与原型上的重名时,实例就会创建该属性,留着今后自己使用,而原型上的属性不会被修改;但如果属性是引用类型值,如:`array`、`object`,当发生重名时,实例是不会拷贝一份新的留给自己使用的,还是坚持实例间共享,所以就会出现上图中的情况
以上两个问题就是原型模式的明显缺点,为了改掉这些缺点,我们一般会采用一种组合模式“组合使用构造函数模式和原型模式”,其实在原型模式这一节,该模式已经有所应用了
组合使用构造函数模式和原型模式
这种模式可谓是集构造函数模式和原型模式之所长,用构造函数模式来定义对象实例的属性或方法,而共享的属性或方法就交给原型模式
function person (name) { this.name = name //实例的属性,在构造函数中声明 } person.prototype = { constructor: person, sayname: function () { //共享的方法存在原型中 alert(this.name) } }
注:此模式目前是ecmascript中使用最广泛、认同度最高的一种创建自定义类型的方法
-----------------
下面要介绍的几个模式是针对不同场景的,而不是说`组合使用构造函数模式和原型模式`有什么缺点,又用这几个模式来弥补,不是这样的
动态原型模式
特点:共享的方法是在构造函数中检测并声明的,原型并没有被显示创建
function person (name) { this.name = name; if (typeof this.sayname !== 'function') { //检查方法是否存在 console.log('sayname方法不存在') person.prototype.sayname = function () { alert(this.name) } } else { console.log('sayname方法已存在') } } var p1 = new person('jack'); //'sayname方法不存在' p1.sayname(); //因为sayname不存在,我们来创建它,所以这里输出'jack' var p2 = new person('lucy'); //'sayname方法已存在' p2.sayname(); //这时sayname已存在,所以输出'lucy'
当`person`构造函数第一次被调用时,`person.prototype`上就会被添加`sayname`方法;《javascript高级程序设计》一书说到:使用动态原型模式时,不能使用对象字面量重写原型。我们来理解一下:
分析:
1.`p1`实例创建,此时原型没有`sayname`方法,那我们就为原型添加一个
2.随后,我们以字面量的形式重写了原型,这时旧的原型并没有被销毁,而且它和`p1`还保持着联系
3.之后的实例,也就是这里的`p2`,都是与新原型保持联系;所以`p1`、`p2`有各自的构造器原型,即使它们的构造器是同一个
所以切记:当我们采用动态原型模式时,千万不要以字面量的形式重写原型
寄生构造函数模式
了解此模式之前,我们先来想一个问题:构造函数为什么要用`new`关键字调用?代码说话:
我们发现什么?如果不是`new`方法调用构造函数,那么就要显式的`return`,否则构造函数就不会有返回值;但如果使用`new`,那就没有这个问题了
下面我们再来看寄生构造函数模式:
function person (name) { var o = new object(); o.name = name; o.sayname = function () { alert(this.name) }; return o } var p1 = new person('jack'); //与工厂模式唯一不同之处:使用new调用 p1.sayname(); //jack
其实new不new都无所谓,因为我们已经显式的return o
那么寄生构造函数模式到底有什么应用场景呢?据《javascript高级程序设计》一书记载,举例:如果我们想创建一个具有额外方法的特殊数组,那么我们可以这样做:
function specialarray () { var values = new array(); array.prototype.push.apply(values,arguments); values.topipedstring = function () { return this.join('|') } return values } var colors = new specialarray('red','blue','green'); alert(colors.topipedstring()) //'red|blue|green'
最后重要的一点:该模式和构造函数和原型无缘,也就是不能区分实例类型,因为该模式生成的实例,它的构造函数都是object,原型都是object.prototype
稳妥构造函数模式
该模式与寄生构造函数相比,主要有两点不同:
1.创建对象实例的方法不引用this
2.不使用new操作符调用构造函数
按照稳妥构造函数的要求,可以将前面的person构造函数重写如下:
function person (name) { var o = new object(); o.sayname = function () { alert(name) //这里其实涉及到了闭包的知识,因此产生了私有属性的概念 } return o }
此模式最适合在一些安全的环境中(这些环境中会禁止使用this和new),同理,此模式与构造函数和原型也无缘
结语
以上就是对js中创建对象的方式的总结,希望对大家有所帮助
这篇基于js对象创建常用方式及原理分析就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持。
推荐阅读
-
基于JS对象创建常用方式及原理分析
-
Web开发基础之JS中创建对象的方式及对象使用
-
【Spring AOP源码】基于JDK动态代理和Cglib创建代理对象的原理分析
-
Node.js中的Buffer对象及创建方式
-
JS面向对象之最常用创建对象的方式
-
基于JS对象创建常用方式及原理分析
-
闭包的原理与经典应用场景,访问器属性,类与对象的创建与成员引用,数组与对象的解构过程与经典案例,JS引入到浏览器中的的方法及获取DOM元素的两个API
-
闭包的原理与经典应用场景,访问器属性,类与对象的创建与成员引用,数组与对象的解构过程与经典案例,JS引入到浏览器中的的方法及获取DOM元素的两个API
-
JS对象创建常用方式有哪些?
-
Web开发基础之JS中创建对象的方式及对象使用