000005 - JavaScript变量、作用域与内存

000005 - JavaScript变量、作用域与内存

1、原始值与引用值

ECMAScript变量可以包含两种不同类型的数据:原始值和引用值。原始值(primitive value)就是最简单的数据,引用值(reference value)则是由多个值构成的对象。

6种原始值:Undefined、Null、Boolean、Number、String和Symbol。保存原始

值的变量是按值(by value)访问的,因为我们操作的就是存储在变量中的实际值。引用值是保存在内存中的对象,保存引用值的变量是按引用(by reference)访问的。

1.1、动态属性

原始值和引用值的定义方式很类似,都是创建一个变量,然后给它赋一个值。不过,在变量保存了这个值之后,可以对这个值做什么,则大有不同。对于引用值而言,可以随时添加、修改和删除其属性和方法。

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

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

注意,原始类型的初始化可以只使用原始字面量形式。如果使用的是new关键字,则JavaScript会创建一个Object类型的实例,但其行为类似原始值.

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、复制值

除了存储方式不同,原始值和引用值在通过变量复制时也有所不同。在通过变量把一个原始值赋值到另一个变量时,原始值会被复制到新变量的位置。这两个变量可以独立使用,互不干扰

let num1 = 5; // 5
let num2 = num1; // 5

num2 = 10;
console.log(num1); // 5
console.log(num2); // 10

在把引用值从一个变量赋给另一个变量时,存储在变量中的值也会被复制到新变量所在的位置。区别在于,这里复制的值实际上是一个指针,它指向存储在堆内存中的对象。操作完成后,两个变量实际上指向同一个对象,因此一个对象上面的变化会在另一个对象上反映出来.

let obj1 = new Object();
let obj2 = obj1;
obj1.name = "Nicholas";
console.log(obj2.name); // "Nicholas"
1.3、传递参数

ECMAScript中所有函数的参数都是按值传递的。这意味着函数外的值会被复制到函数内部的参数中,就像从一个变量复制到另一个变量一 样。如果是原始值,那么就跟原始值变量的复制一样,如果是引用值,那么就跟引用值变量的复制一样。

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

function setName(obj) {
    obj.name = "Nicholas";
}
let person = new Object();
setName(person);
console.log(person.name); // "Nicholas"
1.4、确定类型

typeof操作符最适合用来判断一个变量是否为原始类型。更确切地说,它是判断一个变量是否为字符串、数值、布尔值或undefined的最好方式。如果值是对象或null,那么typeof返回"object",

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

如果变量是给定引用类型的实例,则instanceof操作符返回true。

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,因为原始值不是对象。

2、执行上下文与作用域
2.1、作用域链增强

虽然执行上下文主要有全局上下文和函数上下文两种(eval()调用内部存在第三种上下文),但有其他方式来增强作用域链。某些语句会导致在作用域链前端临时添加一个上下文,这个上下文在代码执行后会被删除。通常在两种情况下会出现这个现象,即代码执行到下面任意一种情况时:

  • try/catch语句的catch块
  • with语句

这两种情况下,都会在作用域链前端添加一个变量对象。对with语句来说,会向作用域链前端添加指定的对象;对catch语句而言,则会创建一个新的变量对象,这个变量对象会包含要抛出的错误对象的声明

function buildUrl() {
    let qs = "?debug=true";
    
    with(location){
        let url = href + qs;
    }
    return url;
}

with语句将location对象作为上下文,因此location会被添加到 作用域链前端。buildUrl()函数中定义了一个变量qs。当with语句中的代码引用变量href时,实际上引用的是location.href,也就是自己变量对象的属性。在引用qs时,引用的则是定义在buildUrl()中的那个变量,它定义在函数上下文的变量对象上。而在with语句中使用var声明的变量url会成为函数上下文的一部分,可以作为函数的值被返回;但像这里使用let声明的变量url,因为被限制在块级作用域,所以在with块之外没有定义。

2.2、变量声明
01.使用var的函数作用域声明

在使用var声明变量时,变量会被自动添加到最接近的上下文。在函数中,最接近的上下文就是函数的局部上下文。在with语句中,最接近的上下文也是函数上下文。如果变量未经声明就被初始化了,那么它就会自动被添加到全局上下文,

function add(num1, num2) {
    var sum = num1 + num2;
    return sum;
}
let result = add(10, 20); // 30
console.log(sum); // 报错:sum在这里不是有效变量

函数add()定义了一个局部变量sum,保存加法操作的结果。这个值作为函数的值被返回,但变量sum在函数外部是访问不到的。如果省略上面例子中的关键字var,那么sum在add()被调用之后就变成可以访问的了

function add(num1, num2) {
    sum = num1 + num2;
    return sum;
}
let result = add(10, 20); // 30
console.log(sum); // 30
02.使用let的块级作用域声明

let关键字跟var很相似,但它的作用域是块级的,这也是JavaScript中的新概念。块级作用域由最近的一对包含花括号{}界 定。换句话说,if块、while块、function块,甚至连单独的块也是let声明变量的作用域。

if (true) {
    let a;
}
console.log(a); // ReferenceError: a没有定义
while (true) {
    let b;
}
console.log(b); // ReferenceError: b没有定义
function foo() {
    let c;
}
console.log(c); // ReferenceError: c没有定义
// 这没什么可奇怪的
// var声明也会导致报错
// 这不是对象字面量,而是一个独立的块
// JavaScript解释器会根据其中内容识别出它来
{
    let d;
}
console.log(d); // ReferenceError: d没有定义

let与var的另一个不同之处是在同一作用域内不能声明两次。重复的var声明会被忽略,而重复的let声明会抛出SyntaxError。

03.使用const的常量声明

使用const声明的变量必须同时初始化为某个值。一经声明,在其生命周期的任何时候都不能再重新赋予新值。

const a; // SyntaxError: 常量声明时没有初始化
const b = 3;
console.log(b); // 3
b = 4; // TypeError: 给常量赋值

const除了要遵循以上规则,其他方面与let声明是一样的。const声明只应用到顶级原语或者对象。换句话说,赋值为对象的const变量不能再被重新赋值为其他引用值,但对象的键则不受限制。

const o1 = {};
o1 = {}; // TypeError: 给常量赋值

const o2 = {};
o2.name = 'Jake';
console.log(o2.name); // 'Jake'

如果想让整个对象都不能修改,可以使用Object.freeze(),这样再给属性赋值时虽然不会报错,但会静默失败:

const o3 = Object.freeze({});
o3.name = 'Jake';
console.log(o3.name); // undefined
04.标识符查找
var color = 'blue';
function getColor() {
    return color;
}
console.log(getColor()); // 'blue'

在这个例子中,调用函数getColor()时会引用变量color。为确定color的值会进行两步搜索。第一步,搜索getColor()的变量对象, 查找名为color的标识符。结果没找到,于是继续搜索下一个变量对象(来自全局上下文),然后就找到了名为color的标识符。因为全局变量对象上有color的定义,所以搜索结束。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值