变量

本文最后更新于:2020年11月3日 凌晨

变量

原始值和引用值

ECMAScript 变量可以包含两种不同类型的数据:原始值和引用值;

在把一个值赋给变量时,JavaScript 引擎必须确定这个值是原始值还是引用值;

  • 原始值(primitive value)就是最简单的数据;
    • 6 种原始值:Undefined、Null、Boolean、Number、String 和 Symbol;
    • 保存原始值的变量是按值(by value)访问的;
  • 引用值(reference value)则是由多个值构成的对象;
    • 引用值是保存在内存中的对象;因为JS不允许直接访问内存位置,因此不能直接操作对象所在的内存空间;
    • 在操作对象时,实际上操作的是对该对象的引用(reference)而非实际的对象本身;
    • 保存引用值的变量是按引用(by reference)访问的;

动态属性

原始值和引用值的定义方式很类似,都是创建一个变量,然后给它赋一个值;

但是对于引用值而言,可以随时添加、修改和删除其属性和方法;

1
2
3
let person = new Object(); 
person.name = "Nicholas";
console.log(person.name); // "Nicholas"

但是原始值不能有属性,虽然添加属性不会报错;

原始类型的初始化可以只使用原始字面量形式;

1
2
3
let name = "Nicholas"; 
name.age = 27;
console.log(name.age); // undefined

当原始类型使用了new关键字,则JS会创建一个Object类型的实例,但其行为类型原始值:

1
2
3
4
5
6
7
8
let name1 = "Nicholas"; 
let name2 = new String("Matt");
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

复制值

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

1
2
3
4
5
6
7
8
9
10
11
// 原始值的复制:原始值会被复制到新变量的位置,num 和 num_2完全独立的,互不干扰,num_2相当于num的副本;
let num = 5;
let num_2 = num;


// 引用值的复制:存储在变量中的值也会被复制到新变量所在的位置,区别在于,这里复制的值实际上是一个指针,它指向存储在堆内存中的对象。
// 操作完成后,两个变量实际上指向同一个对象,因此在一个对象上面的变化会在另一个对象上反映出来;
let obj1 = new Object();
let obj2 = obj1;
obj1.name = "Nicholas";
console.log(obj2.name); // "Nicholas"

传递参数

ECMAScript 中所有函数的参数都是按值传递的;

如果是原始值,那么就跟原始值变量的复制一样,如果是引用值,那么就跟引用值变量的复制一样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function addTen(num) { 
num += 10;
return num;
}
let count = 20;
let result = addTen(count);
console.log(count); // 20,没有变化
console.log(result); // 30

// 在函数内部,obj 和 person 都指向同一个对象。
// 结果就是,即使对象是按值传进函数的,obj 也会通过引用访问对象。
function setName(obj) {
obj.name = "Nicholas";
}
let person = new Object();
setName(person);
console.log(person.name); // "Nicholas"

首先在这里我们能在函数内部修改到外部的对象的属性,这里并不是按引用传递;

这里查阅资料之后发现,有一种按共享传递(call by sharing):调用函数传参时,函数接受对象实参引用的副本(既不是按值传递的对象副本,也不是按引用传递的隐式引用);

在函数传入对象的时候传入的是对象的地址副本,修改了形参的属性可以间接修改了外部的对象的属性,当我们在函数内部重新给形参赋值时候却是不成功的,在函数内部被重写时,而且该函数的形参都是局部作用域,它变成了一个指向本地对象的指针。而那个本地对象在函数执行结束时就被销毁了

1
2
3
4
5
6
7
8
function setName(obj) { 
obj.name = "Nicholas";
obj = new Object();
obj.name = "Greg";
}
let person = new Object();
setName(person);
console.log(person.name); // "Nicholas"

确定类型

前面提出了使用typeof操作符可以判断数据类型是不是原始类型,字符串,数值,布尔值,或者undefined,但是值如果是对象或者null,那么都会返回object;

1
2
3
4
5
6
7
8
9
10
11
12
let s = "Nicholas"; 
let b = true;
let i = 22;
let u;
let n = null;
let o = new Object();
console.log(typeof s); // string
console.log(typeof i); // number
console.log(typeof b); // boolean
console.log(typeof u); // undefined
console.log(typeof n); // object
console.log(typeof o); // object

typeof 虽然对原始值很有用,但它对引用值的用处不大, 我们可以使用instanceof判断具体是什么类型的对象:

1
2
3
4
5
6
7
// 语法
// 如果判断通过返回true,否则返回false
result = variable instanceof constructor

console.log(person instanceof Object); // 变量 person 是 Object 吗?
console.log(colors instanceof Array); // 变量 colors 是 Array 吗?
console.log(pattern instanceof RegExp); // 变量 pattern 是 RegExp 吗?

按照定义,所有引用值都是 Object 的实例,因此通过 instanceof 操作符检测任何引用值和Object 构造函数都会返回 true(比如数组, 正则, 函数等)。

如果用 instanceof 检测原始值,则始终会返回 false,因为原始值不是对象。

1
2
3
4
5
6
7
8
// typeof返回function是 function,但是instanceof可以通过Function和Object
let a = function() {
return;
}

console.log(typeof a); // function
console.log(a instanceof Object) // true
console.log(a instanceof Function) // true

变量
http://www.clearluv.com/2020/11/03/变量/
作者
徐楠峰
发布于
2020年11月3日
许可协议