作用域与闭包:this,var

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

闭包的一个坑

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

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

为了得到我们预想的结果,我们可以把 i 赋值成一个局部的变量,从而摆脱外层迭代的影响。

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

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

时间: 2024-10-06 04:26:21

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

作用域与闭包:this,var,(function () {})

this 在函数执行时,this 总是指向调用该函数的对象.要判断 this 的指向,其实就是判断 this 所在的函数属于谁. 在<javaScript语言精粹>这本书中,把 this 出现的场景分为四类,简单的说就是: 有对象就指向调用对象 没调用对象就指向全局对象 用new构造就指向新对象 通过 apply 或 call 或 bind 来改变 this 的所指. 作用域与闭包:this,var,(function () {})

关于var函数作用域,闭包与let的区别应用,(前端网备份)

var a = [];for (var i = 0; i < 10; i++) {a[i] = (function(j){return function(){ console.log(j); };})(i);}a[7](); var b = []; for (let i = 0; i < 10; i++) { b[i] = function(){ console.log(i); }; } b[7](); var pubvar = 1; 这两个结果是一样的 关于闭包的varfunction cr

JavaScript函数,作用域以及闭包

JavaScript函数,作用域以及闭包 1. 函数 (1). 函数定义:函数使用function关键字定义,它可以用在函数定义表达式或者函数声明定义. a. 函数的两种定义方式: * function functionName() {} * var functionName = function(){} b. 两种函数定义不同之处 1). 声明提前问题 函数声明语句   :声明与函数体一起提前 函数定义表达式 :声明提前,但是函数体不会提前 请看下面图示:绿色线上面实在js初始加载的时候,查看

JavaScript从作用域到闭包

作用域(scope) 全局作用域和局部作用域 通常来讲这块是全局变量与局部变量的区分. 参考引文:JavaScript 开发进阶:理解 JavaScript 作用域和作用域链 全局作用域:最外层函数和在最外层函数外面定义的变量拥有全局作用域. 1)最外层函数和在最外层函数外面定义的变量拥有全局作用域 2)所有末定义直接赋值的变量自动声明为拥有全局作用域,即没有用var声明的变量都是全局变量,而且是顶层对象的属性. 3)所有window对象的属性拥有全局作用域 局部作用域:和全局作用域相反,局部作

JavaScript【5】高级特性(作用域、闭包、对象)

笔记来自<Node.js开发指南>BYVoid编著 1.作用域 if (true) { var somevar = 'value'; } console.log(somevar); JavaScript的作用域完全是由函数决定的,if.for语句中的花括号不是独立的作用域. 1.1.函数作用域 作用域是通过函数来定义的,在一个函数中定义的变量只对这个函数内部可见,我们称为函数作用域.在函数中引用一个变量时,JavaScript会先搜索当前函数作用域,或者称为"局部作用域",

JS中的作用域和闭包

作用域:在编程语言中,作用域控制着变量与参数的可见性及生命周期.JS确实有函数作用域,那意味着定义在函数中的参数和变量在函数外部是不可见的,而且在一个函数中的任何位置定义的变量在该函数中的任何地方都是可见的. var a = 1; var fs = function (){ var b = 2; var c = 4 var fun = function (){ var c = 3; alert(a) //输出1 alert(b) //输出2 alert(c) //输出3 } fun(); } f

函数放到onload里面,在html里面执行函数会报错-----作用域和闭包相关问题

<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <title>修改显示隐藏</title> <meta name="description" content

JS作用域与闭包--实例

//函数作用域 function func(){ var arr = [1,3,5,7,9]; var sum = 0; for(var i = 0,len = arr.length;i < len;i ++){ sum += arr[i]; } console.log("%d\t\n%d",i,sum); } func(); //5 //25 //闭包特性 var outter = []; function clouseTest(){ var arr = ["one&

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

作用域和闭包 ?作用域 引擎:从头到尾负责整个JavaScript的编译及执行过程. 编译器:负责语法分析及代码生成等. 作用域:负责收集并维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限. 作用域是一套规则,用于确定在何处以及如何查找变量(标识符). 如果查找的目的是对变量进行赋值,那么就会使用LHS查询: 如果目的是获取变量的值,就会使用RHS查询. ?词法作用域 无论函数在哪里被调用,也无论它如何被调用,它的词法作用域都只由

js最基础知识回顾3(字符串拼接,数据类型,变量类型,变量作用域和闭包,运算符,流程控制,)

一.javaScript组成     1.ECMAScript:解释器.翻译 ---------------------------------------------------------几乎没有兼容性问题     2.DOM:Document Object Model --------操作HTML的能力----document--------有一些兼容性问题     3.BOM:Browser Object Model -------------浏览器---------------wind