var 作用域

先来看个简单的例子:

var parent = function () {
var name = "parent_name";
var age = 13; var child = function () {
var name = "child_name";
var childAge = 0.3; // => child_name 13 0.3
console.log(name, age, childAge);
}; child(); // will throw Error
// ReferenceError: childAge is not defined
console.log(name, age, childAge);
}; parent();

直觉地,内部函数可以访问外部函数的变量,外部不能访问内部函数的变量。上面的例子中内部函数 child 可以访问变量 age,而外部函数 parent 不可以访问 child 中的变量 childAge,因此会抛出没有定义变量的异常。

有个重要的事,如果忘记var,那么变量就被声明为全局变量了。

function foo() {
value = "hello";
}
foo();
console.log(value); // 输出hello
console.log(global.value) // 输出hello

这个例子可以很正常的输出 hello,是因为 value 变量在定义时,没有使用 var 关键词,所以被定义成了全局变量。在 Node 中,全局变量会被定义在 global 对象下;在浏览器中,全局变量会被定义在 window 对象下。

如果你确实要定义一个全局变量的话,请显示地定义在 global 或者 window 对象上。

这类不小心定义全局变量的问题可以被 jshint 检测出来,如果你使用 sublime 编辑器的话,记得装一个 SublimeLinter 插件,这是插件支持多语言的语法错误检测,js 的检测是原生支持的。

JavaScript 中,变量的局部作用域是函数级别的。不同于 C 语言,在 C 语言中,作用域是块级别的。 JavaScript 中没有块级作用域。

js 中,函数中声明的变量在整个函数中都有定义。比如如下代码段,变量 i 和 value 虽然是在 for 循环代码块中被定义,但在代码块外仍可以访问 i 和 value。

function foo() {
for (var i = 0; i < 10; i++) {
var value = "hello world";
}
console.log(i); //输出10
console.log(value);//输出hello world
}
foo();

所以有种说法是:应该提前声明函数中需要用到的变量,即,在函数体的顶部声明可能用到的变量,这样就可以避免出现一些奇奇怪怪怪的 bug。

但我个人不喜欢遵守这一点,一般都是现用现声明的。这类错误的检测交给 jshint 来做就好了。

闭包

闭包这个概念,在函数式编程里很常见。

闭包是代码块和创建该代码块的上下文中数据的结合。

假如我们要实现一系列的函数:add10,add20,它们的定义是 int add10(int n)

为此我们构造了一个名为 adder 的构造器,如下:

var adder = function (x) {
var base = x;
return function (n) {
return n + base;
};
}; var add10 = adder(10);
console.log(add10(5)); var add20 = adder(20);
console.log(add20(5));

每次调用 adder 时,adder 都会返回一个函数给我们。我们传给 adder 的值,会保存在一个名为 base 的变量中。由于返回的函数在其中引用了 base 的值,于是 base 的引用计数被 +1。当返回函数不被垃圾回收时,则 base 也会一直存在。

我暂时想不出什么实用的例子来,如果想深入理解这块,可以看看这篇 http://coolshell.cn/articles/6731.html

闭包的一个坑

for (var i = 0; i < 5; i++) {
setTimeout(function () {
console.log(i);
}, 5);
}

上面这个代码块会打印五个 5 出来,而我们预想的结果是打印 0 1 2 3 4。

之所以会这样,是因为 setTimeout 中的 i 是对外层 i 的引用。当 setTimeout 的代码被解释的时候,运行时只是记录了 i 的引用,而不是值。而当 setTimeout 被触发时,五个 setTimeout 中的 i 同时被取值,由于它们都指向了外层的同一个 i,而那个 i 的值在迭代完成时为 5,所以打印了五次 5

由于上面的 i 是引用了for循环最后的 i 值,为了得到我们预想的结果,我们可以把 i 赋值成一个局部的变量,从而摆脱外层迭代的影响。

