创建多个对象的方案
- 如果我们现在希望创建一系列的对象:比如Person
- 包括张三、李四、王五、赵六等等,他们的信息都不相同
// 字面量方式
var p1 = {
name: "张三",
age: 18,
height: 177,
address: "深圳"
}
// new Object 方式
var p2 = new Object()
p2.name = "李四";
p2.age = 18;
p2.height = 177;
p2.address = "深圳"
上面这两种方式都有一个很大的弊端,就是如果要创建多个对象,需要写很多重复的代码。
工厂模式创建对象
function factoryObj(name, age, height, address){
var obj = new Object();
obj.name = name;
obj.age = age;
obj.height = height;
obj.address = address;
return obj;
}
var p1 = new factoryObj("zhangsan",17,177,"深圳")
var p2 = new factoryObj("lisi",17,177,"深圳")
工厂方法创建对象有一个比较大的问题:我们在打印对象时,对象的类型都是Object类型
构造函数创建对象
- 构造函数也是一个普通的函数,从表现形式来说,和千千万万个普通的函数没有任何区别
- 如果这么一个普通的函数被使用new操作符来调用了,那么这个函数就称之为是一个构造函数
function Person(name, age, height, address){
this.name = name;
this.age = age;
this.height = height;
this.address = address;
}
var p1 = new Person("zhangsan",17,177,"深圳")
var p2 = new Person("lisi",17,177,"深圳")
构造函数也是有缺点的,它在于我们需要为每个对象的函数去创建一个函数对象实例
对象的原型
- JavaScript当中每个对象都有一个特殊的内置属性[[prototype]],这个特殊的对象可以指向另外一个对象。
在JavaScript中,当我们通过引用对象上的key获取一个value时,会触发它的Get操作;这个操作首先会检查该对象是否存在对应属性,如果有就使用它;如果该对象没有该属性,那么会沿着它的原型[[prototype]]去查找。
var obj = {
name: "zhangsan"
}
// 获取对象原型的方式
// 方式1:__proto__是隐式原型,这个是早期浏览器自己添加的,存在一定的兼容性问题
console.log(obj.__proto__)
// 方式2:ES5之后提供的Object.getPrototypeOf
console.log(Object.getPrototypeof(p1))
函数的原型
- 所有的函数都有一个prototype的属性
function foo(){}
// 首先,函数也是一个对象,在它的身上也会有一个隐式原型__proto__
console.log(foo.__proto__)
// 函数它因为是一个函数, 所以它还会多出来一个显示原型属性: prototype,只有函数才会有显示原型prototype
console.log(foo.prototype)
new操作符的执行过程
- 创建一个空的对象
- 对象的__proto__(隐式原型) = 对象的prototype(显示原型)
function Person(){}
// 那么也就意味着我们通过Person构造函数创建出来的所有对象的[[prototype]]属性都指向Person.prototype
var p1 = new Person()
var p2 = new Person()
console.log(p1.__proto__ === p2.__proto__) // true
console.log(p1.__proto__ == Person.prototype) // true
- 执行代码
- 如果构造函数没有返回非空对象,则返回创建出来的新对象
创建对象的内存表现
// 根据上面的内存图我们可以知道
console.log(Person.prototype.constructor === Person)
console.log(p1.__proto__ === Person.prototype)
console.log(p1.__proto__.constructor === Person.prototype.constructor)
// 因为是循环引用
console.log(Person.prototype === Person.prototype.constructor.prototype)
constructor属性
- 默认情况下原型上都会添加一个属性叫做constructor,这个constructor指向当前的函数对象
function Person(){}
var p1 = new Person();
console.log(Person.prototype.constructor === Person)
console.log(p1.__proto__.constructor)
console.log(p1.__proto__.constructor.name)
重写原型对象
function Person(){}
Person.prototype = {
name: "zhangsan",
age: 18
}
- 每创建一个函数, 就会同时创建它的prototype对象, 这个对象也会自动获取constructor属性
- 我们这里相当于给prototype重新赋值了一个对象, 那么这个新对象的constructor属性,会指向Object构造函数,而不是Person构造函数了
function Person(){}
Person.prototype = {
name: "zhangsan",
age: 18
}
// 也就意味着 , 这样显然是不对的
console.log(Person.prototype.constructor === Object) // false
-
解决方案1
重写prototype,constructor指向不正确修复
function Person(){}
Person.prototype = {
constructor:Person,
name: "zhangsan",
age: 18
}
console.log(Person.prototype.constructor === Object) // false
console.log(Person.prototype.constructor === Person) // true
- 上面的方式虽然可以,但是也会造成constructor的[[Enumerable]]特性被设置了true.
-
默认情况下,原生的constructor属性是不可枚举的
-
- 解决方案2 重写prototype,constructor指向不正确修复,enumerable还原为默认值false
function Person(){}
Person.prototype = {
name: "zhangsan",
age: 18
}
Object.defineProperty(Person.prototype,"constructor",{
value:Person,
enumerable:false,
configurable:true,
writable:true,
})
// 查看所有
console.log(Object.getOwnPropertyDescriptors(Person.prototype))
// 查看单个
console.log(Object.getOwnPropertyDescriptor(Person.prototype,"constructor"))
- 这样的话,constructor和enumerable都是没有问题的。