原始值与引用值

140 阅读4分钟

概念

  • ECMA 变量可以包含两种不同类型的数据:原始值和引用值
    • 原始值(primitive value) 就是最简单的数据
    • 引用值(reference value) 是由多个值构成的对象
  • 在把一个值赋给变量时,JavaScript 引擎必须确定这个值的类型
    • 保存原始值的变量是按值(by value)访问的,因为我们操作的就是存储在变量中的实际值
    • 引用值是保存在内存中的对象。JavaScript 不允许直接访问内存位置,因此也就不能直接操作对象所在的内存空间。在操作对象是,实际上操作的是对该对象的引用(reference)而非实际的对象本身。因此,保存引用值的变量是按引用(by reference)访问的

动态属性

  • 原始值和引用值的定义方式很类似,都是创建一个变量,然后给他赋一个值。不过在变量保存了这个值后,可以对这个值做什么,则很不同。引用值可以随时添加、修改和删除其属性和方法
const person = new Object();
person.name = 'Nicholas';
console.log(person.name); // 'Nicholas'

let name = 'Nicholas';
name.age = 27;
console.log(name.age); // undefined
  • 在给对象一个新属性后即可访问,直到对象被销毁或属性被显示删除
  • 原始值不能有属性,尽管尝试给它添加属性不会报错
  • 只有引用值可以动态添加后面可以使用的属性
const name1 = 'Nicholas';
const name2 = new String('Nicholas');
name1.age = 27;
name2.age = 26;
console.log(name1.age); // undefined
console.log(name2.age); // 26
console.log(typeof name1); // string
console.log(typeof name2); // object
  • 注意:原始类型的初始化只能使用原始字面量形式声明,如果使用的是 new 关键字,则 JavaScript 会创建一个 Object 类型的实例,但其行为类似原始值

复制值

  • 除了储存方式不同,原始值和引用值在通过变量赋值时也有所不同

  • 原始值

    • 复制前后的两个值是完全独立的,互不干扰

    • let num1 = 5;
      let num2 = num1;
      num1 = 10;
      console.log(num2); // 5
      
  • 引用值

    • 复制出来的值实际上是一个指针,它指向储存在堆内存中的对象

    • 复制过后,两个变量实际上指向同一个对象,因此一个变量上面的变化会在另一个变量上反映出来

    • const obj1 = new Object();
      const obj2 = obj1;
      obj1.name = 'Nicholas';
      console.log(obj2.name); // 'Nicholas'
      

传递参数

  • 所有函数的参数都是按值传输的,函数外的值传递到函数内,相当于从一个变量复制到另一个变量一样
  • 在传递原始值时我们非常容易理解,因为本身原始值复制过后都是独立的,复制过后,函数内外操作都不会彼此影响
  • 而在传递引用值时,函数内外设置属性时,双方都会反映这个变化,这并非是因为函数传参是按引用传输,而是因为按值传输后的两个值都指向同一堆内存
let num = 1;
function fn1(num) {
  num += 3;
}
fn1(num);
console.log(num); // 1

let obj = new Object();
function fn2(obj) {
  obj.name = 'Nicholas';
  obj = new Object();
  obj.name = 'Greg';
}
fn2(obj);
// 这里就很能说明传输是按值传输,否则 log 的结果应该是 Greg
console.log(obj.name); // 'Nicholas'

确定类型

  • typeof 操作符最适合用来判断一个变量是否为原始类型,它是判断一个变量是否为字符串、数值、布尔值或 undefined 的最好方法,如果值是对象或 null,则返回 object
  • typeof 虽然对原始值很有用,但他对引用值的用处不大。一般我们不关心一个值是不是对象,而是想知道他是什么类型的对象,这时,我们可以使用 instanceof 操作符
  • result = variable instanceof constructor
  • 如果变量是给定引用类型(由其原型链决定)的实例,则 instanceof 操作符返回 true
const person = {};
console.log(person instanceof Object); // true
const colors = [];
console.log(colors instanceof Array); // true
const pattern = /\d/;
console.log(pattern instanceof RegExp); // true
  • 当然,所有引用值都是 Object 的实例,因此通过 instanceof 操作符检测任何引用值和 Object 构造函数都会返回 true,相对的,如果用于检测原始值,则始终返回 false,因为原始值不是对象
  • 注意typeof 在用于检测函数时会返回 function【ECMA-262 规定,任何实现内部 call 方法的对象都应该返回 function】,当在 Safari(直到Safari5)Chrome(直到Chrome7) 检测正则表达式时,会返回 function,其原因是因为上述浏览器正则实现了这个方法