创建对象
1.通过new object()创建
var person = new Object()
person.name = 'Jack'
person.age = 18
person.sayName = function () {
console.log(this.name)
}
2.通过字母量创建
var person = {
name: 'Jack',
age: 18,
sayName: function () {
console.log(this.name)
}
}
3.工厂函数
function createPerson (name, age) {
return {
name: name,
age: age,
sayName: function () {
console.log(this.name)
}
}
}
生成实例对象
var p1 = createPerson('Jack', 18)
var p2 = createPerson('Mike', 18)
工厂函数改造
这样封装确实爽多了,通过工厂模式我们解决了创建多个相似对象代码冗余的问题,但却没有解决对象识别的问题(即怎样知道一个对象的类型)。
一种更优雅的工厂函数就是下面这样,构造函数:
function Person (name, age) {
this.name = name
this.age = age
this.sayName = function () {
console.log(this.name)
}
}
var p1 = new Person('Jack', 18)
p1.sayName() // => Jack
var p2 = new Person('Mike', 23)
p2.sayName() // => Mike
解析构造函数代码的执行
在上面的示例中,`Person()` 函数取代了 `createPerson()` 函数,但是实现效果是一样的。这是为什么呢?我们注意到,`Person()` 中的代码与 `createPerson()` 有以下几点不同之处:
没有显示的创建对象
直接将属性和方法赋给了 `this` 对象
没有 `return` 语句
函数名使用的是大写的 `Person`
而要创建 `Person` 实例,则必须使用 `new` 操作符。以这种方式调用构造函数会经历以下 4 个步骤:
1. 创建一个新对象
2. 将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象)
3. 执行构造函数中的代码
4. 返回新对象
构造函数和实例对象的关系
使用构造函数的好处不仅仅在于代码的简洁性,更重要的是我们可以识别对象的具体类型了。在每一个实例对象中的__proto__中同时有一个 `constructor` 属性,该属性指向创建该实例的构造函数:
console.log(p1.constructor === Person) // => true
console.log(p2.constructor === Person) // => true
console.log(p1.constructor === p2.constructor) // => true
对象的 `constructor` 属性最初是用来标识对象类型的,但是,如果要检测对象的类型,还是使用 `instanceof` 操作符更可靠一些:
console.log(p1 instanceof Person) // => true
console.log(p2 instanceof Person) // => true
总结:
构造函数是根据具体的事物抽象出来的抽象模板
实例对象是根据构造函数new出来的
每一个实例对象都具有一个 `constructor` 属性,指向创建该实例的构造函数
注意: `constructor` 是实例的属性的说法不严谨,具体后面的原型会讲到
可以通过实例的 `constructor` 属性判断实例和构造函数之间的关系
注意:这种方式不严谨,推荐使用 `instanceof` 操作符,后面学原型会解释为什么#### 构造函数的问题
构造函数的问题
使用构造函数带来的最大的好处就是创建对象更方便了,但是其本身也存在一个浪费内存的问题:
那就是对于每一个实例对象,`type` 和 `sayHello` 都是一模一样的内容,每一次生成一个实例,都必须为重复的内容,多占用一些内存,如果实例对象很多,会造成极大的内存浪费。
function Person (name, age) {
this.name = name
this.age = age
this.type = 'human'
this.sayHello = function () {
console.log('hello ' + this.name)
}
}
var p1 = new Person('lpz', 18)
var p2 = new Person('Jack', 16)
那就是对于每一个实例对象,`type` 和 `sayHello` 都是一模一样的内容,每一次生成一个实例,都必须为重复的内容,多占用一些内存,如果实例对象很多,会造成极大的内存浪费。
console.log(p1.sayHello === p2.sayHello) // => false
构造函数的问题解决
对于这种问题我们可以把需要共享的函数定义到构造函数外部:
function sayHello = function () {
console.log('hello ' + this.name)
}
function Person (name, age) {
this.name = name
this.age = age
this.type = 'human'
this.sayHello = sayHello
}
var p1 = new Person('lpz', 18)
var p2 = new Person('Jack', 16)
console.log(p1.sayHello === p2.sayHello) // => true
这样确实可以了,但是如果有多个需要共享的函数的话就会造成全局命名空间冲突的问题。
你肯定想到了可以把多个函数放到一个对象中用来避免全局命名空间冲突的问题:...
原型
更好的解决方案: `prototype`
Javascript 规定,每一个构造函数都有一个 `prototype` 属性,指向另一个对象。这个对象的所有属性和方法,都会被构造函数的实例继承。
这也就意味着,我们可以把所有对象实例需要共享的属性和方法直接定义在 `prototype` 对象上。
function Person (name, age) {
this.name = name
this.age = age
}
console.log(Person.prototype)
Person.prototype.type = 'human'
Person.prototype.sayName = function () {
console.log(this.name)
}
var p1 = new Person(...)
var p2 = new Person(...)
console.log(p1.sayName === p2.sayName) // => true
这时所有实例的 `type` 属性和 `sayName()` 方法,其实都是同一个内存地址,指向 `prototype` 对象,因此就提高了运行效率。
构造函数、实例、原型三者之间的关系
构造函数、实例、原型三者之间的关系
任何函数都具有一个 `prototype` 属性,该属性是一个对象。
function F () {}
console.log(F.prototype) // => object
F.prototype.sayHi = function () {
console.log('hi!')
}
构造函数的 `prototype` 对象默认都有一个 `constructor` 属性,指向 `prototype` 对象所在函数。
console.log(F.constructor === F) // => true
通过构造函数得到的实例对象内部会包含一个指向构造函数的 `prototype` 对象的指针 `__proto__`。
var instance = new F()
console.log(instance.__proto__ === F.prototype) // => true
`__proto__` 是非标准属性。
实例对象可以直接访问原型对象成员。
instance.sayHi() // => hi!
更简单的原型语法
我们注意到,前面例子中每添加一个属性和方法就要敲一遍 `Person.prototype` 。
为减少不必要的输入,更常见的做法是用一个包含所有属性和方法的对象字面量来重写整个原型对象:
更简单的原型语法存在的问题
在该示例中,我们将 `Person.prototype` 重置到了一个新的对象。
这样做的好处就是为 `Person.prototype` 添加成员简单了,但是也会带来一个问题,那就是原型对象丢失了 `constructor` 成员。
所以,我们为了保持 `constructor` 的指向正确,建议的写法是:
原生对象的原型:
....
原型对象的问题
共享数组
共享对象
如果真的希望可以被实例对象之间共享和修改这些共享数据那就不是问题。但是如果不希望实例之间共享和修改这些共享数据则就是问题。
一个更好的建议是,最好不要让实例之间互相共享这些数组或者对象成员,一旦修改的话会导致数据的走向很不明确而且难以维护。
原型对象使用建议
私有成员(一般就是非函数成员)放到构造函数中
共享成员(一般就是函数)放到原型对象中
如果重置了 `prototype` 记得修正 `constructor` 的指向
什么是继承?
现实生活中的继承;比如车 家产
程序中的继承
构造函数的属性继承:借用构造函数
构造函数的原型方法继承:拷贝继承(for-in)
另一种继承方式:原型继承