`

js 函数变量作用域与解析赋值

    博客分类:
  • js
阅读更多

参考:js教程 变量作用域与解析赋值

不同函数内部的同名变量互相独立,互不影响:

'use strict';

function foo() {
    var x = 1;
    x = x + 1;
}

function bar() {
    var x = 'A';
    x = x + 'B';
}

由于JavaScript的函数可以嵌套,此时,内部函数可以访问外部函数定义的变量,反过来则不行:

'use strict';

function foo() {
    var x = 1;
    function bar() {
        var y = x + 1; // bar可以访问foo的变量x!
    }
    var z = y + 1; // ReferenceError! foo不可以访问bar的变量y!
}

如果内部函数定义了与外部函数重名的变量,则内部函数的变量将“屏蔽”外部函数的变量。

    var x = 1;

    function bar() {

        var x = 'A';

        console.log('x in bar() = ' + x); // 'A'

    }

    console.log('x in foo() = ' + x); // 1

    bar();

}

 

foo();

 

变量提升

'use strict';

function foo() {
    var x = 'Hello, ' + y;
    console.log(x);
    var y = 'Bob';
}

foo();

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

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

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

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

function foo() {
    var
        x = 1, // x初始化为1
        y = x + 1, // y初始化为2
        z, i; // z和i为undefined
    // 其他语句:
    for (i=0; i<100; i++) {
        ...
    }
}

全局作用域

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

'use strict';

var course = 'Learn JavaScript';
alert(course); // 'Learn JavaScript'
alert(window.course); // 'Learn JavaScript'

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

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

'use strict';

function foo() {
    alert('foo');
}

foo(); // 直接调用foo()
window.foo(); // 通过window.foo()调用

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

 

名字空间

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

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

// 唯一的全局变量MYAPP:
var MYAPP = {};

// 其他变量:
MYAPP.name = 'myapp';
MYAPP.version = 1.0;

// 其他函数:
MYAPP.foo = function () {
    return 'foo';
};

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

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

 

局部作用域

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

'use strict';

function foo() {
    for (var i=0; i<100; i++) {
        //
    }
    i += 100; // 仍然可以引用变量i
}

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

'use strict';

function foo() {
    var sum = 0;
    for (let i=0; i<100; i++) {
        sum += i;
    }
    // SyntaxError:
    i += 1;
}

常量

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

var PI = 3.14;

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

'use strict';

const PI = 3.14;
PI = 3; // 某些浏览器不报错,但是无效果!
PI; // 3.14

解构赋值

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

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

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

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

'use strict';

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

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

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

let [x, [y, z]] = ['hello', ['JavaScript', 'ES6']];
x; // 'hello'
y; // 'JavaScript'
z; // 'ES6'

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

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

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

'use strict';

var person = {
    name: '小明',
    age: 20,
    gender: 'male',
    passport: 'G-12345678',
    school: 'No.4 middle school'
};
var {name, age, passport} = person;

 

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

var person = {
    name: '小明',
    age: 20,
    gender: 'male',
    passport: 'G-12345678',
    school: 'No.4 middle school',
    address: {
        city: 'Beijing',
        street: 'No.1 Road',
        zipcode: '100001'
    }
};
var {name, address: {city, zip}} = person;
name; // '小明'
city; // 'Beijing'
zip; // undefined, 因为属性名是zipcode而不是zip
// 注意: address不是变量,而是为了让city和zip获得嵌套的address对象的属性:
address; // Uncaught ReferenceError: address is not defined

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

var person = {
    name: '小明',
    age: 20,
    gender: 'male',
    passport: 'G-12345678',
    school: 'No.4 middle school'
};

// 把passport属性赋值给变量id:
let {name, passport:id} = person;
name; // '小明'
id; // 'G-12345678'
// 注意: passport不是变量,而是为了让变量id获得passport属性:
passport; // Uncaught ReferenceError: passport is not defined

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

var person = {
    name: '小明',
    age: 20,
    gender: 'male',
    passport: 'G-12345678'
};

// 如果person对象没有single属性,默认赋值为true:
var {name, single=true} = person;
name; // '小明'
single; // true

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

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

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

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

使用场景

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

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

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

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

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

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

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

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

也可以传入hourminutesecond属性:

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

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

 

 

 

分享到:
评论

相关推荐

    Python语言基础:作用域.pptx

    作用域作用域作用域的查找顺序新作用域的引入目录Contents01作用域作用域作用域就是一个 Python 程序可以直接访问命名空间的正文区域。Python 中,变量的访问权限决定于这个变量是在哪里赋值的。变量的作用域决定了...

    JS匿名函数、闭包

    结果就是函数内部的所有变量都会被立即销毁--除非将某些变量赋值给了包含作用域(即外部作用域)中的变量。 闭包还可以用于在对象中创建私有变量,相关概念和要点如下: 即使JavaScript中没有正式的私有对象属性...

    linux shell自定义函数(定义、返回值、变量作用域)介绍

    linux shell 可以用户定义函数,然后在shell脚本中可以随便调用。下面说说它的定义方法,以及调用需要注意那些事项

    详解js的作用域、预解析机制

    在ES5中,只存在全局和函数级作用域,在ES6中,引入了块级作用域,js的预解析机制大概分为两个过程:预解析和自上而下逐行解读 预解析:js解析器会先把var定义的变量、function、参数等一些东西存储进仓库里面(内存)。...

    Shell中变量作用域的介绍与使用命令

    作用域是我们在日常学习或者工作中经常会遇到的一个问题,下面这篇文章主要给大家介绍了关于Shell中变量作用域的相关资料,文中介绍的非常详细,需要的朋友可以参考借鉴,下面跟着小编来一起学习学习吧。

    javascript笔记之匿名函数和闭包

    本文介绍了js匿名函数和闭包的相关内容,供大家参考...闭包是指有权访问另一个函数作用域中的变量的函数,创建闭包的常见的方式,就是在一个函数内部创建另一个函数,通过另一个函数访问这个函数的局部变量 &lt;scri

    JS变量提升及函数提升实例解析

    在预解析阶段:首先会在全局作用域内,js解析器会找所有的 var 、function 、参数,并提前到当前作用域的最顶上去(变量的赋值操作不会提前,还在原来的地方),此时并没有执行代码。 然后再开始一行一行执行代码。遇到...

    python变量的作用域是什么

    变量作用域: 一般在函数体外定义的变量成为全局变量,在函数内部定义的变量称为局部变量。 全局变量所有作用域都可读,局部变量只能在本函数可读 函数在读取变量时,优先读取函数本身自有的局部变量,再去读全局...

    JavaScript函数作用域

    作用域是在运行代码是代码中的默写特定部分中的变量,函数和对象的可访问性,变量不是在所有地方都可以使用的,而这个变量的使用范围就是作用域。 局部作用域,函数内部 function fn(){ var a = 10; console.log(a...

    js中作用域的实例解析

    首先,要了解一下作用域的概念:作用–读、写,域–范围或空间。作用域:可用来进行读、写操作的范围或者空间。...预解析的过程中会自动给变量赋值undefined,即:a=undefined;而函数的值就函数本身,即:fn1=functio

    python基础2day05.txt

    函数变量 def 函数名(...): pass 函数可以作为参数传递 函数可以作为返回值返回 函数的嵌套定义 python 作用域 局部作用域 外部嵌套函数作用域 模块级的作用域(全局作用域) 内建模块的作用域 global ...

    JS变量及其作用域

    1、 变量及其作用域:变量分为“全局变量”和“局部变量”,“全局变量”申明在函数外部,可供所有函数使用,而“局部变量”申明在函数体内部,只能在定义该变量的函数体内使用。 (备注:在申明变量时没有var...

    c++作用域运算符用法(全局变量和局部变量)

    通常情况下,如果有两个同名变量,一个是全局变量,另一个是局部变量,那么局部变量在其作用域内具有较高的优先权,它将屏蔽全局变量。作用域运算符 代码如下:#...作用域运算符可以用来解决局部变量与全局变量的重

    JavaScript基础篇之变量作用域、传值、传址的简单介绍与实例

    [removed]变量的声明以下是几种声明变量的方式 代码如下: var...细节:所以大家创建变量都尽量使用Var //变量的作用域(这个问题也容易出,大家要搞明白)[removed]变量的作用域 这些都是细节,和我一样初学的一定要注

    浅谈PHP变量作用域以及地址引用问题

    作用域的概念: 在PHP脚本的任何位置都可以声明...3、静态变量:用static修饰只存在于函数作用域的变量,函数执行结束后其值并不消失。注:初始化后不能再次进行初始化,不能用表达式来赋值。 复制代码 代码如下:functi

    Python 如何访问外围作用域中的变量

    在表达式中引用变量时,Python 会按照如下的顺序遍历各个作用域,寻找该变量: 当前函数作用域 任何外围作用域(比如包含当前函数的其他函数) global 作用域,即代码所在的模块的作用域 如果上述作用域内都找不...

    javascript权威指南 学习笔记之变量作用域分享

    //声明,并且赋值,即定义了 下面是几点总结: 变量的作用域:全局的和局部的。(注意:如果尝试读取一个未声明的变量的值,javascript会生成一个错误) 第一点:在都使用var关键字修饰变量的情况下,如果给一个局部...

    关于javascript作用域的常见面试题分享

    本文主要给大家分享了关于javascript作用域面试题的相关内容,分享出来供大家参考学习,下面来一起看看吧。 一、作用域: 在了解作用域之前,首先需要明白一些基础概念: 每一个变量、函数都有其作用的范围,超出...

    深入解析Python中函数的参数与作用域

    参数的传递是通过自动将对象赋值给本地变量名来实现的。所有的参数实际上都是通过指针进行传递的,作为参数被传递的对象从来不自动拷贝。 在函数内部的参数名的赋值不会影响调用者。 改变函数的可变对象参数的值...

    ES6变量声明与赋值:值传递、浅拷贝与深拷贝详解

    ES6为我们引入了let与const两种新的变量声明关键字,同时也引入了块作用域;本文首先介绍ES6中常用的三种变量声明方式,然后讨论了JavaScript按值传递的特性以及多种的赋值方式,最后介绍了复合类型拷贝的技巧。在...

Global site tag (gtag.js) - Google Analytics