- 首先是用工厂模式创建对象
做法是封装一个创建对象的函数
function createPerson(attr) {
var o = new Object()
o.name = attr.name
o.age = attr.age
o.speak = function() {
console.log('I am ' + this.name)
}
return o
}
var person1 = createPerson({name: 'a', age: 22})
但是这种做法的缺点是不能得知这个对象的类型
- 于是开始用 new 操作符后面跟构造函数来创建对象
function People(attr) {
this.name = attr.name
this.age = attr.age
this.speak = function() {
console.log(this.name)
}
}
var people1 = new People({name: 'a', age:22})
这时用 instanceOf 操作符可以得知 people1 是 People 类型
console.log( people1 instanceOf People ) // true
但是这种方法的缺点是每个 people 都会有一个 speak 的方法,而这个方法是一毛一样的,在计算机世界里这肯定是不行的,浪费了太多资源,所以要把这个方法抽象出来
那把这个方法提出来放到外面呢
function People(attr) {
this.name = attr.name
this.age = attr.age
this.speak = speak
}
let speak = function() {
console.log(this.name)
}
但是将只有某些对象要用的方法放到全局是一种污染
- 所以就要用到原型
每个函数也包括构造函数都有一个 prototype 属性指向原型对象,它的用途就是专门用来包含特定类型(某个构造函数生出来的那一类实例对象)共享的的属性和方法
个人简单理解为专门创建了一个对象来储存那类实例对象的公用方法和属性
function People(attr) {
this.name = attr.name
this.age = attr.age
}
People.prototype.speak = function() {
console.log(this.name)
}
var person1 = createPerson({name: 'a', age: 11})
var person2 = createPerson({name: 'b', age: 22})
person1.speak() // a
person2.speak() // b
上面的 person1 和 person2 为什么都可以使用 speak 方法
首先 person1 会在自己的作用域内找有没有 speak 这个方法,如果没有的话就会顺着一条无形的链找到它的原型作用域里面去。
这个例子里 person1 就已经找到了 speak 方法,然后运行,运行时 this 是指向 person1 自己的,而不是指向找到这个方法的原型;
也有时候可能在第一个原型里没有找到要执行的方法,就会接着找到这个原型的原型上去,直到为 null 或者为 Object。
每创建一个函数(包括构造函数)假设为 a,都会同时创建它的 prototype 属性,指向它的原型对象,这个原型对象假设为 b 也会自动获得 constructor 属性指向这个函数,和 proto 属性指向这个原型对象的原型对象
配合另一篇博文「重学前段(五)」一起食用效果会更好
即:以 构造函数 a 为例,生出实例为 c 对象,那么 c 对象就有一个 proto 属性指向 b 这个原型对象
简单理解,每个对象都有两个属性:
- constructor 属性指向构造它出来的构造函数
- proto 属性指向构造函数指向的原型对象
而每个函数有一个属性 prototype 指向原型对象
从而形成一个闭环,就可以找到彼此,这也是为什么能用 new 创建对象就能知道这个对象的类型
此处存疑:书上说改变构造函数的原型对象,实例对象的 constructor 属性也会被改变,但是跑代码发现没有改变
JS 就是用这种方式来实现继承和面向对象,和基于类的语言不一样。