作用域
作用域决定了你的代码里的变量和其他资源在各个区域中的可见性,为代码提供了一个安全层级,用户只能访问他们当前需要的东西。
在 JavaScript 中有两种作用域:
全局作用域:定义在函数之外的变量会被保存在全局作用域中,在代码的任何地方都是可访问的。
局部作用域(函数作用域):在函数内定义的变量(局部变量)或者函数的参数,只在当前函数体内以及这个函数体嵌套的任意函数体可访问,函数外部不能访问。
访问当前作用域的变量速度比访问其他作用域的快,因为会顺着作用域链查找,直到找到你要的或者没有结果。
在一个函数中,如果局部变量和全局变量同名,局部变量会覆盖全局变量。在函数体内访问这个变量,是局部变量的值。所以,在不同的作用域,可以命名相同的变量而不导致冲突,解决了不同范围的同名变量命名问题
var num = 1; //声明一个全局变量 function func() { var num = 2; //声明一个局部变量 return num; } console.log(func()); //输出:2
局部变量在函数开始执行时创建,函数执行完后局部变量会自动销毁。(当通过闭包在函数外面引用了局部变量,当函数执行完,局部变量不会被销毁)
一个应用中全局作用域的生存周期与该应用相同。局部作用域只在该函数调用执行期间存在。
变量声明提前
变量在整个函数体内都是有定义的,所以在赋值前是能访问的,即变量声明提升到当前函数体顶部(要是当前函数嵌套了其他函数,其他函数中声明的变量,由于作用域链的关系,其他函数体的变量,在当前函数体内是不可访问的,不存在声明提升到当前函数体的说法)
function func() { console.log(num); //输出:undefined,而非报错,因为变量num在整个函数体内都是有定义的 var num = 1; //声明num 在整个函数体func内都有定义 console.log(num); //输出:1
} func();
作用域链
从当前作用域出发,决定了哪些数据能被访问,不讲大道理,直接画图吧。
比如下图:想在函数3里访问变量a,过程是:先在函数3里找,没有就在函数2里找,再没有就在函数1里找,直到找到全局作用域,看是否有这么个变量a,是不是像顺着一根链子找呢,这个就是作用域链。(上一层的作用域也叫做父级作用域)
ECMAScript 6 引入了let和const关键字。这些关键字可以代替var。
let likes = 'Coding';
const skills = 'Javascript and PHP';
和var关键字不同,let和const关键字支持在块级声明中创建使用局部作用域。
if (true) { var name = 'Hammad'; let likes = 'Coding';、 const skills = 'JavaScript and PHP'; } console.log(name); // logs 'Hammad' console.log(likes); // Uncaught ReferenceError: likes is not defined console.log(skills); // Uncaught ReferenceError: skills is not defined
上下文
上下文指的是在相同的作用域中的this的值。可以使用函数方法改变上下文,上下文是执行的时候确定的。
在全局作用域中,上下文总是 Window 对象。
作为一个对象的方法,上下文就是这个方法所在的那个对象。
使用new关键字调用函数时上下文,上下文会设置为被调用的函数的实例
当在严格模式(strict mode)中调用函数时,上下文默认是 undefined。
使用 .call(), .apply() 和 .bind() 改变上下文
Call 和 Apply 函数来改变函数调用时的上下文。
context={a:1,y:2};
function hello(a,b) {
alert(this.a);
alert(a);
alert(b);
}
hello();
hello.call(context,"cc","dd"); //1,cc,dd
hello.apply(context,["cc","dd"]); //
Bind 并不是自己调用函数,它只是在函数调用之前绑定上下文和其他参数。
(function introduce(name, interest) {
console.log('Hi! I'm '+ name +' and I like '+ interest +'.');
console.log('The value of this is '+ this +'.')
}).bind(window, 'Hammad', 'Cosmology')();
函数的运行过程
第一阶段是创建阶段,是函数刚被调用但代码并未执行的时候。创建阶段主要发生了 3 件事。
创建变量对象
创建作用域链
设置上下文(this)的值
第二个阶段就是代码执行阶段,进行其他赋值操作并且代码最终被执行。
词法作用域
词法作用域(静态作用域)的意思是在函数嵌套中,内层函数可以访问父级作用域的变量等资源,在定义时就确定。
闭包
当内部函数试着访问外部函数的作用域链(词法作用域之外的变量)时产生闭包。闭包包括它们自己的作用域链、父级作用域链和全局作用域。
闭包不仅能访问外部函数的变量,也能访问外部函数的参数。
即使函数已经return,闭包仍然能访问外部函数的变量。这意味着return的函数允许持续访问外部函数的所有资源。
当你的外部函数return一个内部函数,调用外部函数时return的函数并不会被调用。你必须先用一个单独的变量保存外部函数的调用,然后将这个变量当做函数来调用。
用闭包实现共有作用域和私有作用域:
(function () { // private scope })();
函数结尾的括号告诉解析器立即执行此函数。我们可以在其中加入变量和函数,外部无法访问。但如果我们想在外部访问它们,也就是说我们希望它们一部分是公开的,一部分是私有的。我们可以使用闭包的一种形式,称为模块模式(Module Pattern),它允许我们用一个对象中的公有作用域和私有作用域来划分函数。
模块模式
var Module = (function() { function privateMethod() { // do something } return { publicMethod: function() { // can call privateMethod(); } }; })(); Module.publicMethod(); // works Module.privateMethod(); // Uncaught ReferenceError: privateMethod is not defined
Module 的return语句包含了我们的公共函数。私有函数并没有被return。函数没有被return确保了它们在 Module 命名空间无法访问。但我们的共有函数可以访问我们的私有函数,方便它们使用有用的函数、AJAX 调用或其他东西。
一种习惯是以下划线作为开始命名私有函数,并返回包含共有函数的匿名对象。这使它们在很长的对象中很容易被管理。向下面这样:
var Module = (function () { function _privateMethod() { // do something } function publicMethod() { // do something } return { publicMethod: publicMethod, } })();
立即执行函数表达式(IIFE)
另一种形式的闭包是立即执行函数表达式(Immediately-Invoked Function Expression,IIFE)。这是一种在 window 上下文中自调用的匿名函数,也就是说this的值是window。它暴露了一个单一全局接口用来交互。如下所示:
(function(window) { // do anything })(this);
两段好玩的闭包代码
1、返回一个函数
function createComparisonFunction(propertyName) { return function(object1, object2){
var value1 = object1[propertyName];
var value2 = object2[propertyName];
if (value1 < value2){
return -1;
} else if (value1 > value2){
return 1;
} else {
return 0;
}
}; } var compare = createComparisonFunction("name");
//createComparisonFunction函数返回后,闭包的作用域链开始初始化,包含了Comparison作用链上的对象;Comparison返回后作用域链被销毁,但是上面的活动对象内存得不到释放,直到匿名函数被销毁 var result = compare({ name: "Nicholas" }, { name: "Greg" }); compare=' ';//解除对匿名函数的引用,释放内存。此时result已经达到目的指向了需要引用的函数
2,循环引用造成的内存泄漏
var el = document.getElementById('MyElement'); var func = function () { //… } el.func = func; func.element = el; //通常循环引用发生在为dom元素添加闭包作为expendo的时候。 function init() { var el = document.getElementById('MyElement'); el.onclick = function () { //…… } } init(); 解除引用: function init() { var el = document.getElementById('MyElement'); el.onclick = function () { //…… } el = null; } init();