变量提升

JavaScript的函数定义有个特点,它会先扫描整个函数体的语句,把所有申明的变量“提升”到函数顶部:

  1. 'use strict';
  2.  
  3. function foo() {
  4. var x = 'Hello, ' + y;
  5. console.log(x);
  6. var y = 'Bob';
  7. }
  8.  
  9. foo();

虽然是strict模式,但语句var x = 'Hello, ' + y;并不报错,原因是变量y在稍后申明了。但是console.log显示Hello, undefined,说明变量y的值为undefined。这正是因为JavaScript引擎自动提升了变量y的声明,但不会提升变量y的赋值。

对于上述foo()函数,JavaScript引擎看到的代码相当于:

  1. function foo() {
  2. var y; // 提升变量y的申明,此时y为undefined
  3. var x = 'Hello, ' + y;
  4. console.log(x);
  5. y = 'Bob';
  6. }

由于JavaScript的这一怪异的“特性”,我们在函数内部定义变量时,请严格遵守“在函数内部首先申明所有变量”这一规则。最常见的做法是用一个var申明函数内部用到的所有变量:

全局作用域

不在任何函数内定义的变量就具有全局作用域。实际上,JavaScript默认有一个全局对象window,全局作用域的变量实际上被绑定到window的一个属性:

  1. 'use strict';
  2.  
  3. var course = 'Learn JavaScript';
  4. alert(course); // 'Learn JavaScript'
  5. alert(window.course); // 'Learn JavaScript'

因此,直接访问全局变量course和访问window.course是完全一样的。

你可能猜到了,由于函数定义有两种方式,以变量方式var foo = function () {}定义的函数实际上也是一个全局变量,因此,顶层函数的定义也被视为一个全局变量,并绑定到window对象:

  1. 'use strict';
  2.  
  3. function foo() {
  4. alert('foo');
  5. }
  6.  
  7. foo(); // 直接调用foo()
  8. window.foo(); // 通过window.foo()调用

JavaScript实际上只有一个全局作用域。任何变量(函数也视为变量),如果没有在当前函数作用域中找到,就会继续往上查找,最后如果在全局作用域中也没有找到,则报ReferenceError错误。

名字空间

全局变量会绑定到window上,不同的JavaScript文件如果使用了相同的全局变量,或者定义了相同名字的顶层函数,都会造成命名冲突,并且很难被发现。

减少冲突的一个方法是把自己的所有变量和函数全部绑定到一个全局变量中。例如:

  1. // 唯一的全局变量MYAPP:
  2. var MYAPP = {};
  3.  
  4. // 其他变量:
  5. MYAPP.name = 'myapp';
  6. MYAPP.version = 1.0;
  7.  
  8. // 其他函数:
  9. MYAPP.foo = function () {
  10. return 'foo';
  11. };

把自己的代码全部放入唯一的名字空间MYAPP中,会大大减少全局变量冲突的可能。

许多著名的JavaScript库都是这么干的:jQuery,YUI,underscore等等。

局部作用域

由于JavaScript的变量作用域实际上是函数内部,我们在for循环等语句块中是无法定义具有局部作用域的变量的:

  1. 'use strict';
  2.  
  3. function foo() {
  4. for (var i=0; i<100; i++) {
  5. //
  6. }
  7. i += 100; // 仍然可以引用变量i
  8. }

为了解决块级作用域,ES6引入了新的关键字let,用let替代var可以申明一个块级作用域的变量:

  1. 'use strict';
  2.  
  3. function foo() {
  4. var sum = 0;
  5. for (let i=0; i<100; i++) {
  6. sum += i;
  7. }
  8. // SyntaxError:
  9. i += 1;
  10. }

常量

由于varlet申明的是变量,如果要申明一个常量,在ES6之前是不行的,我们通常用全部大写的变量来表示“这是一个常量,不要修改它的值”:

var PI = 3.14;
ES6标准引入了新的关键字const来定义常量,constlet都具有块级作用域:

  1. 'use strict';
  2.  
  3. const PI = 3.14;
  4. PI = 3; // 某些浏览器不报错,但是无效果!
  5. PI; // 3.14

解构赋值

从ES6开始,JavaScript引入了解构赋值,可以同时对一组变量进行赋值。

什么是解构赋值?我们先看看传统的做法,如何把一个数组的元素分别赋值给几个变量:

  1. var array = ['hello', 'JavaScript', 'ES6'];
  2. var x = array[0];
  3. var y = array[1];
  4. var z = array[2];

现在,在ES6中,可以使用解构赋值,直接对多个变量同时赋值:

  1. 'use strict';
  2.  
  3. // 如果浏览器支持解构赋值就不会报错:
  4. var [x, y, z] = ['hello', 'JavaScript', 'ES6'];

