JS基础整理(1)—函数和闭包

这段空闲时间,打算整理一些JS非常基础,但是又常常搞不清记不住的知识点。

第一篇,先整理一下JS的函数,特别是闭包的知识。

  • 函数的声明方式
  • 闭包
  • 匿名函数

函数的三种定义方式

  • 函数声明:声明一个变量,把函数对象赋值给它,会提升到脚本、代码块顶部
    function f(x){ return x; }
    
  • 函数表达式:不会声明变量,不会提升
    let f = function(x){ return x; }
    
  • 箭头函数:从定义自己的环境继承this的值
    let f = (x,y)=>{ return x+y; }
    

闭包

语法作用域(lexical scoping)规则:JavaScript函数执行时,使用的是定义函数时生效的变量作用域,而不是调用函数时生效的变量作用域。

闭包(closure):函数对象与作用域组合起来解析函数变量的机制。

例子一:

let scope  = "global scope";

function checkScope(){
  let scope = "local scope":
  function f(){ return scope; }
  return f();
}
checkScope(); //返回什么?

例子一:

let scope  = "global scope";

function checkScope(){
  let scope = "local scope":
  function f(){ return scope; }
  return f;
}
checkScope()(); //返回什么?

第一个例子中,checkScope()声明一个局部变量,定义了一个返回该变量的值的函数并调用它;
第二个例子中,checkScope()中的()转移到了外部,返回的是嵌套函数,在定义它的函数外部调用这个嵌套函数。

结果:两个例子都会返回"local scope"。

分析:根据词法作用域规则,定义函数f()时,scope绑定的值是"local scope",在f()执行时仍然有效。

  • 使用匿名函数(立即调用函数表达式)创建函数的私有状态

闭包可以捕获一次函数调用的局部变量,可以将这些变量作为私有状态。

let f = (function(){
  let counter = 0; // 以下函数的私有状态
  return function() { return counter++; }
}())

f(); // 0
f(); // 1

如果同一个外部函数中有多个闭包,则它们共享相同的作用域。但是,如果使用循环创建多个闭包,需要注意一个问题:

function f(){
  let funcArray = [];
  for(var i=0; i < 10; i++){
    funcArray[i] = () => i;
  }
  return funcArray;
}

let funcs = f();
funcs[3](); // 结果是3吗?

结果:10

分析:f()中创建是10个闭包,它们共享同一个var声明的变量i,for循环结束时,i的值是10。因为闭包关联的作用域是活的,所以10个闭包都共享了10个这值。

解决:把var改成constlet,因为通过var声明的变量作用域是整个函数体,ES6后增加的let和const的作用域是块极的,每次循环都会定义一个与其它循环不同的独立作用域。

function f(){
  let funcArray = [];
  for(let i=0; i < 10; i++){
    funcArray[i] = () => i;
  }
  return funcArray;
}

let funcs = f();
funcs[3](); // 3
funcs[7](); // 7

使用闭包时还需要注意一个问题:

  • 箭头函数:会继承包含它们的函数中的this值,使用箭头函数创建的闭包可以访问外部的this值;
  • function:它的this值指向全局对象或者undefined

参考以下三个例子:

// 例子1
let o = {
  a: 1,
  m: function(){
    function f() { return this.a; }
    return f();
  }
}
o.m(); // undefined
// f()中的this => Window {0: global, window: Window, self: Window, document: document, name: "", location: Location, …}

// 例子2
let o = {
  a: 1,
  m: function(){
    f = () => this.a;
    return f();
  }
}
o.m(); // 1
// 箭头函数中的this => {a: 1, m: ƒ}

// 例子3
let o = {
  a: 1,
  m: function(){
    let self = this; // 把this的值保存在变量中,让以下嵌套函数可以访问外部this的值
    function f() { return self.a; }
    return f();
  }
}
o.m(); // 1

彩蛋

分享最近看到的一道题目:

function Foo() {
    getName = function () {
        console.log(1);
    };
    return this;
}
Foo.getName = function () {
    console.log(2);
};
Foo.prototype.getName = function () {
    console.log(3);
};
var getName = function () {
    console.log(4);
};
function getName() {
    console.log(5);
}
// 以下调用会得到什么结果?
Foo.getName();
getName();
Foo().getName();
getName();
new Foo.getName();
new Foo().getName();
new new Foo().getName();

本文章由javascript技术分享原创和收集

发表评论 (审核通过后显示评论):