这是我参与8月更文挑战的第22天,活动详情查看:8月更文挑战
原始值与引用值
ECMAScript变量可以包含两种不同类型的数据:原始值和引用值。原始值就是最简单的数据(基本数据类型等),引用值则是由多个值构成的对象。
a. 原始值
- 保存原始值的变量是按值访问的
- 操作的是存储在变量中的实际值
b. 引用值
- 保存在内存中的对象
JavaScript不允许直接访问内存位置,不能直接操作对象所在的内存空间- 在操作对象时,实际上操作的是对该对象的引用而不是实际的对象本身
- 保存引用值的变量是按引用访问的
1. 动态属性
原始值和引用值的定义方式很类似,都是创建一个变量,然后给它赋值。对于引用值而言,可以随时添加、修改和删除其属性和方法。而原始值不能由属性,尽管尝试给原始值添加属性不会报错。
let person = new Object();
person.name = "Mannqo";
console.log(person.name); // Mannqo
let name = "Mannqo";
name.age = 18;
console.log(name.age); // undefined
由此可见,只有引用值可以动态添加后面可以使用的属性。注意:原始类型的初始化可以只使用原始字面量形式。如果使用的是new关键字,则JavaScript会创建一个Object类型的实例。
let nameObj = new String("Mannqo");
nameObj.age = 18;
console.log(nameObj.age); // 18
使用new关键字创建一个新对象person其实就是在执行下面这段代码;
let person = {};
person.__proto__ = Object.prototype;
Object.call(person);
2. 复制值
原始值复制:相互独立,独立使用,互不干扰;
引用值复制:指向同一个对象,这里复制的值实际上是一个指针,它指向存储在堆内存中的对象。操作完成后,两个变量实际上指向同一个对象。
因此一个对象上面的变化会在另一个对象上反映出来;看看下面这个例子:
let obj1 = new Object();
let obj2 = obj1;
obj1.name = 'Mannqo';
console.log(obj2.name); // Mannqo
这也解释了为什么改变实例对象的原型会影响到其它的实例对象;
3. 传递参数
ECMAScript中所有函数的参数都是按值传递的。也就是说,函数外的值会被复制到函数内部的参数中,就像一个变量复制到另一个变量中一样。变量有按值和按引用访问,而传参则只有按值传递。
在按值传递参数时,值会被复制到一个局部变量。如果是按引用传递参数【对变量的修改在函数外也是有效的】,值在内存中的位置会被保存在一个局部变量,也就是对本地变量的修改会反映到函数外部,这在ECMAScript中是不可能的。这句话可能有点难理解,看看下面这个例子:
function setName(obj) {
obj.name = "Mannqo";
/*obj = new Object();
obj.name = 'yt';*/
}
let person = new Object();
setName(person);
console.log(person.name); // Mannqo
按值传递,即进入函数时,obj的指向与person的指向相同,obj指向的对象保存在全局作用域的堆内存上,然后对obj的属性进行修改相当于对person进行修改【即使对象是按值传进函数的,obj也可以通过引用访问对象】。
把上面注释掉的两个语句打开;可以看到输出值并没有发生改变。也就是在执行
obj = new Object();时改变了obj的指向,它不再是指向person指向的那个对象了,此时对新对象进行修改并不会影响到person对象;
当obj在函数内部被重写时,它变成了一个指向本地对象的指针,而那个本地对象在函数执行结束时就被销毁了。
如果是按引用的话,那么会可能改变了原来指向的person对象,执行上面那段代码会发生这样的情况;原来person指向的是那个person对象,进去函数的时候obj就相当于person,而改变了obj的指向相当于改变了person的指向;
4. 确定类型
typeof虽然对原始值很有用,当它对引用值的用处不大。因为通常我们是想知道它是什么类型的对象,而不是它是不是对象;这时我们可以通过instanceof来判断;
所有的引用值都是Object的实例,因此通过instanceof操作符检测任何引用值和Object构造函数都会返回true;类似地,如果用instanceof检测原始值,始终会返回false,因为原始值不是对象;