// x, y, z分别被赋值为数组对应元素:
console.log('x = ' + x + ', y = ' + y + ', z = ' + z);

注意,对数组元素进行解构赋值时,多个变量要用[...]括起来。

如果数组本身还有嵌套,也可以通过下面的形式进行解构赋值,注意嵌套层次和位置要保持一致:

  1. let [x, [y, z]] = ['hello', ['JavaScript', 'ES6']];
  2. x; // 'hello'
  3. y; // 'JavaScript'
  4. z; // 'ES6'

解构赋值还可以忽略某些元素:

  1. let [, , z] = ['hello', 'JavaScript', 'ES6']; // 忽略前两个元素,只对z赋值第三个元素
  2. z; // 'ES6'

如果需要从一个对象中取出若干属性,也可以使用解构赋值,便于快速获取对象的指定属性:

  1. 'use strict';
  2.  
  3. var person = {
  4. name: '小明',
  5. age: 20,
  6. gender: 'male',
  7. passport: 'G-12345678',
  8. school: 'No.4 middle school'
  9. };
  10. var {name, age, passport} = person;

对一个对象进行解构赋值时,同样可以直接对嵌套的对象属性进行赋值,只要保证对应的层次是一致的:

  1. var person = {
  2. name: '小明',
  3. age: 20,
  4. gender: 'male',
  5. passport: 'G-12345678',
  6. school: 'No.4 middle school',
  7. address: {
  8. city: 'Beijing',
  9. street: 'No.1 Road',
  10. zipcode: '100001'
  11. }
  12. };
  13. var {name, address: {city, zip}} = person;
  14. name; // '小明'
  15. city; // 'Beijing'
  16. zip; // undefined, 因为属性名是zipcode而不是zip
  17. // 注意: address不是变量,而是为了让city和zip获得嵌套的address对象的属性:
  18. address; // Uncaught ReferenceError: address is not defined

使用解构赋值对对象属性进行赋值时,如果对应的属性不存在,变量将被赋值为undefined,这和引用一个不存在的属性获得undefined是一致的。如果要使用的变量名和属性名不一致,可以用下面的语法获取:

  1. var person = {
  2. name: '小明',
  3. age: 20,
  4. gender: 'male',
  5. passport: 'G-12345678',
  6. school: 'No.4 middle school'
  7. };
  8.  
  9. // 把passport属性赋值给变量id:
  10. let {name, passport:id} = person;
  11. name; // '小明'
  12. id; // 'G-12345678'
  13. // 注意: passport不是变量,而是为了让变量id获得passport属性:
  14. passport; // Uncaught ReferenceError: passport is not defined

解构赋值还可以使用默认值,这样就避免了不存在的属性返回undefined的问题:

  1. var person = {
  2. name: '小明',
  3. age: 20,
  4. gender: 'male',
  5. passport: 'G-12345678'
  6. };
  7.  
  8. // 如果person对象没有single属性,默认赋值为true:
  9. var {name, single=true} = person;
  10. name; // '小明'
  11. single; // true

有些时候,如果变量已经被声明了,再次赋值的时候,正确的写法也会报语法错误:

  1. // 声明变量:
  2. var x, y;
  3. // 解构赋值:
  4. {x, y} = { name: '小明', x: 100, y: 200};
  5. // 语法错误: Uncaught SyntaxError: Unexpected token =

