JavaScript7种创建对象方式异同
今天在梳理小红书第六章内容,顺便记录一下把
1.工厂模式
function Person(name,age) { var o = new Object() o.name = name o.age = age return o } var person1 = Person('Nicholas',33)
console.log(person1 instanceof Person) //false
工厂模式的具体工作就是
var person1 = new Object()
person1.name = 'Nicholas'
person1.age = 33
和直接创建person1 并无多少差别,只是封装到了Person函数中,并没有差别
工厂模式问题
使用instanceof 操作符检查和 Person的关系 返回false
2.构造函数模式
function Person2(name,age) { this.name = name this.age = age } var person2 = new Person2('Hi',33) console.log(person2 instanceof Person2) //true
代码里 person2 instanceof Person2 返回true
主要问题是因为 new操作符 用代码解释比较清楚,请看下面代码
function Person2(name,age) { this.name = name this.age = age } function New(fn) { var n = {} n.__proto__ = fn.prototype return function () { var o = fn.apply(n,arguments) return o?o:n } } var person2 = New(Person2)('Alan',22) console.log(person2 instanceof Person2) //true
关于new操作符的具体工作可以参考 http://www.cnblogs.com/flawlessBlithe/p/8513950.html
这里在New操作符中有一步,是将创建的对象n也就是我们的person2的__proto__指向fn.prototype(Person2.protorype)。然后把构造函数中的属性
实例化到person2中,再把n给返回。就完成着一系列操作
构造函数模式的问题
构造函数中的每个方法都需要在实例中创造一边。==
另外提一下 小红书 关于数据类型一章有提到 通过构造函数创建对象和通过对象字面量创建对象是有差别的我们在这里如果把
Person构造函数命名成Object构造函数,就很容易发现其中问题,因为通过new 构造函数创造的实例对象是会通过作用域链进行查找的,
底层构造函数Object在最外面,所以会先搜索到我们定义的Object函数。而通过对象字面量创建的对象是不会通过作用域链进行查找额,
所以对象字面量创建对象性能也会好一点。
3.原型模式
function Person() { } Person.prototype.name = 'Nicholas' Person.prototype.age = 22 Person.prototype.sayName = function () { console.log(this.name) } var person1 = new Person() person1.sayName() //Nicholas var person2 = new Person() person2.sayName() //Nicholas console.log(person1.sayName === person2.sayName) //true person1.name = 'Alan' console.log(person1.name) //Alan delete person1.name console.log(person1.name) //Nicholas
这段原型模式创建函数的代码把方法添加到Person.prototype中,然后创建了两个实例jperson1,person2,后来在给person1实例添加name属性
读取的时候会先读取到实例中的那么属性,就不会去prototype中搜索了。当删除掉实例中的name属性的时候就会继续去原型中查找。
console.log(Object.getOwnPropertyDescriptor(person1,'name')) //undefined 不会搜索原型 console.log(Object.getOwnPropertyNames(person1)) //[]不会搜索原型 平时返回属性列表 var property = [] for(var item in person1){ //会搜索原型 property.push(item) } console.log(property) //["age", "sayName", "name"] var property2 = Object.keys(person1) // 不会搜索原型 console.log(property2) //[] </script>
顺便测试一下几个Object检查属性的方法, 发现keys,getOwnPropertyDescriptor和getOwnPropertyNames都不会去原型中搜索,fo-in枚举是会去原型中搜索的
当然如果你觉得这样向原型添加属性和方法比较麻烦,可以用下面的方法
function Person3() { } // Person3.prototype = { //Person3.prototype重新指向了新对象 // constructor:Person3, // 这一步会导致construction可枚举 // name:'Bob', // age:23, // sayName:function () { // console.log(this.name) // } // } Object.defineProperties(Person3.prototype,{ name:{ enumerable:true, writable:true, configurable:true, value:'Bob' }, age:{ enumerable:true, writable:true, configurable:true, value:22 }, sayNmae:{ value:function () { console.log(this.name) }, writable:true, configurable:true, enumerable:true } }) var person3 = new Person3() console.log(person3 instanceof Person3)//true console.log(person3.constructor === Person3) // true
原型模式问题
function Person4() { } Person4.prototype.colors = ['red','blue'] var person5 = new Person4() var person6 = new Person4() person5.colors.push('yellow') console.log(Person4.prototype.colors) //["red", "blue", "yellow"] console.log(person5.colors) //["red", "blue", "yellow"]
当通过实例来修改原型中的引用类型的时候,其他实例中也会被修改
4.组合使用构造函数模式和原型模式
构造函数模式和原型模式的利弊
构造函数可以给每个实例添加属性,但是不能共享方法。原型模式,可以共享方法,但是实例修改原型中的数据,又会对其他实例造成影响
我们就可以用这两种方式的优势来做一套比较完美的创建对象的方法
function Person5(name,age) { this.name = name this.age = age this.colors = ['red','yellow'] } Object.defineProperties(Person5.prototype,{ sayName:{ get:function () { return function () { console.log(this.name) } } } }) var person6 = new Person5('Good',33) var person7 = new Person5('Sky',44) console.log(person6.sayName === person7.sayName) //true console.log(person6.name === person7.name) //false
把实例方法添加到构造函数中,把公用的原型方法添加到原型中
5.动态原型模式
将组合模式进行了封装
function Person6(name,age) { this.name = name this.age = age if(typeof this.sayName != "function"){ //第一次sayName是undefined,会进判断,第二次就不会进判断了 Person6.prototype.sayName = function () { console.log(this.name) } } } var person8 = new Person6('earth',44) person8.sayName() //earth
动态原型模式在构造函数内部给原型添加共享的方法,并且只在第一次创建实例的时候添加
6.寄生构造函数模式
跟工厂模式并无差别,只是在创建实例的时候用了new操作符
这个模式可以在特殊情况下为对象创建构造函数。假设我们想创建一个具有额外方法的特殊数组。由于不能直接修改Array构造函数,那么可以用寄生构造函数
function SpecialArray() { var values = new Array() // 创建数组 values.push.apply(values,arguments) //添加值 values.toPipedString = function () { //添加方法 return this.join('|') } return values } var colors = new SpecialArray('red','blue','green') console.log(colors.toPipedString()) //red|blue|green
7.稳妥构造函数模式
道格拉斯,克罗克福德提出了稳妥对象的概念,就是说没有公共属性,方法也不引用this的对象。稳妥对象最适合在一些安全环境中(禁用new和this)
稳妥构造函数是和寄生构造函数类似的模式,但是有两点不同 :1 新创建对象的实例方法不用this。2 不使用new操作符构造函数
上代码 大家感受一下
function Person7(name,age) { var o = new Object() o.age = age o.name = name o.sayName = function () { console.log(name) } return o } var person9 = Person7('Alan',23)