变量、作用域、内存
# 变量
需要注意的是:
- 基本类型的值是按值访问(by value),
- 引用类型是按引用访问(by refernce)。
- 函数内的参数是按值传递
举个例子:
let num1 = 5
let num2 = num1
console.log(num2) // 5
2
3
上述代码发生的事情是:
- 首先在内存空栈中,开辟一个新的空间用于存储num2变量
- 然后将num1的值复制一份给num2
由于 num1
和 num2
是 按值传递
的,所以修改其中一个的值,并不会对影响另外一个值。
let num1 = 5
let num2 = num1
num2 = 10
console.log(num2) // 10
console.log(num1) // 5
2
3
4
5
而对于引用类型来说,则是 按照引用
传递的,举个例子:
const person = { name: 'Jack' }
const person1 = person
console.log(person1.name) // Jack
2
3
4
上述代码发生的事情是:
- 将 person1 指向 person 引用的内存地址。 也就是,现在 person 和 person1 指向同一个地址,修改其中一个的值,都会修改另外一个对象的值,因为此时他们都指向的是同一个内存地址。
const person = { name: 'Jack' }
const person1 = person
person1.name = 'Lucy'
console.log(person.name) // Lucy
console.log(person1.name) // Lucy
2
3
4
5
6
这也是我们有时候需要对对象进行深拷贝的原因,我们又想操作原始对象的属性,但是又不想原始对象受到影响。
函数内的参数 都是 按值传递
的,这个仔细想想也是有道理的。我们肯定是希望函数内部的操作是不会影响函数外部的变量的,不然代码也太难维护了。但是有一个令人迷惑的地方,函数的参数是引用类型时,在函数内部修改参数的值,同时也会影响函数外部。
function test(obj) {
obj.name = 'Lucy'
}
const person = {
name: 'Jack'
}
test(person)
console.log(person.name); // Lucy
2
3
4
5
6
7
8
9
从这个例子来看 似乎函数内部的参数是按 引用
传递的,再看这个例子:
function test(obj) {
obj.name = 'Lucy'
obj = new Object();
obj.name = "Greg";
}
const person = {
name: 'Jack'
}
test(person)
console.log(person.name); // Lucy
2
3
4
5
6
7
8
9
10
11
还是打印的是 Lucy
,如果是按照 引用传递
的话,应该打印的是 Greg
,因为 person 的引用指向了新的地址。所以,总的来说,不管是函数传递的参数是基本类型还是引用类型,都是 按值传递
的。
# 确定类型
确定基本类型,使用typeof
:
const num = 0
console.log(typeof num) // number
2
确定引用了类型使用 instanceof
const obj = {}
console.log(obj instanceof Object) // true
2
当然,有个通用的做法就是,使用 Object.prototype.toString.call()
方法:
const num = 0
const obj = {}
// 判断基本类型
console.log(Object.prototype.toString.call(num) === '[object Number]') // true
// 判断引用类型
console.log(Object.prototype.toString.call(obj) === '[object Object]') // true
2
3
4
5
6
7
8
# 上下文
变量和函数,都有一个 上下文
的概念,上下文决定了决定了它们可以访问哪些数据和变量。每个上下文都有一个变量对象
,数据和变量都存在这个对象上。我们并不能通过代码访问这个这个对象,但是后台处理数据会用到它。
上下文
是个笼统的概念,主要分为 全局上下文
和 局部上下文
。在浏览器中 全局上下文
就是我们常说的window
对象,在node中则是 global
。函数上下文
就属于局部上下文。
这里值得关注的是 临时上下文
,又如下两种情况会增加 临时上下文
:
- try/catch 语句的 catch 块
- with 语句with 语句
这两种情况下,都会在作用域链前端添加一个变量对象。对
with
语句来说,会向作用域链前端添加指定的对象;对catch
语句而言,则会创建一个新的变量对象,这个变量对象会包含要抛出的错误对象的声明。
比如,使用with
创建一个临时上下文:
const obj = {
name: 'mss',
age: 18
}
with(obj) {
console.log(name, age) // mss 18
}
2
3
4
5
6
7
8
这里一旦执行完毕,这个上下文就会立刻销毁。
# 块级作用域
使用 var
声明变量的时候,有一个令人又爱又恨的特性,就是变量提升
。
看个例子:
console.log(num) // undefined
var num = 1
2
细心的朋友会发现,这里打印的是 undefined
。在JavaScript中,这意味着该变量未初始化。这其实是令人困惑的,打印的时候,明明没有声明这个变量,按照正常的理解这里应该抛出错误的。但是上述的代码会被解析为:
var num;
console.log(num)
num = 1
2
3
num
变量被提升到作用域顶部了,也就导致打印出了 undefined
。
为了避免这种 变量提升
后造成的各种魔幻的问题,ES6提出了 块级作用域
,就是一个花括号 {}
就是一个块级作用域。花括号外部无法访问花括号内部的变量。
如下面的例子,提前打印num
变量,就会抛出错误 num is not defined
console.log(num) // num is not defined
{
let num = 1
console.log(num) // 1
}
2
3
4
5