变量:可变化的量,用来存储数据。
使用 var 关键字来定义变量。
// 使用 var 来定义变量
var year = 2017: // 正确
// 变量也可以不使用 var 声明直接赋值
year = 2017;
console.log(year) // 正确
// 不定义变量直接使用会报错
console.log(month);// 错误。报错 month 不存在,但 typeof 返回 undefined
// 可以使用一个 var 定义多个变量,用逗号分开
var year = 2017, month = 07; // 正确
变量的命名遵守标识符的命名规则。
优秀的变量命名法:
- 驼峰命名法:
mathTestScore
。- c 风格:
math_test_score
。
var year = 2017; // 正确
var 2017year = 2017; // 错误
可以在声明变量的同时对它使用等号进行赋值;没有赋值的变量默认取值为 undefined。
// 声明变量并赋值
var box=100;
console.log(box); // 打印 100
// 声明变量不赋值
var box;
console.log(box); // 打印 undefined
变量可以初始化后再次改变(可以同时改变为不同的类型),但对于后期维护带来困难,而且性能也不高,导致成本很高。
ECMAScript 的变量是弱类型的。
所谓弱类型就是可以用来保存任何类型的数据,声明变量时不需要指定变量的类型,根据变量的值确定。
var box = ”小米”;
box = 100; // 不需要重复地使用 var 来声明同一个变量
console.log(box) // 打印100
全局变量和局部变量:
- 全局变量:定义在所有函数之外,作用于整个代码的变量。
- 局部变量:定义在函数体内,只作用于函数体的变量。
使用 var 声明的变量会自动被添加到最接近的环境中。
不使用 var 声明直接赋值的变量,无论在函数体内还是函数体外都是全局变量(不推荐)。
访问局部变量要比访问全局变量更快,因为不用向上搜索作用域链。
var box=’blue’; // 全局变量
function setBox(){
var box=’red’; // 局部变量,范围在 setBox() 里
console.log(box);
}
setBox(); // 打印 red
console.log(box); // 仍然打印 blue
var box=’blue’;
function setBox(box){ // 通过传参,但也是局部变量,范围在 setBox() 里
console.log(box);
}
setBox(‘red’); // 打印 red
console.log(box); // 仍然打印 blue
变量的值的类型:
ECMAScript 变量的值包含两种不同的类型:基本类型和引用类型。
将一个值赋给变量时,解析器必须确定这个值是基本类型值还是引用类型值。
基本类型值指的是保存在栈内存中的简单数据,源自5种基本数据类型:undefined、null
、string、number、boolean,基本类型值完全保存在内存中的一个位置,占有固定大小的空间,按值来访问(可以操作保存在变量中的实际的值)。
引用类型值是指保存在堆内存中的对象,指 object。由于这种值的大小不固定,因此必须在堆内存中为这个值分配空间,但内存地址大小是固定的,因此可以将内存地址保存在栈内存中。也就是说,变量中保存的实际上只是一个指针,指向堆内存中真正保存数据的位置。按引用访问(js 不允许直接访问堆内存中的位置,也就是说不能直接操作对象的内存空间。在操作对象时,实际上操作的是对象的引用而不是真正的对象。先从栈中读取内存地址,再通过地址找到堆中的值)。
动态属性:
定义基本类型和引用类型都是创建一个变量并为该变量赋值。但是基本类型的值无法添加属性。
var box={name: '小米’} // 创建引用类型
console.log(box.name) // 小米
var box=’小米’ // 创建基本类型
box.age=28 // 添加一个属性
console.log(box.age) // undefined
变量复制:
在变量复制方面,不管是基本类型还是引用类型,复制的都是栈内存的数据,堆内存的数据无法复制。基本类型复制的是值本身,引用类型复制的是地址。
从一个变量向另一个变量复制基本类型的值,会创建这个值的一个副本。
从一个变量向另一个变量复制引用类型的值,复制的其实是指针,因此两个变量最终都指向同一个对象。
var box=’小米’
var box2=box
console.log(box2) //小米
var box=’小米’
var box2=box
box=’kkk’
console.log(box2) //小米
console.log(box) // kkk。box2 是 box 的一个副本,但是基本类型是完全独立的。box 和 box2 两个变量分别操作时互不影响。
var box= {name: '小米’}
var box2=box // 把引用地址复制给 box2
box2.name=’kkk’ // 给原有的对象添加属性
console.log(box2.name) // kkk
console.log(box.name) // kkk。因为指向的是同一个 object,不管修改谁, 都会改变。
var box= {name: '小米'}
var box2=box
box = {age: 18} // 赋值了一个新的对象。box 保存的引用地址和 box2 保存的引用地址已经不一样了,指向的对象也不一样。
console.log(box2) // {name: '小米’}
有一个数据 {name: 'Tom'}
存储在堆内存中;现在,声明一个变量 obj 并将 {name: 'Tom'}
赋值给它,实际上 obj 中存储的是 {name: 'Tom'}
在堆内存中的地址 0x123;现在,声明一个变量 obj1 并将 obj 赋值给它,实际上就是将 obj 中保存的值,也就是 {name: 'Tom'}
在堆内存中的地址 0x123 赋值给了 obj1。
传递参数:
ECMAScript 中所有函数的参数都是按值传递的。基本类型传递的是值本身,引用类型递的是地址。
var count=50
function box(count1){
count1 = count1 + 10
}
box(count))
console.log(count) //50。在调用函数时,变量 count 作为实参被传递给函数,于是数值 50 被复制给了形参 count1
var obj = {name: 'Tom'}
function box1(obj1) {
obj1.name = 'Mary' // 给原有的对象添加属性
}
box1(obj)
console.log(obj) // Mary。在调用函数时,变量 obj 作为实参传递给函数,于是 obj 的内容,即地址值被复制给了形参 obj1,obj1 和 obj 指向同一个对象
var obj= {name: '小米'}
function box(obj1){
obj1 = {name: 'kkk'} // 赋值了一个新的对象
}
box(obj)
console.log(obj) //小米。在函数内部,给 obj1 赋值了一个新的对象,obj1 和 obj 指向的已经不是同一个对象了,因此 obj 并未更改。
类型检测:
-
typeof:可以使用 typeof 运算符来检测一个值或者变量的数据类型。
typeof 在检测基本数据类型的时候好用,但检测引用类型的时候,并不能具体检测到到底是什么类型的 Object 对象。
var box=’Lee’ console.log(box, typeof box);// 'Lee' 'string' var box={} console.log(box, typeof box) // [object Object] 'object' var box = [] console.log(box, typeof box) // Array(0) 'object'
-
instanceof:使用 instanceof 运算符检测引用类型,来判断对象的具体类型。
语法:A instanceof B
,实例对象 A 是否是构造函数 B 的实例对象。如果 B 函数的原型对象在 A 对象的原型链上,就返回 true,否则返回 false。function Foo(){} var f1 = new Foo() console.log(f1 instanceof Foo) // true console.log(f1 instanceof Object) // true。这是因为 Object.prototype 是原型链的终点
console.log(Object instanceof Function) // true
console.log(Object instanceof Object) // true
console.log(Function instanceof Function) // true
console.log(Function instanceof Object) // true
// 使用 instanceof 运算符检测基本类型,会返回 false
var box='Lee'
console.log(box instanceof String) // false
// 以对象的形式处理基础类型,使用 instanceof 运算符检测会返回 true
var box=new String('Lee')
console.log(box instanceof String) // true
相等判断:
基本类型值进行相等判断时,会比较值是否相等。引用类型值进行相等判断时,会比较地址是否相等。
var num1 = 1
var num2 = 1
console.log(num1 == num2) // true
var arr1 = [1, 2, 3]
var arr2 = [1, 2, 3]
console.log(arr1 == arr2) // false
var obj1 = {name: 'Lee'}
var obj2 = obj1
console.log(obj1 == obj2) // true
变量声明提升:
把变量声明提升到当前执行上下文的最顶端,只提升声明,不提升值。也就是说,通过 var 定义的变量,在定义语句之前就可以访问到,值为 undefined。
JS 引擎在读取 JS 代码时会进行两个步骤,第一个步骤是解释,第二个步骤是执行。
所谓解释就是会先通篇扫描所有的 JS 代码,然后把所有声明提升到顶端,第二步是执行,执行就是操作一类的。
在执行全局的代码前或者调用函数时,JS 引擎会先创建一个对应的执行上下文,在创建对应的执行上下文时,会创建一个变量对象并把变量声明、函数声明、函数的形参都会被添加到这个对象中。所以在代码执行之前就可以访问到。
作用域提升:在作用域内,可以被提前访问。
因此,变量声明提升和函数提升也都可以被称为是作用域提升。
console.log(a) // 输出 undefined
var a = 10
//相当于
var a
console.log(a) // 由于未赋值,所以输出 undefined
a = 10
console.log(a) // Uncaught ReferenceError: a is not defined
a = 10