方法一:
for (var i = 0; i < 5; i++) {
(function (idx) {
setTimeout(function () {
console.log(idx);
}, 5);
})(i);
}
方法二:
for (var i = 0; i < 5; i++) {
setTimeout(function (i) {
console.log(i);
}, 5, i);
} 方法三:
for (let i = 0; i < 5; i++) {
setTimeout(function () {
console.log();
}, 5);
}

this

在函数执行时,this 总是指向调用该函数的对象。要判断 this 的指向,其实就是判断 this 所在的函数属于谁。

在《javaScript语言精粹》这本书中,把 this 出现的场景分为四类,简单的说就是:

  • 有对象就指向调用对象
  • 没调用对象就指向全局对象
  • 用new构造就指向新对象
  • 通过 apply 或 call 或 bind 来改变 this 的所指。

1)函数有所属对象时:指向所属对象

函数有所属对象时,通常通过 . 表达式调用,这时 this 自然指向所属对象。比如下面的例子:

var myObject = {value: 100};
myObject.getValue = function () {
console.log(this.value); // 输出 100 // 输出 { value: 100, getValue: [Function] },
// 其实就是 myObject 对象本身
console.log(this); return this.value;
}; console.log(myObject.getValue()); // => 100

getValue() 属于对象 myObject,并由 myOjbect 进行 . 调用,因此 this 指向对象 myObject

2) 函数没有所属对象:指向全局对象

var myObject = {value: 100};
myObject.getValue = function () {
var foo = function () {
console.log(this.value) // => undefined
console.log(this);// 输出全局对象 global
}; foo(); return this.value;
}; console.log(myObject.getValue()); // => 100

在上述代码块中,foo 函数虽然定义在 getValue 的函数体内,但实际上它既不属于 getValue 也不属于 myObjectfoo 并没有被绑定在任何对象上,所以当调用时,它的 this 指针指向了全局对象 global

据说这是个设计错误。

这个完全可以通过闭包来解决,如下:

var myObject = {value: 100};
myObject.getValue = function () {
var self = this;
var foo = function () {
console.log(self.value); // => 100
//console.log(this);// 输出全局对象 global
}; foo(); return this.value;
}; console.log(myObject.getValue()); // => 100

3)构造器中的 this:指向新对象

js 中,我们通过 new 关键词来调用构造函数,此时 this 会绑定在该新对象上。

var SomeClass = function(){
this.value = 100;
} var myCreate = new SomeClass(); console.log(myCreate.value); // 输出100

顺便说一句,在 js 中,构造函数、普通函数、对象方法、闭包,这四者没有明确界线。界线都在人的心中。

4) apply 和 call 调用以及 bind 绑定:指向绑定的对象

apply() 方法接受两个参数第一个是函数运行的作用域,另外一个是一个参数数组(arguments)。

call() 方法第一个参数的意义与 apply() 方法相同,只是其他的参数需要一个个列举出来。

简单来说,call 的方式更接近我们平时调用函数,而 apply 需要我们传递 Array 形式的数组给它。它们是可以互相转换的。

var myObject = {value: 100};

var foo = function(){
console.log(this);
}; foo(); // 全局变量 global
foo.apply(myObject); // { value: 100 }
foo.call(myObject); // { value: 100 } var newFoo = foo.bind(myObject);
newFoo(); // { value: 100 }

参考:https://github.com/alsotang/node-lessons/tree/master/lesson11

