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 也不属于 myObject。foo 并没有被绑定在任何对象上,所以当调用时,它的 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. C开发基础--函数调用栈

    发现有一些问题几乎是所有的新人都会遇到,而且也常因为缺乏一些基本的知识而无从下手.函数调用栈的内容就是其中之一.于是花点时间把以前写的内容整理出来. 程序在运行期间,内存中有一块区域,用来实现程序的函 ...

  2. 用JSON-server模拟REST API(一) 安装运行

    用JSON-server模拟REST API(一) 安装运行 在开发过程中,前后端不论是否分离,接口多半是滞后于页面开发的.所以建立一个REST风格的API接口,给前端页面提供虚拟的数据,是非常有必要 ...

  3. cocos2d-x类型转换(CCstring int string char UTF-8互转)

    在做数据转换时,最好包含以下头文件 #include <iostream> #include <cmath> #include <string> #include  ...

  4. JVM垃圾收集策略解析

    地址:http://developer.51cto.com/art/201002/184385_all.htm

  5. [Effective JavaScript 笔记]第4章:对象和原型--个人总结

    前言 对象是js中的基本数据结构.对象在js语言编码中也随处可见,比如经常会用到的函数,也是一个Function构造函数,Function.prototype原型对象.每当声明一个函数时,都会继承Fu ...

  6. 学习windows内核书籍推荐 ----------转自http://tieshow.iteye.com/blog/1565926

      虽然,多年java,正在java,看样子还得继续java.(IT小城,还是整java随意点)应用程序 运行于操作系统之上,  晓操作系统,方更晓应用程序. 主看windows,因为可玩性高,闭源才 ...

  7. c++字符串详解(转)

    之所以抛弃char*的字符串而选用C++标准程序库中的string类,是因为他和前者比较起来,不必担心内存是否足够.字符串长度等等,而且作为一个类出现,他集成的操作函数足以完成我们大多数情况下(甚至是 ...

  8. 用sed删除空行

    用sed删除空行 我的代码如下:class Song def initialize(name)        @name = name    end def tell        puts @nam ...

  9. asp.net动态输出透明gif图片

    要使用asp.net动态输出透明gif图片,也就是用Response.ContentType = "image/GIF". 查了国内几个中文资料都没解决,最后是在一个英文博客上找到 ...

  10. Django authentication 使用方法

    转自 : https://docs.djangoproject.com/en/1.8/topics/auth/customizing/