俗话说“在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`上寻找