学习笔记=>《你不知道的JavaScript(上卷)第二部分》第三章:对象
语法:
对象可以通过两种形式定义:①声明(文字)形式。②构造形式:
//声明形式 var obj = { key:value }; //构造形式 var obj = new Object(); obj.key = value;
实际上,这两种形式生成的结果都是一样的。只是使用上声明形式更简洁。
类型:
在JavaScript中一共有六种主要类型:number,string,boolean,null,undefined,object;(术语是“语言类型”)。
其中number,string,boolean,null,undefined为简单基本类型,他们本身并不是对象。
注:null有时候有被当做对象类型,但是其实这是语言bug,实际上,null本身是基本类型。
实际上,JavaScript中有许多特殊的对象子类型,我们可以称为复杂的基本类型,函数就是一个对象子类型,JavaScript中的函数是
“一等公民”,是因为它的本质和普通对象是一样,所以可以像操作普通对象一样操作函数。
内置对象:
JavaScript中有一些对象子类型,通常被称为内置对象。有些内置对象的名字看上去可基本类型一样,但是实际上它们的关系更复杂。
● Array
● String
● Number
● Function
● Object
● Boolean
● Date
● RegExp
● Error
这些内置对象在表现上很像其他语言中的类型(type)和类(class),但是在JavaScript中,它们其实就是一些内置函数,这些函数可
以当做构造函数来使用,从而可以构建一个对应子类型的新对象。
//创建一个原始字符类型变量 var strPrimitive = 'hello world'; //使用构造函数创建一个复杂基本类型字符串对象 var strObject = new String('hello world'); console.log(typeof strPrimitive,typeof strObject); //string,object console.log(strPrimitive instanceof String,strObject instanceof String); //false,true console.log(Object.prototype.toString.call(strObject)); //[object String]
原始值‘hello world’并不是一个对象,它只是一个字面量,并且是一个不可变的值,如果要在这个值上面执行一些操作,比如获取长度等,
都需要先将其转换成String对象。在必要时,语言会自动把字符串字面量转换为String对象,也就是说,你并不需要显示创建一个对象。
var str = 'hello'; console.log(str.length); //5 //之所以可以直接在字面量上面使用方法和属性是因为引擎自动将字面量装换为String对象,就可以直接使用属性和方法了
属性描述符:
在ES5之前,JavaScript语法并没有提供可以直接检测属性特性的方法,比如判断属性是否可读,ES5开始,对象所有的属性都具备属性描述符。
var obj = {a:111}; console.log(Object.getOwnPropertyDescriptor(obj,'a')); //{value:111,writable:true,configurable:true,enumerable:true}
上面是一个普通对象中属性的属性描述符,可以看到返回的对象中有是个属性:
① value:111 ==> 属性的值
② writable:true ==> 可写入,true为可写,false为只读
③ configurable:true ==> 可配置,true为可配置,false为不可配置
④ enumerable:true ==> 可枚举,true为可枚举,false为不可枚举
我们平常创建普通对象的时候,属性描述符默认配置就是上面例子中的配置。也可以通过Object.defineProperty来定义属性描述符。
属性描述符详解:
1,writable:决定是否可以修改属性的值。
var obj = { name:'张三', sex:'男', age:35, describe:'is good' }; Object.defineProperty(obj,'age',{ writable:false, //设为只读 enumerable:true, configurable:true }); //修改失效 obj.age = 45; console.log(obj.age); //35
将属性设置为只读的时候,后面对属性值的修改会静默失败,严格模式下会报错。
2,configurable:只要属性是可配置的,就可以通过Object.defineProperty来修改属性描述符。
var obj = { name:'张三', sex:'男', age:35 };
Object.defineProperty(obj,'age',{ writable:true, enumerable:true, configurable:false //将属性设置为不可配置 }); obj.age = 45; console.log(obj.age); //45 //设置为不可配置后,任可以修改属性值 Object.defineProperty(obj,'age',{ writable:true, enumerable:true, configurable:true //再次修改属性可配置性为可配置 }); //TypeError: Cannot redefine property: age
将属性设置为不可配置后,任然可以修改它的值,但是如果再次修改其属性描述时,会报错(无论是否在严格模式下)。
因此可以看出,configurable的修改是单向的,无法撤销,同时不可删除这个属性。
注:将configurable设置为false时,有个小小的例外=>如果当时writable为true的时候,在不可配置的情况下任然
可以将writable修改为false,之后就不可再修改。
这个例子中的delete操作符只是用来删除一个对象属性。如果这个对象的属性刚好是某个对象/函数的最后引用者,这个时候删除这个属性后,垃圾回收机制可以回收那个对象。
3,enumerable:控制属性是否出现在可枚举列表中。
var obj = { name:'张三', sex:'男' }; Object.defineProperty(obj,'age',{ value:30, writable:true, enumerable:true, configurable:true }); for(let i in obj){ console.log(i); //name,sex,age } Object.defineProperty(obj,'age',{ value:30, writable:true, enumerable:false, configurable:true }); for(let i in obj){ console.log(i); //name,sex }
不变性:
有时候我们会希望对象是不可变的,但是很重要的一点是所有的方法创建的都是浅不变形,也就是说,它们之后影
响目标对象和它的直接属性,如果它的属性再引用其他对像的时候,其他对象的内容任然是可变的,不受影响。
1,常量属性:将writable和configurable设置为false的时候就等同于创建了一个常量属性,不可修改,不可配置
var obj = { name:'张三', sex:'男' }; Object.defineProperty(obj,'age',{ value:30, writable:false, enumerable:true, configurable:false });
2,禁止扩展:禁止一个对象添加新属性。使用Object.preventExtensions()
var obj = { name:'张三', sex:'男' }; Object.preventExtensions(obj); obj.age = 30; delete obj.sex; console.log(obj); //{name:'张三'}
3,密封:Object.seal(),封闭后不仅不能添加新属性,且不能重新配置和删除现有属性(可以修改属性值)。实现
上只是在Object.preventExtensions基础上将所有属性configurable:false。
4,冻结:Object.freeze(),实际上会在一个现有对象上调用Object.seal() + writable:false。
这个方法是应用在对象上的最高级别的不变性。它会禁止对象本身的任何修改及其任意直接属性的修改(外部引用对象不受影响)。
Getter和Setter:
在ES5中可以通过getter和setter修改属性值的获取和修改的默认操作。但是只能应用在单个属性上,无法应用在整个对象上。
getter是一个隐藏函数,会在获取属性值的时候调用。setter也是一个隐藏函数,会在属性设置值的时候调用。
当给一个属性添加getter或setter的时候,这个属性会被定义为"访问描述符",对于访问描述符来说,JavaScript会忽略掉它
的value和writable特性,取而代之的是关心get和set特性
var obj = { get a(){ return 111; } }; Object.defineProperty(obj,'b',{ get:function(){ return 222; }, enumerable:true }); console.log(obj); //{} for(var i in obj){ console.log(i,obj[i]); } //a,111 //b,222
无论是字面量中的get a(){}还是defineProperty中的get:function(){},二者都会在对象中创建一个没有值的属性,对
于这个属性的访问会自动调用隐藏函数,它的返回值会自动当作属性的值返回。
var obj = { get a(){ return 111; }, }; console.log(obj.a); obj.a = 222; console.log(obj.a);
上面例子中,我们为属性a赋值222,但是没有赋上去,是因为我们这个访问描述符只定义了getter没有定义
setter(即使定义了setter,如果不将它们的值关联起来,赋值也无意义)。
为了让属性更合理,通常getter和setter是成对出现(如果只想创建只读属性例外)。
var obj = { get a(){ return this._a; }, set a(val){ this._a = val } }; console.log(obj.a); //undefined obj.a = 222; console.log(obj.a); //222 for(var i in obj){ console.log(i); } //a //_a
存在性:
判断一个属性名在某个对象中的存在性,常用的有in操作符和hasOwnProperty()方法。
in:检查属性是否在指定对象中或它的原型链中。
hasOwnProperty:只会检查指定对象中的存在性,不会检查原型链。
枚举:
propertyIsEnumerable():判断一个对象中的某个属性的枚举性(不包含原型链中属性),可枚举返回true,不可枚举返回false。
Object.keys():返回一个对象中所有可枚举的属性(不包含原型链中属性),结果为一个数组。
Object.getOwnPropertyNames():返回一个对象中所有属性,无论是否可枚举(不包含原型链中属性),结果为一个数组。
var obj = {}; Object.defineProperty(obj,'a',{ value:111, writable:true, enumerable:true, configurable:true }); Object.defineProperty(obj,'b',{ value:222, writable:true, enumerable:false, configurable:true }); console.log(obj); //{a:111,b:222} console.log(obj.propertyIsEnumerable('a'),obj.propertyIsEnumerable('b')); //true,false console.log(Object.keys(obj)); //{a:111} console.log(Object.getOwnPropertyNames(obj)); //{a:111,b:222}
目前还没有内置的方法可以获取到能在in操作符中匹配到的属性列表(对象的自身属性与原型链上的属性)。不过可以手动的使用
递归遍历某个对象上的整个原型链,并保存每一层中使用Object.keys获取到的列表。