作用域与闭包:this,var的更多相关文章

  1. js闭包的作用域以及闭包案列的介绍:

    转载▼ 标签: it   js闭包的作用域以及闭包案列的介绍:   首先我们根据前面的介绍来分析js闭包有什么作用,他会给我们编程带来什么好处? 闭包是为了更方便我们在处理js函数的时候会遇到以下的几 ...

  2. 【javascript】作用域和闭包浅析

    作用域 分全局作用域和局部作用域 全局作用域:函数外部定义的变量,可以被整个program的各成员参照利用. 局部作用域:函数内部定义的变量,仅供该函数的各成员参照利用. var val=1; //全 ...

  3. 剖析JavaScript函数作用域与闭包

    在我们写代码写到一定阶段的时候,就会想深究一下js,javascript是一种弱类型的编程语言,而js中一个最为重要的概念就是执行环境,或者说作用域.作用域重要性体现在哪呢?首先,函数在执行时会创建作 ...

  4. JS教程:词法作用域和闭包 (网络资源)

    varclassA = function(){ ; } classA.prototype.func1 = function(){ var that = this, ; function a(){ re ...

  5. 《你不知道的JavaScript》第一部分:作用域和闭包

    第1章 作用域是什么 抛出问题:程序中的变量存储在哪里?程序需要时,如何找到它们? 设计 作用域 的目的:为了更好地存储和访问变量. 作用域:根据名称查找变量的一套规则,用于确定在何处以及如何查找变量 ...

  6. JS作用域与闭包--实例

    <script> "use strict" //函数作用域 function func(){ var arr = [1,3,5,7,9]; var sum = 0; f ...

  7. 你不知道的JavaScript(作用域和闭包)

    作用域和闭包 ・作用域 引擎:从头到尾负责整个JavaScript的编译及执行过程. 编译器:负责语法分析及代码生成等. 作用域:负责收集并维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非 ...

  8. JavaScript 函数作用域和闭包

    函数作用域和闭包  词法作用域   它们在定义它们的作用域里运行,而不是在执行的作用域运行,但是只有在运行时,作用域链中的属性才被 定义(调用对象),此时,可访问任何当前的绑定.   调用对象     ...

  9. 《JavaScript 闯关记》之作用域和闭包

    作用域和闭包是 JavaScript 最重要的概念之一,想要进一步学习 JavaScript,就必须理解 JavaScript 作用域和闭包的工作原理. 作用域 任何程序设计语言都有作用域的概念,简单 ...

  10. JavaScript从作用域到闭包

    目录 作用域 全局作用域和局部作用域 块作用域与函数作用域 作用域中的声明提前 作用域链 函数声明与赋值 声明式函数.赋值式函数与匿名函数 代码块 自执行函数 闭包  作用域(scope) 全局作用域 ...

随机推荐

  1. thinkphp中F方法

    我们已经了解了ThinkPHP中的S方法的用法,F方法其实是S方法的一个子集功能,仅用于简单数据缓存,并且只能支持文件形式,不支持缓存有效期,因为采用的是PHP返回方式,所以其效率较S方法较高,因此我 ...

  2. SQL例题合集

    1. 查询Student表中的所有记录的Sname.Ssex和Class列. Select sname,ssex,class from student; 2. 查询教师所有的单位即不重复的Depart ...

  3. sql中文字符串获取拼音首字母

    SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO )) ) as begin ),) set @PY='' begin ) --如果非汉字字符,返回原字 ...

  4. Eclipse的模板设置代码

    Eclipse Java注释模板设置详解   设置注释模板的入口: Window->Preference->Java->Code Style->Code Template 然后 ...

  5. [BZOJ4636]蒟蒻的数列

    [BZOJ4636]蒟蒻的数列 试题描述 蒟蒻DCrusher不仅喜欢玩扑克,还喜欢研究数列 题目描述 DCrusher有一个数列,初始值均为0,他进行N次操作,每次将数列[a,b)这个区间中所有比k ...

  6. poj2485 Highways

    Description The island nation of Flatopia is perfectly flat. Unfortunately, Flatopia has no public h ...

  7. HDOJ 2544

    最短路 Time Limit: 5000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submis ...

  8. 【Python】Django 支持 restful 风格 url

    URL通配符示例: url(r'^file_download/(?P<filename>(.)*)$', views.FILE_DOWNLOAD_VIEW.as_view()), 代码示例 ...

  9. 什么是iis服务器

    IS 是Internet Information Server的缩写,它是微软公司主推的服务器,最新的版本是Windows2003里面包含的IIS 6.0,IIS与WindowNT Server完全集 ...

  10. 37.寻找丑数[Ugly numbers]

    [题目] 我们把只包含质因子2.3和5的数称作丑数(Ugly Number),例如:2,3,4,5,6,8,9,10,12,15,等,习惯上我们把1当做是第一个丑数.求按从小到大的顺序的第1500个丑 ...