1. delete关键字
对象属性会屏蔽原型属性,通过delete
删除对象的属性,可以继续访问原型属性,达到解除屏蔽的目的,看如下代码:
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.friends = ['小明', '小刚'];
}
Person.prototype = {
constructor: Person,
sayName: function(){
console.log(this.name);
}
};
var p1 = new Person('张三', 18, 'JavaScript');
p1.sayName = function(){
console.log('没有名字');
}
p1.sayName();//没有名字
//删除对象属性,可以继续访问到原型上的属性
delete p1.sayName;
p1.sayName();//张三
如代码所示,给p1
添加了sayName
方法,屏蔽掉了原型上的sayName
,通过delete
关键字删除对象上的属性,又可以访问到原型上的属性了。
2. hasOwnProperty()
当给定属性存在于实例中时,返回true
,否则返回false
,看下面代码:
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.friends = ['小明', '小刚'];
}
Person.prototype = {
constructor: Person,
sayName: function(){
console.log(this.name);
}
};
var p1 = new Person('张三', 18, 'JavaScript');
var p2 = new Person('李四', 18, 'Java');
p1.sayName = function(){
console.log('没有名字');
}
console.log(p1.hasOwnProperty('sayName'));//true
console.log(p2.hasOwnProperty('sayName'));//false
如代码所示,给p1
添加了sayName
方法,通过hasOwnProperty
检测的结果为true
,没有给p2
添加,检测结果为false
。hasOwnProperty
只关心实例对象有没有这个属性,有就返回true
,没有就返回false
,不管原型有没有。
3. in关键字
只要通过实例对象能够访问到的属性,in
关键字就返回true
,怎么理解呢?看下面代码:
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.friends = ['小明', '小刚'];
}
Person.prototype = {
constructor: Person,
sayName: function(){
console.log(this.name);
}
};
var p = new Person('张三', 18, 'JavaScript');
console.log(p.hasOwnProperty('name'));//true
console.log(p.hasOwnProperty('sayName'));//false
console.log('name' in p);//true
console.log('sayName' in p);//true
delete p.name;
console.log(p.hasOwnProperty('name'));//false
console.log('name' in p);//false
如代码所示,hasOwnProperty
只有属性在实例中存在时才会返回true
。name
和sayName
通过实例都是可访问的,所以使用in
检测时结果为true
。in
关键字,只要通过实例能访问到属性就返回true
,也就是说,不管属性存在实例还是原型中,in
关键字都会返回true
,而hasOwnProperty
只有当属性存在实例中才会返回true
。
这样结合二者我们也可以自定义一个判断属性只存在原型中的方法了,如下代码:
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.friends = ['小明', '小刚'];
}
Person.prototype = {
constructor: Person,
sayName: function(){
console.log(this.name);
},
/*
* param: property, 类型:String, 描述:属性
* return: 类型:Boolean,描述:返回true,属性只存在原型中
* */
hasPrototypeProperty: function(property){
return (property in this) && !this.hasOwnProperty(property);
}
};
var p = new Person('张三', 18, 'JavaScript');
console.log(p.hasPrototypeProperty('name'));//false, name不存在原型中
console.log(p.hasPrototypeProperty('sayName'));//true, sayName只存在原型中
p.sayName = function(){
console.log('没有名字');
}
console.log(p.hasPrototypeProperty('sayName'));//false, sayName不是只在原型中存在
如代码所示,重写原型时添加了一个方法,用于判断属性是否只在原型中存在。首先使用in
关键字判断属性在实例或原型中存在,再使用hasOwnProperty()
排除掉在实例中存在的情况,这样结果返回true
时就表示该属性只在原型中存在了。
4. 枚举属性
4.1 for-in枚举
可以枚举出所有能够通过对象访问的、可枚举的属性。能够通过对象访问的属性好理解,那什么样的属性算可枚举的属性呢?目前读者只要记住所有开发人员定义的属性都是可枚举的就好了,如下代码所示:
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.friends = ['小明', '小刚'];
}
Person.prototype = {
constructor: Person,
sayName: function(){
console.log(this.name);
}
};
var p = new Person('张三', 18, 'JavaScript');
for(var pro in p){
console.log(pro);//name age job friends constructor sayName
}
4.2 Object.keys()方法
ES5新增的方法,这个方法接收一个对象作为参数,可枚举该对象本身所有可枚举的属性,不包括原型,见如下代码:
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.friends = ['小明', '小刚'];
}
Person.prototype = {
constructor: Person,
sayName: function(){
console.log(this.name);
}
};
var p = new Person('张三', 18, 'JavaScript');
console.log(Object.keys(p));//["name", "age", "job", "friends"]
console.log(Object.keys(Person));//[]
console.log(Object.keys(Person.prototype));//["constructor", "sayName"]
如代码所示,枚举的只是对象本身的属性,不是能通过对象访问的属性都可枚举,这点要注意和for-in
进行比较。
4.3 Object.getOwnPropertyNames()
这个方法可以枚举所有属性,无论是不是可枚举的,只有一个例外——__proto__
,见如下代码:
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.friends = ['小明', '小刚'];
}
Person.prototype = {
constructor: Person,
sayName: function(){
console.log(this.name);
}
};
var p = new Person('张三', 18, 'JavaScript');
console.log(Object.getOwnPropertyNames(p));//["name", "age", "job", "friends"]
console.log(Object.getOwnPropertyNames(Person));//["length", "name", "arguments", "caller", "prototype"]
console.log(Object.getOwnPropertyNames(Person.prototype));//["constructor", "sayName"]
5. 原生对象的原型
所有原生引用类型(Array
、String
、Date
等)都在其构造函数的原型上定义了方法。学习了原型的知识,我们也可以在原型上扩展方法,如下代码所示:
Date.prototype.addDay = function (count) {
return new Date(this.getFullYear(), this.getMonth(), this.getDate() + count);
}
var _date = new Date();
console.log(_date.addDay(2));//Mon Oct 29 2018 00:00:00 GMT+0800 (中国标准时间),后天
console.log(_date.addDay(-1));//Fri Oct 26 2018 00:00:00 GMT+0800 (中国标准时间),昨天
如代码所示,给Date
添加了一个加几天的方法,这样做很方便,但是原书中有这样一段话,“尽管可以这样做,但我们不推荐在产品化的程序中修改原生对象的原型。如果因某个实现中缺少某个方法,就在原生对象的原型中添加这个方法,那么当在另一个支持该方法的实现中运行代码时,就可能会导致命名冲突。而且,这样做也可能会意外地重写原生方法”。
虽然作者这样说,但是工作中依然会有人这么写,也确实方便,我就遇到过很多次,这个还是在工作中具体情况具体分析吧。
本文参考《JavaScript高级程序设计(第3版)》