变量、作用域和内存
原始值和引用值
原 始值的类型有Number
,String
,Boolean
,Null
,Undefined
,Symbol
,
引用值的类型有Object
,Array
,Function
对象是引用值来看下面的一个例子
let a = 10;
let b = a;
b = 20;
console.log(a); // 10
console.log(b); // 20
以上的两个变量都是独立的 Number 类型的变量,所以改变其中一个不会影响另 一个
let a = { name: "fanceir" };
let b = a;
b.name = "fanceir2";
console.log(a.name); // fanceir2
console.log(b.name); // fanceir2
因为对象是引用值,所以改变其中一个会影响另一个
确定类型
之前讲到过typeof
是判断一个对象是否为原始类型,但是对于null
来说还是会返回object
,所以可以使用Object.prototype.toString.call()
来判断
let a = null;
let b = 1056;
let c = "fanceir";
let d = new String("fanceir");
console.log(typeof a); // object
console.log(typeof b); // number
console.log(typeof c); // string
console.log(typeof d); // object
在上面的例子中,有一个 String 的实例,这里typeof
返回的是object
。
如此可以发现,typeof 只能分辨原始值的类型,但是对引用值的用处不是很大,我们想知道一个对象具体是什么类型的对象,这里可以使用instanceof
,instanceof
可以通过原型链来判断一个函数是否是另一个函数的实例
let a = new String("fanceir");
console.log(a instanceof String); // true
执行上下文和作用域
执行上下文可以理解为当前代码的执行环境,上下文代码在执行的时候会创建变量的一个作用域链。这个作用域链会决定哪些数据可以被访问到。
let a = 10;
function foo() {
let aa = 20;
function bar() {
let aaa = 30;
console.log(a, aa, aaa);
}
bar();
//这里不能访问aaa
}
foo();
//这里不能访问aa和aaa
var 的变量提升
console.log(a); // undefined
var a = 10;
console.log(a);
let a = 10;
下面的一个例子会报错,这是因为let
和const
声明的变量不会被提升
堆地址和栈地址
基本数据类型的值会出现在栈内存中,就是我们说的变量
对象是保存到堆内存中的,每次创建一个新的对象都会在堆内存中创建一个空间而此时变量保存的是对象的内存地址。
如果两个变量保存的是同一个对象的引用,当一个通过变量修改属性的同时,另一个也会改变
let a = 1;
let b = a;
b = 2;
在上面的例子中,a
和 b
是基本数据类型(number),它们的值存储在栈内存中。当我们将 a
的值赋给 b
时,b
得到了 a
的副本,因此修改 b
不会影响 a
。
let obj1 = { name: "Alice" };
let obj2 = obj1;
obj2.name = "Bob";
console.log(obj1.name); // 输出 'Bob'
在这个例子中,obj1
和 obj2
都引用了同一个对象。当我们通过 obj2
修改对象的属性时,obj1
也会反映出这个变化,因为它们指向的是同一个堆内存中的对象。
基本数据类型
的值存储在栈内存
中,赋值到变量时,变量保存的是值的副本,修改副本不会影响原值。
而对象引用存储
在堆内存
中,赋值到变量时,变量保存的是对象的内存地址
,修改对象属性会影响其他变量引用的对象属性。
基本数据类型 | 对象引用 |
---|---|
number | object |
string | Array |
boolean | Function |
null | Date |