1. 基本类型和引用类型的值
ECMAScript变量可能包含两种不同数据类型的值:基本类型值和引用类型值。
- 基本类型值指的是简单的数据段
- 引用类型值指那些可能由多个值构成的对象,保存在内存中
注意:
在很多语言中,字符串以对象的形式来表示,因此被认为是引用类型的。ECMAScript放弃了这一传统。
1.1 动态的属性
定义基本类型值和引用类型值的方式:创建一个变量并为该变量赋值。
- 对于引用类型的值,我们可以为其添加属性和方法,也可以改变和删除其属性和方法
var person = new Object();
person.name = '张三';
console.log(person.name); // '张三'
- 不能给基本类型的值添加属性,尽管这样做不会导致任何错误
vae name = '张三';
name.age = 12;
console.log(name.age); // undefined
1.2 复制变量值
- 如果从一个变量向另一个变量复制基本类型的值,会在变量对象上创建一个新值,然后把该值复制到为新变量分配的位置上。
例如:
var num1 = 5;
var num2 = num1;
- num1中保存的值是5
- 使用num1的值来初始化num2时,num2中也保存了值5
- num2中的5与num1中的5是完全独立的,该值只是num1中5的一个副本
- 从一个变量向另一个变量复制引用类型的值时,同样也会将存储在变量对象中的值复制一份放到为新变量分配的空间中。
!!! 不同的是,这个值的副本实际上是一个指针,这个指针指向存储在堆中的一个对象。复制操作结束后,两个变量实际上将引用同一个对象。因此,改变其中一个变量,就会影响另一个变量。
例如:
var obj1 = new Object();
var obj2 = obj1;
obj1.bane = '张三';
console.log(obj2.name); // '张三'
- 变量obj1保存了一个对象的新实例
- 这个值被复制到了obj2中
- obj1和obj2都指向同一个对象
1.3 传递参数
ECMAScript中所有函数的参数都是按值传递的。
把函数外部的值复制给函数内部的参数,就和把值从一个变量复制到另一个变量一样。
- 基本类型值的传递如同基本类型变量的复制一样。
- 在向参数传递基本类型的值时,被传递的值会被复制给一个局部变量。
例如:
function addTen(num) {
num += 10;
return num;
}
var count = 20;
var result = addTen(count);
console.log(count); // 20
console.log(result); // 30
- 引用类型值的传递如同引用类型变量的复制一样。
- 在向参数传递引用类型的值时,会把这个值在内存中的地址复制给一个局部变量,因此这个局部变量的变化会反映在函数的外部。
function setName(obj) {
// 在这个函数内部,obj和person引用的是同一个对象
obj.name = '张三';
}
var person = new Object();
setName(person);
console.log(person.name); // '张三'
1.4 检测类型
typeof:基本上用来检测基本数据类型。在检测引用类型的值时返回"object",此时这个操作符的用处不大。
instanceof: 能确切知道某个对象是什么类型的对象。
语法格式:
result = variable instanceof constructor
如果变量是给定引用类型(根据它的原型链来识别)的实例,那么instanceof操作符就会返回true。
alert(person instanceof Object);
alert(colors instanceof Array);
2. 执行环境与作用域
执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为。
-
每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中。
-
在Web浏览器中,全局执行环境被认为是window对象,因此所有全局变量和函数都是作为window对象的属性和方法创建的。
-
某个执行环境中的所有代码执行完毕后,该环境被销毁,保存在其中的所有变量和函数定义也随之销毁。
2.1 作用域链
当代码在一个环境中执行时,会创建变量对象的一个作用域链。
作用域链能保证对执行环境有权访问的所有变量和函数的有序访问。
// 案例1:
function f1() {
function f2() {
}
}
var num = 456;
function f3() {
function f4() {
}
}
// 案例2
function f1() {
var num = 123;
function f2() {
console.log( num );
}
f2();
}
var num = 456;
f1();
2.2 预解析
JavaScript代码的执行是由浏览器中的JavaScript解析器来执行的。
JavaScript解析器执行JavaScript代码的时候,分为两个过程:预解析过程和代码执行过程。
预解析过程:
- 把变量的声明提升到当前作用域的最前面,只会提升声明,不会提升赋值。
- 把函数的声明提升到当前作用域的最前面,只会提升声明,不会提升调用。
- 先提升var,在提升function
JavaScript的执行过程:
var a = 25;
function abc (){
alert(a);//undefined
var a = 10;
}
abc();
// 如果变量和函数同名的话,函数优先
console.log(a);
function a() {
console.log('aaaaa');
}
var a = 1;
console.log(a);
2.3 变量提升
-
变量提升
定义变量的时候,变量的声明会被提升到作用域的最上面,变量的赋值不会提升。
-
函数提升
JavaScript解析器首先会把当前作用域的函数声明提前到整个作用域的最前面
// 1、-----------------------------------
var num = 10;
fun();
function fun() {
console.log(num);
var num = 20;
}
//2、-----------------------------------
var a = 18;
f1();
function f1() {
var b = 9;
console.log(a);
console.log(b);
var a = '123';
}
// 3、-----------------------------------
f1();
console.log(c);
console.log(b);
console.log(a);
function f1() {
var a = b = c = 9;
console.log(a);
console.log(b);
console.log(c);
}