从JS底层理解var,const,let

目录

  • 基本数据类型和引用数据类型
  • 声明提高
  • var,let,const

基本数据类型和引用数据类型

基本数据类型是按值访问的,由于能够操做保存在变量中的实际的值。
引用数据类型的值是保存在内存中的对象,JS不容许直接访问内存中的位置,因此在操做的时候操做的是对象的引用;所以是引用数据类型是按照引用访问的。es6

复制变量值

复制基本类型的值数组

var num1 = 5;
var num2 = num1;

num1和num2中的5是彻底独立的,互不影响
基本类型复制数据结构

复制引用类型函数

var obj1 = new Object();
var obj2 = obj1;
obj1.name = 'lucyStar';
console.log(obj2.name);
// lucyStar

咱们能够看到,obj1保存了一个对象的实例,这个值被复制到 Obj2中。复制操做完成后,两个变量实际引用的是同一个对象,改变了其中一个,会影响另一个值
引用类型复制spa

传递参数

参数传递就跟把函数外部的值复制给函数内部的参数;设计

基本类型传参3d

function addTen(num) {
    num+=10;
    return num;
}
const count = 20;
const result = addTen(count);
console.log(count);
// 20,没有变化

console.log(result);
// 30

引用类型传参指针

function setName(obj) {
    obj.name = 'luckyStar';
    obj = new Object();
    obj.name = 'litterStar'
}
const person = new Object();
setName(person);
console.log(person.name);
// luckyStar

在函数内部修改了参数的值,可是原始的引用仍然保持未变。
实际上,在函数内部重写 obj时,这个变量引用的就是一个局部对象了。而这个局部对象会在函数执行完毕以后当即销毁。code

变量提高(hoisting)

为了更好地解释声明提高,下面例子中使用 var 而不是使用 ES6新增的let和const(它们不存在声明提高)
  1. 下面的代码输出什么
a = 2;
var a;
console.log(a);
// 2

可能有人会认为是 undefined, 由于 var a 声明在 a = 2以后,会被从新赋值为 undefined。但他实际上的输出结果是 2对象

  1. 下面的代码输出什么
console.log(a);
var a = 2;

可能有人会认为,因为变量 a 在使用前没有先进行声明,所以会抛出 ReferenceError异常。但实际它的输出是 undefined

引擎会在解释JavaScript代码以前首先会对其进行编译。编译阶段中一部分工做就是找到全部的声明,并用合适的做用域将他们关联起来。

因此正确的思考思路是:包含变量和函数在内的全部声明都会在任何代码被执行前首先被处理。

当你看到 var a = 2时,可能会被认为这是一个声明。可是 JavaScript实际上会将其当作两个声明:var aa = 2; 第一个声明是在编译阶段进行的。第二个声明会被留在原地等待执行阶段。

因此第一个例子中的代码会以以下的形式进行处理

var a;

a = 2;
console.log(a);

其中第一部分是编译,第二部分是执行。

第二个例子会按照如下流程进行处理

var a;

console.log(a);
a = 2;
注意:只有声明自己会被提高,而赋值或其余运行逻辑会留在原地。

函数声明和变量声明都会被提高,可是函数会首先被提高,而后才是变量

foo(); // 1

var foo;
function foo(){
    console.log(1);
}
foo = function() {
    console.log(2);
}

// 上面代码会按照如下流程进行处理

// 函数声明会提高到变量前面
function foo(){
    console.log(1);
}
var foo;

foo(); // 1
foo = function() {
    console.log(2);
}

虽然重复的 var声明会被忽略掉,可是出如今后面的函数声明仍是会覆盖以前的

foo(); // 3

function foo(){
    console.log(1);
}
var foo = function() {
    console.log(2);
}
function foo() {
    console.log(3);
}

思考一下下面的代码输出什么

var name = 'Tom';
(function() {
    if (typeof name == 'undefined') {
        var name = 'Jack';
        console.log('Goodbye ' + name);
    } else {
        console.log('Hello ' + name);
    }
})();

答案是 Goodbye Jack

改为下面这样应该会更容易理解一些

// 去掉下面这行也是同样的,由于会优先访问函数做用域内部的变量
// var name = 'Tom';
(function() {
    var name; // 注意这行
    if (typeof name == 'undefined') {
        var name = 'Jack';
        console.log('Goodbye ' + name);
    } else {
        console.log('Hello ' + name);
    }
})();

当即执行函数的中的变量 name 的定义被提高到了顶部,并在初始化赋值以前是 undefined,因此 typeof name == 'undefined'

var,let,const

咱们先来看看,var,let,const 声明变量的位置
变量位置
能够看到 let和const声明的变量在块级做用域中,不存在变量提高。

// var 的状况
console.log(a); // 输出undefined
var a = 2;

// let 的状况
console.log(b); // 报错ReferenceError
let b = 2;

let

  1. 声明的变量能够被修改。
  2. 要注意暂时性死区(TDZ)

总之,在代码块内,使用let命令声明变量以前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)

function foo(x = y, y = 2) {
  return [x, y];
}

foo(); // 报错

由于参数x默认值等于另外一个参数y,而此时y尚未声明,属于“死区”。

const

声明的变量是常量;

const 实际保证的,并非变量的值不变,而是变量指向的那个内存地址所保存的数据不得改动。

对于基本数据类型(数值。字符串。布尔值)。值就保存在变量指向的那个内存地址,所以等同于常量。
但对于引用数据类型主要是对象和数组)。变量指向的内存地址,保存的只是一个指向实际数据的指针。

const 只能保证这个指针是固定的(即便老是指向另外一个固定的地址),至于它指向的数据结构是否是可变的,那就彻底不能控制了。所以,将一个对象声明为常量必须很是当心。

const foo = {};
// 为 foo 添加一个属性,能够成功
foo.prop = 123;
foo.prop // 123

// 将 foo 指向另外一个对象,就会报错
foo = {}; // TypeError: "foo" is read-only

参考

相关文章
相关标签/搜索