上一篇文章提出了类和对象的概念,但是之前的类还有问题需要改进
改进思路
上一章Person构造函数 (类) 的问题
- 构造函数每执行一次,就会创建一个新的sayName方法
- 每个对象的sayName方法都是唯一的,100个对象就有100个sayName方法,且这些方法都一样,不合理
function Person(name, age) {
this.name = name;
this.age = age;
this.sayName = function () {
alert(this.name);
};
}
var p1 = new Person('悟空', 18);
var p2 = new Person('八戒', 28);
p1.sayName();
p2.sayName();
// 这里打印false 证明了两个对象定义的sayName方法不是同一个方法
console.log(p1.sayName == p2.sayName); // false
使所有对象共享同一个方法
- 将函数定义在全局作用域中,将这个函数赋值给
sayName
方法 - 这样构造函数执行100次,
sayName
方法也只有一个
function Person(name, age) {
this.name = name;
this.age = age;
this.sayName = fun;
}
// 将sayName方法定义在全局作用域中
function fun () {
alert(this.name);
}
var p1 = new Person('悟空', 18);
var p2 = new Person('八戒', 28);
p1.sayName();
p2.sayName();
// 这里打印true 证明了两个对象调用同一个sayName方法
console.log(p1.sayName == p2.sayName); // true
尽量不要在全局作用域中声明变量
- 将函数定义在全局作用域,污染了全局作用域的命名空间
- 而且函数定义在全局作用域中也很不安全,多人开发时容易被覆盖
将函数作为方法放在原型对象中
- 既可以确保函数只有一个,又不会影响全局作用域
- 这是最终方案,原型对象详见下方
原型对象prototype
原型对象的概念
- 我们所创建的每一个函数,解析器都会向函数中添加一个属性prototype
- 这个属性对应着一个对象,这个对象就是原型对象
- 如果这个函数作为普通函数调用,prototype没有任何作用
- 当这个函数当作构造函数调用时,它所创建的对象中都会有一个隐含属性,指向该构造函数的原型对象,我们可以通过
__proto__
来访问该属性
// 创建构造函数(类)
function Person() {
}
// 访问函数中的原型对象
console.log(Person.prototype); // {constructor: ƒ}
var p1 = new Person();
var p2 = new Person();
// 访问Person类的实例的原型对象
console.log(p1.__proto__); // {constructor: ƒ}
console.log(p2.__proto__); // {constructor: ƒ}
原型对象的用法
- 原型对象就像是一个公共的区域,所有同一个类的实例都可以访问到这个原型对象
- 我们可以将对象中共有的内容,统一设置到原型对象中
- 当我们访问一个对象的属性或方法时,会先在对象自身的属性中去找,如果有则直接使用,如果没有,则会取原型对象中寻找,如果找到则直接使用
// 创建构造函数(类)
function Person() {
}
// 向Person的原型中添加属性a
Person.prototype.a = 123;
// 向Person的原型中添加方法sayHello
Person.prototype.sayHello = function() {
alert('hello');
};
var p1 = new Person();
var p2 = new Person();
// 向p2对象中添加属性a
p2.a = 321;
// 访问属性a
console.log(p1.a); // 123
console.log(p2.a); // 321
// 访问方法sayHello (访问原型中的方法)
p1.sayHello(); // hello
p2.sayHello(); // hello
总结:
以后我们创建构造函数 (类) 时,可以将对象共有的属性和方法,统一添加到构造函数的原型对象中,这样不用分别为每一个对象添加,也不会影响到全局作用域,就可以使每个对象都具备这些属性和方法了
检查对象属性
对于js来说,对象中只有属性,只是当属性值为函数时,我们称之为方法
in检查中是否含有某个属性
使用in检查对象中是否含有某个属性时,如果对象中没有但是原型中有,也会返回true
hasOwnProperty检查对象自身是否含有某个属性
使用该方法只有对象自身中含有true时,才返回true
// 创建构造函数(类)
function Person() {
}
// 向Person的原型中添加属性a
Person.prototype.a = 123;
// 向Person的原型中添加方法sayHello
Person.prototype.sayHello = function() {
alert('hello');
};
var p1 = new Person();
var p2 = new Person();
// 向p2对象中添加属性a
p2.a = 321;
console.log('a' in p1); // true
console.log('a' in p2); // true
console.log('sayHello' in p1); // true
console.log(p1.hasOwnProperty('a')); // false
console.log(p2.hasOwnProperty('a')); // true
console.log(p1.hasOwnProperty('a')); // false
原型链
思考:hasOwnProperty
哪来的,上方代码中,对象是在调用hasOwnProperty
方法哦~ 说明是对象的属性,我们测试一下
下方代码证明 hasOwnProperty
在构造函数的原型对象的原型对象中
// 创建构造函数(类)
function Person() {
}
var p1 = new Person();
var p2 = new Person();
console.log(p1.hasOwnProperty('hasOwnProperty')); // false
console.log(p2.hasOwnProperty('hasOwnProperty')); // false
console.log('hasOwnProperty' in p1); // true
console.log('hasOwnProperty' in p2); // true
// 从上面4行代码看 hasOwnProperty 貌似写在构造函数的原型对象中 别急继续验证
// 从构造函数的原型对象中查,false表示没有,hasOwnProperty 不在构造函数的原型对象中
console.log(p1.__proto__.hasOwnProperty('hasOwnProperty')); // false
// 从构造函数的原型对象的原型对象中查 终于找到了
console.log(p1.__proto__.__proto__.hasOwnProperty('hasOwnProperty')); // true
难道原型对象是无限套娃? 测试一下
- 原型对象也是对象,所以它也有原型
- Object对象的原型中没有原型,如果在Object的原型中依然未找到,则返回null
// 创建构造函数(类)
function Person() {
}
var p1 = new Person();
// 从构造函数的原型对象的原型对象中查 终于找到了
console.log(p1); // Person
console.log(p1.__proto__); // Object
console.log(p1.__proto__.__proto__); // Object
console.log(p1.__proto__.__proto__.__proto__); // null (这里null 是因为__proto__属性值为null)
当我们使用一个对象的属性或方法时
- 原型对象也是对象,所以它也有原型
- 先在自身中寻找,自身如果有,则直接用
- 自身没有,则去原型对象中寻找,如果原型对象中有,则使用
- 如果没有,则去原型的原型中寻找,直到找到Object对象的原型
- Object对象的原型中没有原型,如果在Object的原型中依然未找到,则返回undefined