前2章探索了this绑定指向不同的对象需要函数引用的call-site。
但是什么是对象,为什么我们需要指向它们?
本章探索细节。
Syntax
the rules that describe how words and phrases are used in a computer language!
对象来源于两种forms
- declarative(literal) form:
- constructed from
var myObj = { key: value // ... }; var myObj = new Object(); myObj.key = value;
2者的结果没有区别。
用constructed form来创建对象及其少见。 开发者总是使用literal form。就连内建对象也是这样。
Type
object是6种primary types之一。 (string, number, boolean, null, undefined, object)
⚠️, 不是everything in JavaScript is an object, 这句话在JavaScript中完全❌!
对象有复杂的子类型。 complex primitives。
- 函数是一种子类型,a callable object。不过和普通的对象使用方式是一样的。
- 数组Array也是一种子类型,有额外的行为。
- 普通对象是hash类型。是对象的主类型。
Build-in 对象
其他的对象子类型,都是内建对象。
从名字看他们似乎和他们的simple primitives counter-parts(副本)相关,但是:
他们的关系是很复杂的,下面会进行简短的探索。
- String
- Number
- Boolean
- Object
- Function
- Array
- Date
- RegExp
- Error
?都是对象!!! 不要看名字String,就认为它是string。
类似其他语言的类,比如Ruby的String类。
但是在JS, 他们是内建函数。可以当作一个constructor使用(new Xxxx, 见上章)。
结果会得到一个这个子类型的新的constructed object。
例子:一个primary type: string
var str = "hello"typeof str //"string" str instanceof String //false
对比⬆️例子:这是一个对象,由String对象构建
var strObject = new String( "I am a string" ); typeof strObject; // "object" strObject instanceof String; // true
就是一个对象,可以使用对象的方法: strObject['key'] ,但只能读,不能改!!
理解:
实际上JS团体并不怎么使用这个方式创建这个对象子类型。它没有literal form
因为literal form的创建方式,基本可以替代constructed object form。比如使用length方法,通过charAt(3)得到对应的value.
var strPrimitive = "I am a string"; console.log( strPrimitive.length ); // 13 console.log( strPrimitive.charAt( 3 ) ); // "m"
甚至strPrimitive["3"] 的写法也能得到值:m。
原因是engine自动强迫它成为一个String 对象,所以property/method都可以用
这叫做same sort of coercion相同类型强制转化。
string, number ,boolean都会被类型强制转化,然后就可以使用方法和特性了。
Datal 值,只能用constructed object form形式创建,它没有literal form副本。
null, undefined没有对象包裹器形式,只有primitive values。
Object
s, Array
s, Function
s, and RegExp
s (regular expressions) are all objects。
提供了constructed form。这种形式比literal form有更多的可选项。
大多时候使用更简便的literal form。除非你需要额外的设置options。
Error对象很少直接用,一般是在错误发生时自动抛出。可以通过new Error()创建,很少用到。
Contents(大章节)
一个对象的contents由values and type组成,储存在指定的地点,叫做properties。
一般我们提到contents ,暗示了这些values被储存在这个对象内,但这只是表象。
实际上,engine stores values in implementation-dependent ways,并不是储存值在一些对象容器内。
储存在container的只是属性名字。作为一个pointers。指向标记。
var myObject = { a: 2 }; myObject.a; // 2 myObject["a"]; // 2
使用点或方括号["..."],的结果一样,但用法有区别。
- 点后面需要一个identifier
- ["..."]内部是一个字符串。因此可以使用如“Super-Funn!”这样的复杂写法。
在对象中,属性名字总是strings.如果你使用number,boolean作为属性名字,它会被转化为string.
Computed Property Names
ES6增加了可以计算的属性名字,你可以设置一个表达式,用一个[]包裹这个表达式。
var prefix = "foo"; var myObject = { [prefix + "bar"]: "hello", [prefix + "baz"]: "world" } myObject.foobar //"hello" myObject["foobaz"] //"world"
Property vs. Method
property access。是一些开发者用于区别的名称。
在其他语言,函数是属于对象(类)的,被称为方法method。
The safest conclusion is probably that "function" and "method" are interchangeable in JavaScript.
函数,方法,其实没什么区别。
即使你声明一个函数表达式作为这个对象的一部分,实际上这个函数并不属于这个对象。仅仅是引用
var myObject = { foo: function foo() { console.log( "foo" ); } }; var someFoo = myObject.foo; someFoo; // function foo(){..} myObject.foo; // function foo(){..}
Arrays
数组默认有数字索引。
var myArray = [ "foo", 42, "bar" ]; myArray.length; // 3 myArray[0]; // "foo" ,方括号内是数字,不是字符串!! myArray[2]; // "bar"
数组也是对象,所以可以添加属性给数组
Duplicating Objects(没看懂!)
最常见的请求功能之一:如何复制一个对象!
浅层复制,深层复制。
深层复制容易出现无限循环。
可以使用Object.assign()方法。
Property Descriptors
用于对象的属性的特点的描述。Object.getOwnPropertyDescriptor( 一个对象名, '这个对象的属性名字')
var myObject = { a:2, } Object.getOwnPropertyDescriptor(myObject, "a") //输出 {value: 2, writable: true, enumerable: true, configurable: true}
你可以看到,这个属性描述器。 属性a包括了除了value之外,还是其他3个特性。
使用Object.defineProperty()方法,可以新建一个属性,或者修改现存的属性的内部特性。一般用不上。
特性:Writable
严格模式下,会报错; 非严格模式不会提示❌。
"use strict"; var myObject = {}; Object.defineProperty( myObject, "a", { value: 2, writable: false, // not writable! configurable: true, enumerable: true } ); myObject.a = 3; // TypeError,
//Cannot assign to read only property 'a' of object
特性:Configurable
设置了false的话,就不能再使用defineProperty()方法修改了。
configurable: false代表你不能对这个属性进行特性修改。
同时,这个属性也不能被删除!(使用: delete myObject.a 直接删除对象的属性)
特性:Enumerable
这个特性控制是否一个属性可以show up in certain object-property enumerations。(使用一些计算相关的方法)
Immutability不变
有时候,想要让对象或对象的属性不会发生改变,冻结freeze!
ES5增加了相关方法。
⚠️
所有方法只创建浅层不变(冻结),如果 一个对象有一个引用到另一个对象, 这个对象的contents不受冻结的影响。
Object Constant
使用writable: false和configurable: false,你可以创建一个常量(不能修改、删除)作为一个对象属性。
Prevent Extensions
设置一个对象不能新增属性(可以操作旧属性): 调用Object.preventExtensions( 一个对象);
Seal
Object.seal(),让一个对象调用Object.preventExtensions(),并且所有属性都是configurable:false。
即不能删除已有属性,不能新增属性。
Freeze
Object.freeze() ,最高层次的冻结。相当于上面三个的全部效果。不可修改,删除属性 ,不能新增属性。
存取一个对象属性值的内部运行机制!
[[Get]]
细微的但重要的细节,关于proterty access被如何执行!
这个操作符号执行类似一个函数的调用: [[Get]]().它会查找属性,并返回对应的值。
它会使用prototype chain进行查找。
如果找不到相关property's name,会返回undefined
[[Put]]
有[[Get]] operation用于从一个属性中得到一个value,自然也有对应的[[Put]] operation!
也是类似调用[[Put]](),有一系列判断!
判断属性是否存在:
- 属性如果存在则:进一步检查:
- 属性是否是一个存取描述器? 是的话,调用setter。
- 属性是否是一个数据描述器,并且writeable: false? 是的话,在非严格模式失败,或者在严格模式抛出❌TypeError.
- 其他情况,设置value给现存的属性。
- 属性不存在:这个判断的过程更复杂,会用到[[Prototypte]]
Getters & Setters
默认的[[Put]] and [[Get]]操作,可以完全控制对象,从已存在的属性取值,把值设置给新的属性或已存在属性。
ES5介绍了一个方法,可以重写这2个默认操作的部分代码,通过使用getters and setters,在a pre-property level。
Getters是属性可以调用一个隐藏的函数来取出一个值。
Setters是属性可以实际调用一个隐藏的函数来设置一个值。
当你定义一个属性时,有getter,或者setter。 它的定义就成为了一个'accessor descriptor'存取描述器。
(相反的是‘data descriptor’数据描述器)。
当属性是存取描述器时,value和writable特性就无意义并忽略了。作为替代,JS会改用set, get特性 。
Vue 会在初始化实例时对属性执行 getter/setter
转化过程。让属性成为响应式,即监听setter是否被调用。
可以使用2个方法定义对象的属性为accessor descriptor:
- 通过对象字面量句法:get a() { .. }
- 通过明确的定义:使用defineProperty(..)
var myObject = {
get a() {
return this._a_;
},
set a(val) {
this._a_ = val * 2;
}
};
myObject.a = 2;
myObject.a; // 4
Existence
如何确定一个对象中有一个属性,2种方法:
- in 关键字, 检查prototype chain
- hasOwnProperty()方法, 不检查原型链。
var myObject = { a: 2 }; ("a" in myObject); // true ("b" in myObject); // false myObject.hasOwnProperty( "a" ); // true myObject.hasOwnProperty( "b" ); // false
in关键字,会检查属性是否在对象中存在,或者是否存在于高层的原型链中。
Enumeration
对象的属性的特性之一。这里进一步讨论它。
var myObject = { }; Object.defineProperty( myObject, "a", // make `a` enumerable, as normal { enumerable: true, value: 2 } ); Object.defineProperty( myObject, "b", // make `b` NON-enumerable { enumerable: false, value: 3 } ); myObject.b; // 3 ("b" in myObject); // true myObject.hasOwnProperty( "b" ); // true // ....... for (var k in myObject) { console.log( k, myObject[k] ); } // "a" 2
可以看出,myObject.b存在可存取,但是不能用在foo..in循环。
所以enumerable的基本意思就是 “如果这个对象的属性被iterated通过,就会be included”。
好的习惯:
使用for .. in循环在对象上(返回属性/value对儿);
使用传统的for循环在数组上, 用于返回array的值。
相关
对象.propertyIsEnumerable("a") 方法。
Object.keys(..) , 返回当前对象的enumerabled的属性名字,格式是array.
Object.getOwnPropertyNames(..), 返回当前对象的所有属性名字。
Iteration
数字索引的数组:用标准循环。循环数组的index.
var myArray = [1, 2, 3]; for (var i = 0; i < myArray.length; i++) { console.log( myArray[i] ); } // 1 2 3
数组的循环方法。
ES5新增:forEach(), every(), some(). 都可以接受一个回调函数。区别是他们如何各自响应从回调中返回的value.
forEach():
循环数组的所有value!, 忽略从回调中返回的value。
var numbers = [65, 44, 12, 4];
function myFunction(item,index,arr) { arr[index] = item * 10 } numbers.forEach(myFunction)
//输出:undefined 。 forEach不返回value.
numbers //输出:(4) [650, 440, 120, 40]
every(..),循环value,发现回调函数返回了一个false(falsy)的值停止循环,否则当循环结束,返回true。
some(), 正相反,当发现回调函数返回了一个true(truthy)的值停止循环, 否则,当循环结束, 返回false。
every(), some()相当于for循环内,当查到满足条件的value时,执行break语句。
之后还有一大块内容:深度循环内部@@iterator(没看),涉及ES6
Review (TL;DR)
对象声明有literal form, a constructed form两种 。
错误概念,JS中的每个事都是对象❌。
对象有子类型:如function。
对象是key/value的集合。通过.propName, ["propName"]存取。
当一个属性被存取时,Engine会使用内部默认的[[Get]]
operation (and [[Put]]
for setting values), 它们不光查看对象上的属性,也会检索原型链条。
属性有确定的特性,特性可以通过property descriptor来控制。如writable, configurable。
对象可以通过多种方法使用这些特性,如Object.preventExtensions(), Object.freeze()等。
Properties无需contain values,它们可以通过getters/setters设置(存取属性)。它们可以被enumerable或不能,例如:enumerable控制属性是否可以用for..in循环它们。