JavaScript采用词法作用域(lexical scoping)。
函数的执行依赖于变量作用域,这个作用域是在函数定义时决定的,而不是函数调用时决定的。
为了实现这种词法作用域,JavaScript函数对象的内部状态不仅包含函数的代码逻辑,还必须引用当前的作用域链。
函数对象可以通过作用域链相互关联起来,函数体内部的变量都可以保存在函数作用域内,这种特性在计算机科学文献中称为“闭包(closure)”。
从技术的角度讲,所有的JavaScript函数都是闭包:它们都是对象,它们都关联到作用域链。
嵌套函数
var scope = "globle scope";
function checkscope() {
var scope = "local scope";
function f() {
return scope;
}
return f;
}
var ff = checkscope();
console.log(ff());
var scope = "globle scope";
function checkscope() {
var scope = "local scope";
function f() {
return scope;
}
return f();
}
console.log(checkscope());
如果这个函数定义了嵌套的函数,并将它作为返回值返回或者存储在某处的属性里,这时就会有一个外部引用指向这个嵌套的函数。
<script>
let items = document.querySelectorAll("ul#test li");
function bindClick(nodes) {
for (var i = 0; i < nodes.length; i++) {
nodes[i].onclick = function () {
console.log(i, nodes[i].innerHTML);
};
}
}
bindClick(items);
</script>
let声明的变量识别块作用域,块作用域中声明的函数就形成了闭包。
每次循环都会创建一个新的函数,则这些函数每一个都会形成闭包。
因此,每次调用特定函数时所访问的i,就是其闭包中引用了外部函数作用域链上的i。
function bindClick(nodes) {
for (let i = 0; i < nodes.length; i++) {
nodes[i].onclick = function () {
console.log(i, nodes[i].innerHTML);
};
}
}
通过创建一个名称易懂的独立函数,调用是无需每次传入第一个参数,因为第一个参数通过bind提供了固定值。
当有一个很通用的函数,为了方便提供一个较常用的变体
let sum = function (x, y) {
return x + y;
};
let succ = sum.bind(null, 1);
// succ被称为偏函数
console.log(succ(2));
自定义偏函数示例
let jack = {
firstName: "Jack",
say(time, phrase) {
console.log(`[${time}] ${this.firstName}: ${phrase}`);
},
};
let tom = {
firstName: "Tom",
};
jack.sayNow = partial(
jack.say,
new Date().getHours() + ":" + new Date().getMinutes()
);
tom.sayNow = jack.sayNow;
jack.sayNow("Hello");
tom.sayNow("World");
实现高级柯里化函数,针对n个参数的函数
如果传递args数与原函数已经定义的参数个数一样或更长,那么直接调用func函数。
获得偏函数:否则,不调用func函数,返回另一个包装器pass,提供连接之前的参数一起做为新参数重新应用curried。然后再次执行一个新调用,返回一个新偏函数(如果参数不够)或最终结果。
function curry(func) {
return function curried(...args) {
if (args.length >= func.length) {
return func.apply(this, args);
} else {
return function pass(...args2) {
return curried.apply(this, args.concat(args2));
};
}
};
}