这是因为JavaScript引擎把{开头的语句当作了块处理,于是=不再合法。解决方法是用小括号括起来:

  1. ({x, y} = { name: '小明', x: 100, y: 200});

使用场景

解构赋值在很多时候可以大大简化代码。例如,交换两个变量xy的值,可以这么写,不再需要临时变量:

  1. var x=1, y=2;
  2. [x, y] = [y, x]

快速获取当前页面的域名和路径:

  1. var {hostname:domain, pathname:path} = location;

如果一个函数接收一个对象作为参数,那么,可以使用解构直接把对象的属性绑定到变量中。例如,下面的函数可以快速创建一个Date对象:

  1. function buildDate({year, month, day, hour=0, minute=0, second=0}) {
  2. return new Date(year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second);
  3. }

它的方便之处在于传入的对象只需要yearmonthday这三个属性:

  1. buildDate({ year: 2017, month: 1, day: 1 });
  2. // Sun Jan 01 2017 00:00:00 GMT+0800 (CST)

也可以传入hourminutesecond属性:

  1. buildDate({ year: 2017, month: 1, day: 1, hour: 20, minute: 15 });
  2. // Sun Jan 01 2017 20:15:00 GMT+0800 (CST)

使用解构赋值可以减少代码量,但是,需要在支持ES6解构赋值特性的现代浏览器中才能正常运行。目前支持解构赋值的浏览器包括Chrome,Firefox,Edge等。

原文

js之变量介绍的更多相关文章

  1. FormData js对象的介绍和使用

    FormData js对象的介绍和使用 FormData对象,可以把所有表单元素的name与value组成一个queryString,提交到后台. 在使用ajax提交时,使用FormData对象可以减 ...

  2. vue系列---Mustache.js模板引擎介绍及源码解析(十)

    mustache.js(3.0.0版本) 是一个javascript前端模板引擎.官方文档(https://github.com/janl/mustache.js) 根据官方介绍:Mustache可以 ...

  3. JS 各种引擎介绍

    JS 各种引擎介绍 http://www.oschina.net/project/tag/296/javascript-engine 不同浏览器有不同的JS引擎: WebKit , Safari浏览器 ...

  4. Python--前端基础之JavaScript(JS的引入方式,JS的变量、常量和标识符,JS的数据类型,运算符,流程控制,JavaScript的对象)

    JavaScript介绍 JavaScript是运行在浏览器端的脚步语言,JavaScript主要解决的是前端与用户交互的问题,包括使用交互与数据交互. JavaScript是浏览器解释执行的,前端脚 ...

  5. 深入理解js的变量提升和函数提升

    一.变量提升 在ES6之前,JavaScript没有块级作用域(一对花括号{}即为一个块级作用域),只有全局作用域和函数作用域.变量提升即将变量声明提升到它所在作用域的最开始的部分.上个简历的例子如: ...

  6. js之变量和作用域

    JS的变量和其他语言的变量有很大区别.JS变量时“松散型”的,决定它只是在特定时间用于保存特定的一个名字而已.由于不存在变量要保存何种数据类型,变量的值和其数据类型可以在脚本的生命周期内改变. JS两 ...

  7. Js字面变量,定义问题

    Js字面变量.浏览器的版本问题:

  8. js学习--变量作用域和作用域链

    作为一名菜鸟的我,每天学点的感觉还是不错的.今天学习闭包的过程中看到作用域与作用域链这两个概念,我觉得作为一名有追求的小白,有必要详细了解下. 变量的作用域 就js变量而言,有全局变量和局部变量.这里 ...

  9. [dart学习]第三篇:dart变量介绍 (二)

    本篇继续介绍dart变量类型,可参考前文:第二篇:dart变量介绍 (一) (一)final和const类型 如果你不打算修改一个变量的值,那么就把它定义为final或const类型.其中:final ...

随机推荐

  1. 题解 P1534 【不高兴的津津(升级版)】

    题目链接 不算太难.就是题目有歧义. wa了好几次才发现.上一天要是小于8的话.结算是昨天一个负值在加上今天课时数.再减去8.233.... 而不是上一天小于8个小时.就清零了..大家要注意(ps:题 ...

  2. Python——变量的引用和函数的参数和返回值的传递方式

    变量的引用 在python中,所有的变量都是指向地址,变量本身不保存数据,而是保存数据在内存中的地址.我们用下面的程序来理解: a = 10 print(id(a)) a = 11 print(id( ...

  3. resultType和resultMap的区别

    1.resultType和resultMap的区别 1>resultType 返回的结果类型 2>resultMap 描述如何将结果集映射到Java对象 2.resultMap节点 1&g ...

  4. SDUT OJ 数据结构实验之二叉树二:遍历二叉树

    数据结构实验之二叉树二:遍历二叉树 Time Limit: 1000 ms Memory Limit: 65536 KiB Submit Statistic Discuss Problem Descr ...

  5. [USACO07DEC]泥水坑Mud Puddles BFS BZOJ 1627

    题目描述 Farmer John is leaving his house promptly at 6 AM for his daily milking of Bessie. However, the ...

  6. 获取一个表中的字段总数(mysql) Navicat如何导出Excel格式表结构 获取某个库中的一个表中的所有字段和数据类型

    如何获取一个表中的字段总数 1.function show columns from 表明: 结果 : 2.functiuon select count(*) from INFORMATION_SCH ...

  7. 【转】idea中applicationContext-trans.xml中的Cannot resolve bean 'dataSource'...的问题解决

    问题如下: (applicationContext-trans.xml中的部分截图) 先了解问题是怎么出现的: 此处的dataSource是在applicationContext-dao.xml中配置 ...

  8. Android 使用URL访问网络资源

    1.介绍 2.使用方法 3.java后台代码 package com.lucky.test51url; import android.annotation.SuppressLint; import a ...

  9. 转载Java NIO中的Files类的使用

    Java NIO中的Files类(java.nio.file.Files)提供了多种操作文件系统中文件的方法. Files.exists() Files.exits()方法用来检查给定的Path在文件 ...

  10. npm 安装 sass=-=-=

    先按照 cnpm  .....因为外网安不上... cnpm install node-sass --save-dev cnpm install sass-loader --save-dev