假如你没去过天安门, 故宫, 长城相当于你没到过北京. 假如你搞不懂 JS 变量的作用域, 相当于你没学过 JS. 关于 JS 变量作用域的重要性自己好好悟吧! 提示: 查看本文章记得看注释哦!
JS 是一门弱类型 (松散型) 的语言, 这也就是说其天生就与众不同, 独领风骚!
在讲解变量作用域之前, 我们先来了解一下 JS 中的变量. JS 中的变量与其它语言有很大的不同, 由于 JS 变量拥有松散 (不强制) 的本质, 从而决定了其只是一个在特定阶段保持特定类型值的名字.
JS 变量包含两种不同的数据类型: 基本数据类型 (值类型) 与引用数据类型(复杂数据类型).
基本数据类型的值保存在栈内存中. 而引用数据类型的值则保存在堆内存中, 在栈内存中只保留引用类型的指针地址.
基本类型值有以下五种: undefined,Null,Boolean,Number 和 String. 基本数据类型的值保存在栈内存中.
- // 在栈内存中开辟一块空间存储值为 "laotie" 的变量 name
- var name="laotie";
- // 在栈内存中开辟一块空间存储值为 "laotie" 的变量 name2
- var name2=name;
- //name 与 name2 是相对独立的, 所以改变 name2 的值为 "xiaozhang", 而 name 的值不会受到影响
- var name2="xiaozhang";
- console.log(name);//laotie
- console.log(name2);//xiaozhang
再来看一下引用类型:
- /* 在栈内存中存放 obj 的地址
- * 其值存放在堆内存中.
- * 栈内存的地址的指向堆内存中的值.*/
- var obj={
- name:"zhangpeiyue"
- }
- /* 将 obj 的地址赋值给 obj2
- 所以在栈内存中存储的地址与 obj 的地址相同,
- obj 与 obj2 共享同一个值.
- */
- var obj2=obj;
- obj2.name="xiaozhang";
- // 因为 obj 与 obj2 共享同一个值, 所以上行代码改变的是同一个值
- console.log(obj.name);//xiaozhang
- // 你也可以认为 obj 即为 obj2, 引用类型比较的是地址, 因此为 true
- console.log(obj==obj2);//true
接下来我们分别看一下基本数据类型与引用类型的比较
基本数据类型的比较, 比较的是值:
- // 基本数据类型比较的是值, 只要值相等比较结果即为 true
- var a=1;
- var b=1;
- console.log(a==b);//true
- var c=2;
- var d=c;
- console.log(c==d);//true
引用类型的比较, 比较的是地址:
- var obj={
- age:12
- }
- var obj2={
- age:12
- }
- // 引用类型比较的是地址, 而不是值.
- // 由于每次创建的引用类型地址都不同, 所以结果为 false
- console.log(obj==obj2);//false
- var obj3={
- age:12
- }
- // 将 obj3 的地址赋值给 obj4. 所以地址相同
- var obj4=obj3;
- // 由于比较的是地址, 且 obj3 与 obj4 的地址相同, 所以结果为 true
- console.log(obj3==obj4);
再来看一下关于基本类型与引用类型作为函数中的参数问题
基本类型作为参数, 参数为局部变量
- /* 接收的所有基本数据类型, 接收的是其值.
- 接收的参数都是函数体中的局部变量.
- 在函数体内改变值, 对外部不会产生任何影响 */
- function fn(a){
- a+=1;
- console.log(a);//14
- }
- var a=13;
- fn(a);
- console.log(a);//13
引用数据类型作为参数, 参数为全局变量
- /* 引用数据类型传递的是引用地址,
- 因此函数体中的 obj 与函数外的 obj 的引用地址相同.
- 所以函数体中的 obj 与函数外的 obj 共享同一值,
- 改变其中一个值, 其它的也会随之改变
- */
- function fn(obj){
- obj.name="laowang"
- console.log(obj.name);//laowang
- }
- var obj={
- name:"laotie"
- }
- fn(obj);
- console.log(obj.name);//laowang
终于聊到作用域啦! JS 变量作用域, 就是指变量所影响的范围. JS 中作用域分为全局作用域与局部作用域(函数作用域). 在全局作用域内定义的变量为全局变量, 在局部作用域内定义的变量为局部变量.
全局作用域是最外围定义的作用域, 在 web 浏览器中全局作用域指的是 window 对象. 因此在全局作用域定义的变量和函数, 你可以认为是 window 对象的属性与方法!
- var color="red";// 定义一个全局 color
- function fn(){
- color="blue";// 全局函数可以在函数内访问
- }
- fn();
- console.log(color);//blue
全局的变量和函数, 都是 window 对象的属性和方法.
- var color="red";// 定义一个全局 color
- function fn(){
- color="blue";// 全局函数可以在函数内访问
- }
- window.fn();
- console.log(window.color);//blue
函数作用域内的声明的变量与全局作用域内声明的变量同名
- var color="yellow";// 定义全局变量 color
- function fn(){
- // 在函数体内如果拥有指定的变量, 就不会去外层查找
- var color="red";// 这里是局部变量 color, 外面是访问不到的哦
- console.log(color);//red
- }
- fn();
- console.log(color);//yellow
通过传参. 传递的参数为基本类型, 参数在函数体内是局部变量. 传递的参数为引用类型, 参数在函数体内是全局变量. 文章开始已涉及过, 在此不在解释!
如果函数体内存在子函数, 则只有该函数才可以访问子函数.
- var color="green";
- function fn(){
- // 子函数是建议以下划线开头
- function _fn2(){
- var color2="orange";
- console.log(color);//green
- console.log(color2);//orange
- }
- _fn2();//_fn2()的作用域在 fn()内
- }
- fn();
- _fn2();// 在此处调用 fn2()是调取不到的
注意: 当在一个作用域内执行代码时, 就会有一个被称为作用域链的东西. 它的作用是保证对变量与方法访问的有序性. 也就是当前执行环境中存在指定的变量或方法就不会去外围查找, 如果没有则会向外围查找, 直到找到为止! 如果找不到会报错! 一层层向外寻找指定变量和方法的行为, 形成了一个作链条. 这个链条就是作用域链. 访问局部变量要比全局变量快许多, 因为不需要向外围查找 (向上查找) 指定的变量.
JS 没有块级作用域, 所谓块级作用域指的是 if,for 等语句用花括号包裹的代码!
- if(true){
- var name="zhang";
- }
- console.log(name);//zhang
当你在函数中声明一个没有带上 var 关键字的变量时, 这个变量就会成为全局变量. 不过这种行为很容易造成命名冲突, 所以非常不推荐大家使用!
- function fn(){
- // 此处 a=12 相当于 window.a=12.
- a=12;// 声明一个不带 var 关键字的变量
- }
- fn();
- console.log(a);//12
这是因为 fn 函数是在 window 环境下运行的, 由此函数体内的 a=12 相当于执行了 window.a=12. 而 window 是 JS 的顶级对象. 也可以认为我们为顶级对象增加了值为 12 的 a 属性. 所以变量 a 就成为了全局变量.
另外如果函数体内的变量是通过 var 关键字声明的, 则该变量为局部变量, 只能在该函数体内进行访问, 函数体外是访问不到的.
- function fn(){
- var a=12;
- console.log(a);//12
- }
- fn();
- console.log(a);// 报错: a is not defined
分享一道阿里关于作用域的面试题:
- var obj = {
- b: 2
- };
- var fn = function () {};
- fn.c = 3;
- function test(x, y, z) {
- x = 4;
- y.b = 5;
- z.c = 6;
- return z;
- }
- test(a, obj, fn);
- console.log(a + obj.b + fn.c);//12
变量的生命周期
所谓变量的生命周期指的是变量由声明到销毁.
对于全局变量来讲, 其生命周期是永久的, 除非我们主动去销毁这个全局变量. 而在函数体内声明的局部变量, 当函数运行完以后, 局部变量就失去了任何价值, 它们也会随着函数的执行完毕而销毁.
- var fn=function(){
- var a=1;// 退出函数后, 局部变量 a 会销毁
- console.log(a);
- }
- fn();
JS 环境中分配的内存一般有如下生命周期
内存分配: 当我们声明变量, 函数, 对象的时候, 系统会自动为他们分配内存
内存使用: 即读写内存, 也就是使用变量, 函数等
内存回收: 使用完毕, 由垃圾回收自动回收不再使用的内存
来源: http://www.jianshu.com/p/bf10e7178966