前面我们讨论过了使用使用工厂模式创建对象,使用构造函数创建对象,再到使用原型模式创建对象。
我们发现了使用比较理想的构造函数来创建对象也会有问题,最后我们使用原型模式来创建对象。
一说到原型模式,原型对象肯定要有了解哦。下面我们一起来学习一下原型对象吧!
原型对象的作用:
接下来我们来学习一下原型对象的使用:
function Person() {
}
Person.prototype.name = 'alon';
Person.prototype.age = 29;
Person.prototype.job = 'web developer';
Person.prototype.sayName = function () {
alert(this.name);
}
var person1 = new Person();
person1.sayName();//"alon"
var person2 = new Person();
person2.sayName(); //"alon"
alert(person1.sayName == person2.sayName); //true
代码解析:
其实上面内容都是上一节最后讲的,就当复习了吧!接下来才是这一章节的重点,打起精神。
写在前面,要分清楚三个东西,构造函数(Person),原型对象,实例(person1、person2)。谁有什么谁干什么,分清楚。
原型对象的创建:
原型对象上的constructor属性:
新的实例创建时, 原型对象在哪里呢?
// 原型对象中自身带有一个属性: constructor属性
// 属性指向Person函数
console.log(Person.prototype.constructor); // Person函数
// 对象实例也有一个属性指向原型
console.log(person1.__proto__); // 原型对象
console.log(Person.prototype); // 原型对象
console.log(person1.__proto__ === Person.prototype); // true
我们通过一个图(很丑的手画图)来解释上面的概念:
再来通过一张比较正式的图来加深大家理解:
解析:
当你要获取对象中的属性时,对象搜索属性和方法的过程:
- 每当代码读取某个对象的某个属性时,都会执行搜索,也就是要找到给定名称的属性。
- 搜索首先从对象实例本身开始
- 如果在实例中找到了具有给定名字的属性,则返回该属性的值。
- 如果没有找到,则继续搜索指针指向的原型对象,在原型对象中查找具有给定名字的属性。
- 如果在原型对象中找到了这个属性,则返回该属性的值。
- 也就是说,在我们调用personl.sayName()的时候,会先后执行两次搜索。
可以通过__proto__来修改原型的值(通常不会这样修改, 知道即可)
补充说明:
var person1 = new Person()
var person2 = new Person()
person1.sayHello() // alon
person2.sayHello() // alon
// 给person1实例添加属性
person1.name = "Kobe"
person1.sayHello() // Kobe, 来自实例
person2.sayHello() // alon, 来自原型
通过hasOwnProperty判断属性属于实例还是原型。
alert(person1.hasOwnProperty("name")) // true
alert(person2.hasOwnProperty("name")) // false
简洁语法概述:
字面量重写原型对象:
// 定义Person构造函数
function Person() { }
// 重写Person的原型属性
Person.prototype = {
name: "alon",
age: 18,
height: 1.88,
sayHello: function () {
alert(this.name)
}
}
创建Person对象
var person = new Person()
console.log(person.constructor)
alert(person.constructor === Object) // true
alert(person.constructor === Person) // false
alert(person instanceof Person) // true
这里说一下 instanceof() :instanceof的普通的用法,obj instanceof Object 检测Object.prototype是否存在于参数obj的原型链上。
注意:
如果在某些情况下, 我们确实需要用到constructor的值, 可以手动的给constructor赋值即可。
// 定义Person构造函数
function Person() {}
// 重写Person的原型属性
Person.prototype = {
constructor: Person,
name: "alon",
age: 18,
height: 1.88,
sayHello: function () {
alert(this.name)
}
}
// 创建Person对象
var person = new Person()
alert(person.constructor === Object) // false
alert(person.constructor === Person) // true
alert(person instanceof Person) // true
上面的方式虽然可以, 但是也会造成constructor的[[Enumerable]]特性被设置了true。
// 定义Person构造函数
function Person() {}
// 重写Person的原型属性
Person.prototype = {
name: "alon",
age: 18,
height: 1.88,
sayHello: function () {
alert(this.name)
}
}
Object.defineProperty(Person.prototype, "constructor", {
enumerable: false,
value: Person
})
我们来看下面的代码会不会有问题:
// 定义Person构造函数
function Person() {}
// 创建Person的对象
var person = new Person()
// 给Person的原型添加方法
Person.prototype = {
constructor: Person,
sayHello: function () {
alert("Hello JavaScript")
}
}
// 调用方法
person.sayHello()
代码解析:
代码是不能正常运行的。 最初我们创建的person实例指向的是原来的原型对象, 原来的原型对象里没有sayHello()方法。实例创建出来之后,Person的prototype才指向了一个新的对象,在新的对象里才含有sayHello()方法。
原型对象也有一些缺点:
考虑下面代码的问题:
// 定义Person构造函数
function Person() {}
// 设置Person原型
Person.prototype = {
constructor: Person,
name: "alon",
age: 18,
height: 1.88,
hobby: ["Basketball", "Football"],
sayHello: function () {
alert("Hello JavaScript")
}
}
// 创建两个person对象
var person1 = new Person()
var person2 = new Person()
alert(person1.hobby) // Basketball,Football
alert(person2.hobby) // Basketball,Football
person1.hobby.push("tennis")
alert(person1.hobby) // Basketball,Football,tennis
alert(person2.hobby) // Basketball,Football,tennis
OK, 我们会发现, 我们明明给person1添加了一个爱好, 但是person2也被添加到一个爱好。
因为它们是共享的同一个数组。
但是, 我们希望每个人有属于自己的爱好, 而不是所有的Person爱好都相同。
那就有如下方法。
组合使用构造函数模式与原型模式。
构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。
每个实例都会有自己的一份实例属性的副本,但同时又共享着对方法的引用,最大限度地节省了内存。
另外,这种混成模式还支持向构造函数传递参数;可谓是集两种模式之长。
组合构造函数和原型模式的代码:
// 创建Person构造函数
// 创建Person构造函数
function Person(name, age, height) {
this.name = name
this.age = age
this.height = height
this.hobby = ["Basketball", "Football"]
}
// 重新Peron的原型对象
Person.prototype = {
constructor: Person,
sayHello: function () {
alert("Hello JavaScript")
}
}
// 创建对象
var person1 = new Person("alon", 18, 1.88)
var person2 = new Person("Kobe", 30, 1.98)
// 测试是否共享了函数
alert(person1.sayHello == person2.sayHello) // true
// 测试引用类型是否存在问题
person1.hobby.push("tennis")
alert(person1.hobby) //Basketball,Football,tennis
alert(person2.hobby) //Basketball,Football
这个就解决了使用引用类型的实例存在的问题。
向着宏大的目标努力前进~
文章参考借鉴:https://mp.weixin.qq.com/s/TeBnVpvb_sewv3np7TKc-Q