对象与函数
在JavaScript中, 一切皆对象, 函数也是特殊的对象.
对象
在JavaScript中, 对象 是名值对的集合, 值可以由多个名值对组成. 名值对是互相关联的一对名和值 Name/Value Pair. 名可以被定义多次,但在特定的 执行环境 中只能匹配一个值.
1 | Address: |
对象的属性
对象及其属性(将属性和方法统称为属性, 下同)都存在于内存空间, 对象可以获取到与其相关联的属性(have a reference to them, 即获取到的是指向属性所在内存空间的地址).
有两种操作符可以获取属性值:
object["property"]
: 括号(Computed Member Access Operator)object.property
: 点号(Member Access Operator)
多用点号操作符, 因为更简洁也更利于调试.
对象字面量(Object Literals)
var person = {}
: 用对象字面量方法创建对象.
1 | var person = { |
1 | var person = new Object(); |
两种方法中, 对象字面量创建对象的方法更好, 比new
更简洁, 用处更大. 比如:
1 | var john = { |
尽管语法不一致, 实际的原理是一样的, 都是在内存空间中创建对象及属性.
JSON与对象字面量
过去传输数据常用XML格式:
1 | <object> |
但因为XML格式包含太多额外的标记, 在传输过程中会浪费很多带宽, JSON的出现解决了这样的问题.
JSON实际上是对象字面量语法的一个子集. 并不属于JavaScript, 但因为格式和JS对象十分相似, 所以JavaScript解析JSON格式的数据十分容易. JavaScript中内置了JSON.stringify()
将JS对象转化为JSON, JSON.parse()
将JSON解析为JS对象.
1 | { |
函数也是对象
JavaScript中, 函数是一等公民
任何其他数据类型可以做的事, 函数也能做到, 如:
- 将函数赋给变量
- 作为参数传递给其他函数
- 可以被即时创建
1 | function log(a) { |
函数也是对象, 因此可以为函数关联属性. 其中两个比较特别的属性是函数名和代码. 而函数名并不是必要的, 不存在函数名的函数是匿名函数. 我们所写的那段代码实际上是函数作为对象的其中一个属性, 并不是函数本身, 同时这个属性是可以被调用的.
1 | function greet(){ |
当函数greet被创建时, 该函数被置于内存空间, 在上述情况下, 该函数是全局对象的属性.
函数声明与函数表达式
表达式(expression): 会返回值, 可以将其储存在一个变量中, 但并非必要.
1 | a = 3; |
以上三者都为表达式, 因为都返回了值, 表达式返回的可以是任何类型的值.
声明(statement): 不会返回值.
1 | var a; |
以上代码中a === 3
是表达式, 返回布尔值, if语句是声明.
函数声明:
1 | greet(); |
函数声明直到执行时才返回值, 在执行环境的创建阶段(creation phase)被置于内存空间, 即被提升(hoisting), 因此在声明之前的位置可以调用函数.
函数表达式:
1 | anonymousGreet(); // 不可以在这里调用 |
函数表达式不会被提升(no hoisting), 因为事先置于内存空间的是var anonymousGreet;
, 因此上述代码会抛出错误.
值(by value)与引用(by reference)
变量(原始类型)通过值传递
1 | // by value |
- 将某个原始类型值(primitive value)赋值给变量a, 此时a就指向了那个值在内存空间所在的地址;
b = a
: 变量b指向一个新的内存空间地址, 那个地址所存储的值是a变量所指向值的拷贝, 因此b的值为3;- 因为存储在不同的地址, 因此之后再改变a的值不会影响到b的值.
变量(对象)通过引用传递
1 | var c = {greeting: 'hi'}; |
- 将对象(包括函数)赋值给变量a时, a指向对象在内存空间所在地址;
b = a
: 变量b不会指向新的内存空间地址, 而是指向a所指向的地址, 没有新的对象被创建;- 因此改变a和b的值都会影响到对方.
changeGreeting(d);
通过传入函数的方式改变d的行为同样也会影响到c.- 所有对象都是通过引用传递.
c = { greeting: 'howdy'};
赋值操作符会在内存空间创建一个新地址, 此时c就不再指向原地址, 而d的指向不变, 因此出现c和d值不同的情况.
参数(arguments)
当函数的执行环境被创建时, JS引擎除了创建变量环境(container for variables), this
, 外部环境(for reference), 还创建了另一个特殊的变量arguments
.arguments
是所有参数值的集合, 是类数组元素, 并非数组.
设置参数默认值:
1 | function greet(firstname, lastname, language){ |
尽管greet函数在定义时有三个参数, 但还是可以不传递任何参数调用greet函数, 这是JavaScript的特性, 在大多数编程语言中不可以这样调用, 由于变量声明提升(Hoisting), 结果出现三个undefined, 但这不是我们想要的结果, 可是实际过程中不传递参数直接调用函数的情况很可能出现, 可以通过以下方法避免出现undefined的情况.
1 | function greet(firstname, lastname, language){ |
在ES6中可以这样设定默认值:
1 | function greet(firstname='John', lastname='Doe', language='en'){ |
‘this’
this
的值取决于函数是如何被调用的.
1 | var c = { |
对于上述代码的第二个console.log(this);
, 因为在对象c内定义了函数表达式setname, 并在内改变了name的值, 因此预期输出的是Object {name: "Updated again"}
, 但实际上并非如此, 通过在setname函数中输出this值发现, 这个this指向全局对象(浏览器中是Window), 因此setname实际上是在全局环境中定义了name变量, 在全局环境输出name可以发现这里的name值才是”Updated again”.
为了避免发生这样的错误, 可以预先定义this的值:
1 | var c = { |
因为对象通过引用传递, self与this一样指向对象c, 可以通过self改变对象的属性. 在setname函数中, 没有声明self的值, 通过作用域链到外部寻找self变量, 于是找到了在log方法内定义的指向对象c的self.
参考