`

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等。 

 

 

 

分享到:
评论

相关推荐

    javascript变量作用域

    JavaScript 变量作用域详解 JavaScript 变量作用域是基于其特有的作用域链的。在 JavaScript 中,变量作用域是指变量可以被访问和修改的范围。 JavaScript 没有块级作用域,而是基于函数作用域和全局作用域的。 ...

    js变量作用域

    ### JavaScript 变量作用域详解 #### 一、引言 在探讨JavaScript的面向对象特性之前,理解变量作用域的概念至关重要。本篇文章旨在通过一系列示例和解释,帮助读者掌握JavaScript中的变量作用域机制。 #### 二、...

    深入理解变量作用域

    本文将从JavaScript权威指南出发,深入探讨变量作用域的相关知识点,包括全局作用域、局部作用域、以及闭包等高级概念。 #### 二、全局作用域与局部作用域 1. **全局作用域** - 定义:在JavaScript中,如果一个...

    php变量作用域的深入解析

    4. 静态变量作用域:在函数内定义且仅在函数内访问,但其值在函数调用间保持不变。 5. 环境变量作用域:通过$_ENV数组访问的服务器环境变量。 了解这些作用域的规则对于编写高效且易于维护的PHP代码至关重要。在...

    学习python中变量作用域及嵌套作用域.pdf

    外部作用域是指当前函数或代码块的外部函数或代码块中的变量作用域。外部作用域中的变量优先级次于局部作用域,但高于全局作用域和内置作用域。 全局作用域是指当前模块中的变量作用域。全局作用域中的变量优先级...

    python中变量作用域及嵌套作用域.pdf

    Python 中变量作用域及嵌套作用域 Python 中的变量作用域是指变量的可见范围和生命周期。变量作用域可以分为四个级别:局部作用域(Local Scope)、外部作用域(Enclosing Scope)、全局作用域(Global Scope)和内...

    JavaScript程序设计-变量作用域.pdf

    JavaScript是一种广泛用于网页和网络应用的编程语言,其核心特性之一就是变量作用域。变量作用域决定了变量在何处可以被访问以及何时会被销毁。本文将深入探讨JavaScript中的全局变量、局部变量、变量提升、词法作用...

    JavaScript 基础函数_深入剖析变量和作用域

    JavaScript是一种广泛用于网页开发的脚本语言,它的函数和作用域是实现代码逻辑结构和模块...理解JavaScript中的变量作用域非常重要,它有助于避免变量命名冲突和意外的变量修改,从而编写出更加健壮和易于维护的代码。

    深入理解js函数的作用域与this指向.docx

    JavaScript中的函数作用域和`this`指向是编程中至关重要的概念,它们决定了代码中变量的可见性和`this`关键字的绑定。以下是对这两个主题的详细解释: ### 函数作用域 函数作用域决定了变量在何处可以被访问。...

    JavaScript:函数与作用域

    ### JavaScript:函数与作用域 #### 一、函数基础 **1.1 函数声明与表达式** 在JavaScript中,函数是一段可重用的代码块,用来执行特定任务。函数可以通过两种方式进行定义:函数声明和函数表达式。 - **函数...

    java变量的生命周期与作用域

    方法作用域是指变量定义在方法内部的作用域,块作用域是指变量定义在块内部的作用域,类作用域是指变量定义在类内部的作用域,而实例作用域是指变量定义在实例内部的作用域。 在 Java 中,变量的生命周期和作用域是...

    3.8 函数参数与变量的作用域(ppt).zip

    总之,掌握函数参数和变量作用域是编程基础的重要部分。通过合理使用它们,我们可以编写出高效、可读且易于维护的代码。在实际开发中,理解这些概念有助于避免常见的编程陷阱,并提升代码质量。

    深入理解JavaScript作用域和作用域链

    全局作用域是指在代码的任何位置都可以访问的变量或函数,这通常包括在最外层函数之外定义的变量、未声明直接赋值的变量(它们会自动声明为全局)以及window对象的属性。全局作用域的变量在整个脚本或模块中都是可...

    javascript函数作用域学习示例(js作用域)

    JavaScript中的作用域是一个非常核心的概念,它决定了变量和函数的可见性和生命周期。在学习JavaScript作用域之前,我们需要先了解在其他编程语言中常见的块级作用域(block scope)。在像C语言这样的编程语言中,每...

    javascript的函数作用域

    JavaScript 函数作用域知识点详解: JavaScript 是一种基于函数作用...掌握 JavaScript 的函数作用域对于编写高效、可维护的代码至关重要,尤其是在涉及到变量和函数命名时要非常小心,避免命名冲突和不必要的副作用。

    javascript 函数及作用域总结介绍

    在JavaScript中,如果同一个作用域内存在同名的函数和变量,在执行到变量赋值之前,同名函数会生效,而变量赋值之后,变量会覆盖函数。这种行为可能导致难以追踪的bug,特别是在使用第三方库和复杂的项目中。 以上...

    第六章:js解析顺序和作用域1

    JavaScript是一种动态类型的编程语言,它的解析顺序和作用域是理解其行为的关键概念。在JavaScript中,代码的解析分为两个主要阶段:定义阶段和执行阶段。 一、JavaScript解析顺序 1. 定义阶段: 在这个阶段,...

    js-作用域-变量申明提升 - 甘劭 - 博客园1

    JavaScript中的作用域和变量声明提升是理解JS代码执行的关键概念。首先,作用域是指变量或函数可以在哪些区域中被访问的限制。JavaScript主要有两种作用域:全局作用域和函数作用域,而没有像C#、C、Java那样的块级...

    预解析&作用域1

    作用域是 JavaScript 中另一个重要的概念,它是指变量或函数在程序中的可访问范围。在 JavaScript 中,我们主要讨论两种作用域:全局作用域和函数作用域。 全局作用域是指在程序开头的说明部分定义和说明的量。它的...

    JavaScript基础语法+变量与数据类型+流程控制:条件语句与循环+函数与作用域+对象与原型链全套教程

    函数与作用域 对象与原型链 数组与字符串操作 DOM操作与事件处理 JavaScript面向对象编程 异步编程:回调函数与Promise 模块化编程与CommonJS ES6新特性:箭头函数,let与const ES6新特性:模板字符串与解构赋值 ES6...

Global site tag (gtag.js) - Google Analytics