对象简介
-
对象的概念:一组"键值对"(key-value)的集合,无序的数据集合.(键值对是指"键名:键值"这种一对对的形式,其中键名(又称属性,成员)要遵守一定的命名和书写规则;键值可以是js的任何一种数据类型,这包括原始值primitive如number,string,boolean等,也包括一般对象object,函数Function,数组Array)
[说明:键名/键值,属性名/属性值,成员/成员值都是指的同一个东西].- 键名/键值:形式结构上的描述
- 属性名/属性值:css中的概念
- 成员/成员值:java中的概念
-
键名/属性名的规则:
- 对象的属性名都是字符串(ES6引入Symbol值也可作为键名)
- 不是字符串的,自动转为字符串
- 如果属性名不符合标识符条件(例如:包含空格),必须加上引号,否则会报错
注:js标识符命名规则- js中标识符可以由 字母、数字、下划线、$ 组成,但不能是保留字;
- js中标识符只能由 字母、下划线、$ 开始,而不能由数字开始;
- js中标识符区分大小写
-
对象的属性(property):(详细内容,参照后面的章节)
- 各个属性(键值对)之间用逗号","隔开,最后一项的逗号可加也可不加
- 属性分类:数据属性(data property),访问器属性(accessor property),内部属性(internal property)
- 各个属性又有各自的特性(attribute)与属性描述符对象(property descriptor)
-
函数的参数的传递方式:原始类型按值传递,复合类型按引用传递(对象的引用:该对象所在的内存地址,即"指向对象的指针")
-
属性的操作:
-
属性的读取与赋值:
- 点号".":obj.属性名(注意:数字键名不能用此种方法,点号会被当成小数点)
- 方括号"[]":obj[‘属性名’],方括号内的两个单引号不能省略.并且方括号之内还可以进行字符串处理.例:obj[‘on’+‘click’]
-
属性(名)查看:Object.keys(obj)方法(不能查看属性值,只能查看含有哪些属性)
-
删除对象本身的属性:delete命令
- 格式:
delete obj.属性名;
- 删除成功返回true;该属性不得删除,返回false.(见后续章节)
- delete命令不能删除继承属性/原型属性
- 格式:
-
验证属性存在性:in 运算符
- 格式:
‘字符串’ in obj;
- 检查对象的键名而不是键值
- 不区分本身属性与继承属性
- 格式:
-
判断属性是否为本身属性:
obj.hasOwnProperty(‘字符串’);
-
遍历可enumerable的属性:for…in循环
格式:for (var i in obj) {…}
-
操作同一对象的多个属性:with语句
with(obj) {a=1;b=2;}
等同于obj.a=1;obj.b=2;
[注意:with语句操作的必须是对象已经拥有的属性.否则就会创建一个仅在with语句的作用域内有效的新属性.with语句结束后,该属性又变成undefined.正因为如此,我们不推荐使用with语句]
-
对象属性详解
-
属性分类:
- 数据属性(data property):一般的键值对
- 访问器属性(accessor property):不保存值,但可对访问和赋值操作的实际过程进行自定义
- 内部属性(internal property):一般无法直接访问,需要借助相应的函数
-
数据属性(data property):
-
就是我们说的一般的属性,它保存一个值,要么是数据本身(原始类型),要么是数据的引用(对象).
-
数据属性的访问与赋值操作的实际过程是默认的,无法修改
-
数据属性的特性(property attributes):
- 属性(property)与特性(attribute)是两个不同的概念
- 属性的特性(attribute)是对属性本身能够进行的行为的约束
- 数据属性的特性有如下几种:
-
属性特性不能直接进行修改,必须使用专门的方法Object.defineProperty(obj,‘属性名’,属性描述符对象)
注:属性描述符,或者说属性描述符对象是指将要修改的特性与修改值,以键值对的形式书写,并用对象封装.例如{configurable:false,writable:false}
-
与属性描述符相关的函数:
- Object.defineProperty(obj, ‘属性名’, 属性描述符)
- Object.defineProperties(obj, 属性-描述符键值对对象)
这里的 属性-描述符键值对对象 是指如下形式的封装对象:
{属性名1:描述符对象1 , 属性名2:描述符对象2,…} - Object.create(proto, 属性-描述符键值对对象)
- Object.getOwnPropertyDescriptor(obj, 属性名):返回特定属性的属性描述符对象;如果不存在该属性,返回undefined.
-
-
访问器属性(accessor property):
-
访问器属性不包含数据值,他们有一对儿getter和setter函数(这两个函数不是必须的,保存在其属性特性Set和Get中)。允许用户在赋值或取值都经过预先设定的函数,从而实现内部属性的那一种特殊效果.
读取访问器属性时,会调用getter函数,这个函数负责返回有效的值。 在写入访问器属性时,会调用setter函数,这个函数负责决定如何处理数据。 -
属性特性:(前两个与数据属性相同,后两个不同)
小结:因为不保存值,所以没有Value和Writable特性,Set和Get取而代之. -
访问器属性的创建和修改:不能直接修改,需要用到特定函数Object.defineProperty(obj,‘属性名’,属性描述符对象)或者Object.defineProperties(obj, 属性-描述符键值对对象)
-
-
内部属性(internal property):
- 一些属性仅仅在规范之中用到,它们被称为"内部属性"是因为它们不能通过js直接访问,但也确确实实影响着代码.内部属性的书写都是包含在双方括号([[]])之中的.
- 所有对象共有的内部属性:
更多相关资料,请参考:ECMA5.1
重要的内部属性详解:
-
[[prototype]]:存放当前对象的原型对象的引用
1.函数与函数的原型对象(prototype object):- 在JavaScript中,创建一个函数A(就是声明一个函数), 浏览器就会在内存中创建一个对象B,而且该函数默认会有一属性 prototype 指向这个对象(即:prototype属性的值)
- 这个对象B就是函数A的原型对象,简称函数的原型。这个原型对象B也默认会有一个属性 constructor 指向了这个函数A (即:constructor属性的值是函数A)
- 凡是以函数A为构造函数而创建的对象C,D,E等等,也都有一个内部的[[prototype]]属性,也指向这个对象B.
2.要点一:构造函数的prototype的属性与其实例对象的[[prototype]]属性的区别.
前面的内容提到"内部属性"是不能直接访问到的,需要借助某些函数.而构造函数的prototype属性不是内部属性,而是普通属性;但是其实例对象的[[prototype]]确是内部属性,直接obj.prototype总是返回undefined.
代码:
function Test() {}
var t1 = new Test();
console.log(Test.prototype);
console.log(t1.prototype);
运行结果:
3.要点二:任何对象都有构造函数,都有原型对象
对象可以分为字面量对象,new出来的对象和函数对象.显然,new出来的对象都是有构造函数和原型对象的.-
字面量对象
代码:
var square = {length: 10};
var pro=Object.getPrototypeOf(square);
console.log(pro);
console.log(pro==Object.prototype);
运行结果:
因此,字面量对象的原型是Object.prototype,其构造函数为Object(). -
函数对象:
代码:
function Test(){}
var proF=Object.getPrototypeOf(Test);
console.log(proF);
console.log(proF==Function.prototype);
运行结果:
因此,函数对象的原型是Function.prototype,构造函数为Function().
至此,我们可以得出开头的结论.
4.要点三:任何函数都可以作为构造函数,但不一定都能对其实例对象初始化.
例子1:
代码:
function Test2(x,y){
length=x;
name=y;
}
var T2 = new Test2(1,‘peter’);
console.log(Object.getPrototypeOf(T2)==Test2.prototype);
console.log(T2.length);
运行结果:
例子2:
代码:
function Test3(x,y){
this.length=x;
this.name=y;
}
var T3 = new Test3(1,‘peter’);
console.log(Object.getPrototypeOf(T3)==Test3.prototype);
console.log(T3.length);
运行结果:
5.我们约定,凡是构造函数,其函数名首字母必须大写. -
进一步探讨内部属性
由上面的要点一,我们可以看出,prototype属性像是构造函数的私有属性(类似java的私有成员).构造函数自身可以直接访问,但其实例对象不能直接访问,需要借助在构造函数里定义的"特权"函数才能间接访问.
我们再把我们的视野移到整个内部属性集合,像[[Class]],[[Extensible]]等.内部属性 访问方法 [[Prototype]] Object.getPrototypeOf(obj) , obj.__proto__(访问器属性,类似于方法) [[class]] Object.prototype.toString.call(obj) [[Extensible]] Object.isExtensible , Object.preventExtensions
那么,我们如何自定义一个函数的私有属性呢?
在js中,我们通常通过闭包来实现私有属性.
代码:
function Student(num,str){
var grade = num;
var name = str;
this.getGrade = function (){
return grade;
}
this.getName = function(){
return name;
}
}
var s = new Student(98,‘peter’);
console.log(s);
console.log(s.grade);//undefined,不能直接访问私有属性
s.name = ‘selena’;//对象新建了一个自身属性name,不是私有属性[[name]]
console.log(s.name);//selena,访问自身属性
console.log(s.getName());//peter,通过特权方法getName()访问私有属性
运行结果:
这种实现的优缺点总结:
1.私有属性和特权函数只能在构造函数中创建
2.闭包的使用增加了资源占用,可能会导致一些问题
对象的保护
-
阻止对象的扩展(Preventing Extension)
效果:禁止向对象添加新属性,仍可以删除已有属性
实现途径:Object.preventExtensions(obj)
检查是否已阻止:Object.isExtensible(obj) -
封闭对象(Sealing)
效果:禁止添加新属性,全部已有属性"unconfigurable"(不可更改属性的特性值,不可删除自身属性),但基于历史原因,仍可以将writable改为read-only.
实现途径:Object.seal(obj)
检查对象是否已封闭:Object.isSealed(obj) -
冻结对象(Freezing)
效果:使所有属性变为只读,同时不可扩展(添加新属性),不可修改属性特性,不可删除属性
实现途径:Object.freeze(obj)
检查对象是否已冻结:Object.isFrozen(obj)
严格模式
-
严格模式是ECMAScript的一部分.可以灵活切换,可以是整个文件,也可以仅在一个函数内
-
启用方式:
整个文件----将’use strict’放在在文件开始处
某个函数----将’use strict’放在在函数开始处 -
对安全性的需求是严格模式使用的主要动机
-
严格模式特征:
- 安全性:极严格地限制eval()的使用,with语句禁止使用
- 全局变量使用之前必须被明确地声明(不会再自动生成),这有助于预防输入上的错误
- 未使用new调用构造函数时:
在严格模式出现之前,this是绑定到全局变量的,这导致会有属性被添加到这个对象.
严格模式中,如果构造函数不是经new调用的,那么this是绑定到undefined的,而且通常会有一个exception被抛出.
-
烦人的报错:尝试修改一个只读的属性的值或者删除一个不可配置的属性,会丢出exception.
-
不再支持八进制数:严格模式中,以0开头的数不会再被理解成八进制,0100就是十进制的100,而不是八进制的64.
-
参数对象:arguments.callee和arguments.caller属性被排除掉(基于安全原因:这样使得他们不被外部代码发现)
-
函数参数:禁止出现与形参同名的参数或变量名.