函数调用位置的不同会造成this绑定对象的不同。
1.语法
定义对象的两种方式:
-
声明(文字)形式
是创建对象的一般用法,绝大多数内置对象也是这样做的。
var myObj = { key:value //... } -
构造形式
var myObj = new Object(); myObj.key = value;构造形式和文字形式生成的对象是一样的。
在文字声明中你可以添加多个键/值对,但是在构造形式中必须逐个添加属性。
2.类型
对象是JavaScript的基础。
JavaScript语言类型:
-
string
-
number
-
boolean
-
null
-
undefined
-
object
除了object,其他都属于简单基本类型,本身并不是对象。
null有时会被当做一种对象类型,typeof null 返回字符串 "object"
特殊的对象子类型,称之为 复杂基本类型。
函数就是对象的一个子类型,是“可调用的对象”,本质上和普通的对象一样,可以像操作对象一样操作函数(比如当作另一个函数的参数)。
数组也是对象的一种类型。
内置对象
对象子类型,通常被称为 “内置对象”
- String
- Number
- Boolean
- Object
- Function
- Array
- Date
- RegExp
- Error
这些内置对象,从表现形式来说很像其它语言中的类型type或者类class,实际上只是一些内置函数。这些内置函数可以当作构造函数来(new)使用,从而可以构造一个对应子类型的新对象。
字面量 是一个不可变的值,如果要在这个字面量上执行一些操作,比如获取长度,访问其中某个字符等,需要将其转换为String对象。
在必要时语言会自动把字符串字面量转换为String对象,并不需要显式的创建一个对象。所以可以直接在字符串字面量上访问属性和方法。数值字面量,布尔字面量都可以进行自动转化。
null 和undefined只有文字形式,Date只有构造形式。
Object Array RegExp Function, 无论使用文字形式还是构造形式,他们都是对象,不是字面量。
需要额外选项时,可以使用构造形式创建对象。
Error 对象一般是在抛出异常时被自动创建,也可以使用 new Error(..)这种构造形式来创建。
3.内容
对象的内容是由一些存储在特定命名位置的(任意类型的)值组成的,我们称之为属性。
存储在对象容器内部的是这些属性的名称,就像指针,指向这些值真正存储的位置。
访问对象中某个属性的值,可以使用.操作符或者[]操作符。
-
.语法 通常被称为属性访问
.操作符要求属性名满足标识符的命名规范
-
[""]语法通常被称为键访问
[".."]语法可以接受任意UTF-8/Unicode字符串作为属性名,并且因为该语法使用字符串来访问属性,所以可以在程序中构造这个字符串。
在对象中,属性名永远都是字符串。如果使用string(字面量)以外的其他值作为属性名,那他首先会被转换成一个字符串。对象和数组中数字下标的用法不同。
var myObject = {}; myObject[myObject] = "baz" myObject["[object Object]"]; //"baz"可计算属性名
在文字形式中使用[]包裹一个表达式来当作属性名
属性与方法
可在对象的文字形式中声明函数。
数组
数组支持[]访问形式,数组期望的是数值下标,索引是整数。
数组也是对象,可以给数组添加属性。虽然添加了命名属性,数组的length并未发生变化。
最好只用对象来存储键/值对,只用数组来存储数值下标/值对。
注意:如果试图向数组添加一个属性,但是属性名看起来像一个数字,那他会变成一个数值下标,因此会修改数组的内容,而不是添加一个属性。
复制对象
-
浅拷贝
复制出的新对象中引用和旧对象中引用的对象一样。
Object.assign(..) //第一个参数是目标对象,之后跟一个或多个源对象。遍历一个或多个源对象的所有可枚举(enumerable)的自有键(owned key)并把它们复制(使用 = 操作符赋值 ) 到目标对象,最后返回目标对象.源对象属性的一些特性(比如writable)不会被复制到目标对象。 var newObj = Object.assign({},myObject); newObj.a;//2 -
深拷贝
对于JSON安全的对象(可以被序列化为一个JSON字符串,并且可以根据这个字符串解析出一个结构和值完全一样)来说,有一种巧妙地复制方法。这种方法需要保证JSON是安全的。
var newObj = JSON.parse(JSON.stringify(someObj));
属性描述符
普通的对象属性对应的属性描述符(“数据描述符”,因为它只保存一个数据值,包含另外三个特性:writable(可写)、enumerable(可枚举)和configurable(可配置))。
在创建普通属性时,属性描述符会使用默认值,也可以使用Object.defineProperty(..)来添加一个新属性或者修改一个已有属性(如果它是configurable)并对特性进行设置。
var myObject = {}; Object.defineProperty(myObject,"a",{ value:2, writable:true, configurable:true, enumerable:true }); myObject.a;//2-
Writable
writable决定是否可以修改属性的值。
如果不可写,则修改则会静默失败,在严格模式下,会出错TypeError,表示如法修改一个不可写的属性。
writable:false代表属性不可改变。
-
Configurable
只要属性是可配置的,可以使用defineProperty(..)方法来修改属性描述符。
把configurable修改成false是单向操作,无法撤销。
尝试修改一个不可配置的属性描述符会出错TypeError错误,不管是否处于严格模式。
例外:即便属性是configurable:false,仍可以把writable的状态由true改为false,但是无法由false改为true.
configurable:false还会禁止删除属性,静默失败,属性是不可配置的。
delete myObject.a不要把delete看作一个释放内存的工具,他就是一个删除对象属性的操作。
-
Enumerable
控制属性是否会出现在对象的属性枚举中。可以不出现在枚举中,但仍然可以正常访问它。
用户定义的所有普通属性默认都是enumerable
不变性
-
浅不变形
影响目标对象和他的直接属性。如果目标对象引用了其他对象(数组、对象、函数,等),其他对象的内容不受影响,仍然是可变的。
-
深不变性
-
对象常量
结合writable:false和configurable:false就可以创建一个真正的常量属性(不可修改、重定义或者删除)
-
禁止扩展
禁止一个对象添加新属性并且保留已有属性,可以使用Object.preventExtensions(..)
在非严格模式下,创建新属性会静默失败。在严格模式下,将会抛出TypeError。
-
密封
Object.seal(..)创建一个“密封”的对象。
实际上,在一个现有对象上调用Object.preventExtensions(..)并且把所有现有属性标记为configurable:false.
密封之后不仅不能添加新属性,也不能重新配置或者删除任何现有属性,但是可以修改属性的值。
-
冻结
Object.freeze(..)会创建一个冻结对象。应用在对象上的级别最高的不可变性,禁止对于对象本身及其任意直接属性的修改(引用的其他的对象不受影响)
实际上会在一个现有对象上调用Object.seal(..)并把所有数据访问的属性标记为writable:false,这样就无法修改它们的值。
深度冻结:在这个对象上调用Object.freeze(..),然后遍历它引用的所有对象并在这些对象上调用Object.freeze(..).但是注意:可能会在无意中冻结其他(共享)对象。
[[Get]]
属性访问
myObject.a在myObject上实现了[[Get]]操作(类似函数调用),首先在对象中查找是否有名称相同的属性,如果找到就会返回这个属性的值。如果没有找到,遍历可能存在的[[Prototype]]链,也就是原型链。如果最终没有找到,返回undefined.
不同于访问变量,如果引用了当前词法作用域中不存在的变量,会抛出ReferenceError异常。
[[Put]]
给对象的属性赋值,[[Put]]被触发时:
-
如果存在这个属性:
- 属性是否是访问描述符?如果是并且存setter,就调用setter
- 属性的数据描述符中,writable是否是false。如果是,在非严格模式下静默失败,在严格模式下,抛出TypeError异常。
- 如果都不是,将该值设置为属性的值。
-
如果不存在这个属性
涉及到[[Prototype]]
Getter和Setter
对象默认的[[Put]]和[[Get]]操作分别可以控制属性值的设置和获取。应用在单个属性上,不能应用在整个对象上。
给一个属性定义getter和setter或者两者都有时,这个属性会被定义为“访问描述符”。JavaScript会忽略他们的value和writable特性,取而代之的是关心set和get(还有configurable 和 enumerable)特性。
//对象文字语法 var myObject ={ get a(){ return 2; } } //defineProperty(...)显式定义 Object.defineProperty( myObject, "b", {//描述符 //给b设置一个getter get:function() { return this.a *2}, //确保b会出现在对象的属性列表中 enumerable:true } ) myObject.a;//2 myObject.b;//4两种方式都会在对象中创建一个不包含值的属性,对于这个值的访问会自动调用一个隐藏函数,它的返回值会被当作属性访问的返回值。
setter会覆盖单个属性默认的[[Put]]操作。通常来说getter和setter是成对出现的(只定义一个的话通常会产生意料之外的行为)。
存在性
在不访问属性值的情况下判断对象中是否存在属性
var myObject = { a:2 }; //in操作符会检查属性(名)是否在对象及其[[Prototype]]原型链中 ("a" in myObject);//true ("b" in myObject);//false //检查属性是否在对象中,不会检查原型链 myObject.hasOwnProperty("a");//true myObject.hasOwnProperty("b");//falsemyObject.a 属性访问返回值可能是undefined,但是这个值有可能是属性中存储的undefined,也有可能是属性不存在所以返回undefined
所有的普通对象都可以通过对于Object.prototype的委托来访问hasOwnProperty(..),但有的对象可能没有连接到Object.prototype(通过Object.create(null)创建的),这种情况下,myObject.hasOwnProperty("b")就会失败,此时可以使用一种更加强硬的方法进行判断:Object.prototype.hasOwnProperty.call(myObject,"a"),它借用基础的hasOwnProperty(..)方法并把它显式绑定到myObject上。
数组中属性名是他的下标。
-
枚举
for (var k in myObject){ console.log(k,myObject[k]); } // "a" 2 可枚举的属性才可以出现在对象属性的遍历中在数组上应用for...in循环的枚举,不仅会包含所有数值索引,还会包含所有可枚举属性。
最好只在对象上应用for...in循环,遍历数组时就使用传统的for循环来遍历数值索引。
propertyIsEnumerable(..)
会检查给定的属性名是否直接存在于对象中(不是在原型链上),并且满足enumerable:true
Object.keys(...)
返回一个数组,包含所有可枚举属性
Object.getOwnPropertyNames(..)
返回一个数组,包含所有属性,无论他们是否可枚举。
遍历
数组:遍历下标来指向值。myArray[i],采用数字顺序
遍历对象属性时采用的顺序可能不一致。
使用for ..in遍历对象是无法直接获取属性值的,它实际上遍历的是对象中的所有可枚举属性,需要手动获取属性值。
一些数组的辅助迭代器:
-
forEach(..)
遍历数组中的所有值并忽略回调函数的返回值
-
every(..)
一直运行,直到回调函数返回false(或者“假"值),类似break,会提前终止遍历
-
some(..)
一直运行直到回调函数返回true(或者”真“值),类似break,会提前终止遍历
接受一个回调函数,并把它应用到数组的每个元素上,区别是他们对于回调函数的返回值的处理方式不同。
可以使用ES6的for...of语法来遍历数据结构(数组、对象、等等)中的值,for...of会寻找内置或者自定义的@@iterator对象并调用next()方法来遍历数据值。可以直接用在数组上。
-