JavaScript对象是一个很有意思的数据类型,由于js没有类的概念,js对象就承担起JavaScript面向对象的重任。
JavaScript 并没有类的概念,但是它有对象。
ECMSscript将对象定义为无序属性的集合,其属性可以包含基本值、对象或者函数。
JavaScript的对象的属性没有严格的顺序,每个属性和方法都需有一个名字和对应的值,看起来和JSON基本没有区别(实际上js对象和JSON可相互转换)。
对象基本概念
对象的创建非常的简单>>>
显示申明
var obj = new Object()
obj.name = "tom"
obj.age = 21
obj.sayName = function(){return this.name}
**对象字面量**
var obj = {name:"tom",
age:21,
sayName:function(){return this.name}
}
ECMA-262第五版定义了内部采用的特性时(attribute), 描述了属性(property)
注:property是用来描述对象的,attribute是用来描述property的元数据,此处的attribute区别于HTML的attribute,HTML标签的attribute是标签本身所具有的描述数据,和property描述的对象(DOM)不同,也就是这两个的描述参照并不是同一基准
属性
为了支持JavaScript引擎工作,ECMAScript5规定了属性的类型:数据属性和访问器属性。
数据属性
数据属性:数据属性包含一个数据值的位置,在这个位置可以读取和写入值。数据属性有四个描述其行为的特性。
[[Configurable]]:是否能够对属性进行其他更改,包括删除,修改等
[[Enumerable]]:是否能通过for-in循环返回属性
[[Writable]]:如字面意思,能否写入值(修改值)
[[Value]]:包含属性的数据值,代表属性值得存储位置
四个特性默认值为,true,true,true,undefined
修改属性默认特性:Object.defineProperty(obj,propertyName,attrConfig)
该方法接收三个参数:修改的对象,修改的属性值名称,该属性的特性描述符对象
例如:
var Car = {}
Object.defineProperty(Car,"price",{
writeable:false,
value:120000
})
注意:运用Object.defineProperty()产生的属性的特性默认为false,false,false,undefined
所以上面的例子中Car的price属性中,configurable默认为false,enumerable默认为false
直接通过对象创建的属性并无此限制
访问器属性
访问器属性就是我们在其他语言中常看到的getter和setter函数,访问器属性并不包含值,它也有四个特性
[[Configurable]]:是否能够对属性进行其他更改,包括删除,修改等
[[Enumerable]]:是否能通过for-in循环返回属性
[[Get]]:读取数据时调用的函数,默认为undefined
[[Set]]:写入数据时调用的函数,默认为undefined
例如:
var people = {
name:"tom",
_age:17,
adult:false
}
Object.defineProperty(people,"age",{
get:function(){
return this._age
},
set:function(val){
this._age = val
if(val > 18){
this.adult = true
}
}
})
定义多个属性
要想定义一个属性就调用一次definProperty方法会非常繁琐,ES5就存在方法defineProperties支持多属性定义,只是传入的参数有些许区别
例如:
Object.defineProperties("people",{
name:{
writable:false,
value:"Amy"
},
_age:{
get:function(){}
set:function(){}
}
})
读取属性的特性
使用ECMAScript5的Object.getOwnPropertyDescriptor(),可以取得给定属性的描述符
参数:包含待查属性的对象,待查属性的名称
例如:
var perple = {}
Object.defineProperty(perple,"name",{
writable:false,
value:"Jack"
})
var result = Object.getOwnPropertyDescriptor(people,"name")
console.log(result.value)
console.log(result.writable)
原型对象
对象的创建往往是重复的,就好像创建一个学生对象并不能完成所有工作,如果牵扯到班级的概念,可能就要创建班级中所有学生的对象了,对象也有很多的重复性,比如一个班的人所在班级学校都是一样的,是共有的,为了简化代码,也就有了原型对象的概念。
注:对象创建中,每一个学生代表一个实例,这和其他OO(面向对象)语言的类具有相似的理念。
我们创建的每一个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途。就是可以包含那些由特定类型的实例共享的属性和方法。通过字面意思,prototype就是通过调用构造函数而创建的那个对象的原型对象。而原型对象也有一个指针constructor指向他所代表的构造函数。例如:Person.prototype.constructor == Person
原型对象就像是一个班级的标签,实例就是班级的学生,不管这个班级来了多少个新学生,只要这个班级叫一年级,那么这个班级的学生就都是一年级学生。原型对象包含所有实例公共的属性为所有实例共享。
创建了自定义的构造函数之后,其原型对象默认只会取得 constructor 属性;至于其他方法,则都是从 Object 继承而来的。当调用构造函数创建一个新实例后,该实例的内部将包含一个指针(内部属性),指向构造函数的原型对象。ECMA-262 第 5 版中管这个指针叫 [[Prototype]] 。虽然在脚本中没有标准的方式访问 [[Prototype]] ,但 Firefox、Safari 和 Chrome 在每个对象上都支持一个属性__proto__
;而在其他实现中,这个属性对脚本则是完全不可见的。不过,要明确的真正重要的一点就是,这个连接存在于实例与构造函数的原型对象之间,而不是存在于实例与构造函数之间。
function Person(){}
Person.prototype.name="Nicholis"
Person.prototype.age=29
Person.prototype.job="Software Engineer"
Person.prototype.sayName=function(){
console.log(this.name)
}
var person1 = new Person()
person1 .sayName() // Nicholis
var person2 = new Person()
person2 .sayName() // Nicholis
console.log(person1 .sayName == person2 .sayName) // true
需要注意的几点:
- 构造函数的prototype指针指向该特定类型对象(Person)的原型对象,同时该对象的constructor指正指向该构造函数。
- 实例的[[prototype]]并非构造函数的prototype指针,在主流浏览器中的实际实现是
_proto_
指针,指向构造函数的原型对象。 - 实例与构造函数并无直接关系
原型对象属性和实例属性
原型对象属性说不定也有和实例属性冲突的时候,比如运动会的时候大多数人在开幕式的任务就是走队列喊口号,但是领头的人喊的口号和班里其他人很有可能不同。
领头:一年三班
同学:团结一心
领头一年三班
同学:共创佳绩
在JS中,发生了冲突该如何选择,和现实中也是相差无几的。
function Class13(){}
Class13.prototype.sentence= "永争第一";
Class13.prototype.say = function(){
alert(this.sentence);
};
var student = new Class13(); // 大多数同学
var leader= new Class13(); // 领头人
leader.sentence= "一年三班";
alert(leader.say ()); //"一年三班" —— 来自实例
alert(student .say ()); //"永争第一" ——原型
以上代码不难看出,student实例没创建实例属性,便输出了原型对象属性,而设置了实例属性的leader输出的是实例属性,领头人在作为同学的基础上口号是“永争第一”,但是新的任务让他产生了新的属性,屏蔽了之前的设置。
当为对象实例添加一个属性时,这个属性就会屏蔽原型对象中保存的同名属性;换句话说,添加这个属性只会阻止我们访问原型中的那个属性,但不会修改那个属性。即使将这个属性设置为 null ,也只会在实例中设置这个属性,而不会恢复其指向原型的连接。
更简单的原型语法
每一次都要书写xxx.prototype着实让人心烦,还好我们有更简便的方法——字面量
function Person(){}
Person.prototype = {
name : "Nicholas",
age : 29,
job: "Software Engineer",
sayName : function () {
alert(this.name);
}
};
但是用字面量的方法hi重写整个原型对象,导致的直接后果就是constructor指针不再是默认指向构造函数,而是Object
解决办法一:在字面量中定义constructor
function Person(){}
Person.prototype = {
constructor : Person,
name : "Nicholas",
age : 29,
job: "Software Engineer",
sayName : function () {
alert(this.name);
}
};
问题:constructor默认数据属性enumerable会设置为true
解决办法二:通过Object.defineProperty()重新定义constructor
Object.defineProperty(Person.prototype, "constructor", {
enumerable: false,
value: Person
});
原型对象的问题
- 省略了为构造函数传递初始化参数这一环节,结果所有实例在默认情况下都将取得相同的属性值。
- 原型对象包含引用类型值的属性,会使得实例对该属性的操作产生连锁反应。
function Person(){
}
Person.prototype = {
constructor: Person,
name : "Nicholas",
age : 29,
job : "Software Engineer",
friends : ["Shelby", "Court"],
sayName : function () {
alert(this.name);
}
};
var person1 = new Person();
var person2 = new Person();
person1.friends.push("Van");
alert(person1.friends); //"Shelby,Court,Van"
alert(person2.friends); //"Shelby,Court,Van"
alert(person1.friends === person2.friends); //true
以上代码反映了引用类型在原型对象中的问题:当实例直接对引用类型进行方法操作时,将不会为实例创建同名实例属性,而是寻址,找到friends数组的地址,再进行push操作,这就导致了实例改变了原型对象的值,使得其他实例也会取得改变后的friends。
单纯的将引用类型进行赋值操作,那就不会有以上问题,因为这个过程中并没有寻址这个环节,而直接进行了实例属性申明和赋值。如下:
var person1 = new Person();
var person2 = new Person();
person1.friends=["hello"];
console.log(person1.friends); //"hello"
console.log(person2.friends); //"Shelby,Court,Van"
console.log(person1.friends === person2.friends); //false
相关方法
isPrototypeOf()
用途:判断实例与目标是否含有原型对象关系,即判断对象是否是某实例的原型对象
alert(Person.prototype.isPrototypeOf(person1)); //true
alert(Person.prototype.isPrototypeOf(person2)); //true
Object.getPrototypeOf()
用途:获取实例的原型对象
alert(Object.getPrototypeOf(person1) == Person.prototype); //true
alert(Object.getPrototypeOf(person1).name); //"Nicholas"
hasOwnProperty()
用途: 检测一个属性是存在于实例中。
function Person(){}
Person.prototype.name = "Tom";
var person1 = new Person();
var person2 = new Person();
person2.name = "Amy"
alert(person1.hasOwnProperty("name")); //false
alert(person2.hasOwnProperty("name")); //true
alert(person2.hasOwnProperty("nam")); //false
注:该方法结合操作符 in 可判断属性来自实例还是原型
in操作符会判断给定属性是否能通过对象访问,如果能,返回true,不能,返回false
结合hasOwnProperty()就很好判断属性来源了
function hasPrototypeProperty(object, name){
return !object.hasOwnProperty(name) && (name in object);
}
alert(hasPrototypeProperty(person1, "name")); //true
alert(hasPrototypeProperty(person2, "name")); //false
Object.keys()
用途:要取得对象上所有可枚举的实例属性。接收一个对象作为参数,返回一个包含所有可枚举属性的字符串数组。
function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
alert(this.name);
};
var keys = Object.keys(Person.prototype);
alert(keys); //"name,age,job,sayName"
var p1 = new Person();
p1.name = "Rob";
p1.age = 31;
var p1keys = Object.keys(p1);
alert(p1keys); //"name,age"
Object.getOwnPropertyNames()
用途:得到所有实例属性,无论它是否可枚举。
ar keys = Object.getOwnPropertyNames(Person.prototype);
alert(keys); //"constructor,name,age,job,sayName"
// constructor 不可枚举