作用域、闭包和this

作用域、闭包和this
使用 let 实现块级作用域
作用域就是变量与函数的可访问范围,
即控制着变量与函数的可见性与生命周期
var scope = 'global';
function foo() {
var scope = 'local';
console.log(scope);
}
foo();
console.log(scope);
块级作用域,指在一个代码块中所定义的变量,代码一般以花括号包裹,在这之中定义的变量在代码块之外不可见。
JavaScript中,从语法层面看似乎支持这种块级作用域,但是在es6之前并不存在这种作用域
for (var scope = 0; scope < 5; scope++ ) {

}
console.log(scope);
        输出结果为5
        变量scope被定义在for循环语句之内,在循环之外引用scope变量由于缺少了块级作用域,变量被扩散到全局作用域中,因此可以正常输出
        缺少块级作用域不仅会导致代码块执行完毕后变量不会被回收,并且由于定义在代码块中的变量被扩散到上一层作用域中,还会导致另外一种称之为变量提升的副作用
    // 变量提升
if (!('a' in window)) {
    var a =1;
}
console.log(a); //undefined
        变量a不仅仅存在于if代码块中,由于不存在块级作用域,变量a的声明被置于全局作用域,因此a始终存在于window对象,if语句内的赋值并不会被执行
    let声明的变量只存在于其所在的代码块中
            // let
function foo() {
    let scope = 'function';
    if (true) {
        let  scope = 'block';
        console.log(scope);
    }
    console.log(scope);
}
foo();
    使用let命令定义的变量,并不会产生声明前置的效果,表示变量必须在声明后才能使用

未声明前的引用会导致错误,在语法上称之为“暂时性死区” TDZ
console.log(typeof a); //undefined
var a = 1;
console.log(typeof b); //ReferenceError
let b = 1;
声明提升现象也存在于函数,而es6规定函数本身的作用域在其块级作用域之内,这使得函数声明于let命令产生了类似的效果
闭包
JavaScript查找变量即变量解析的过程,先从当前定义的局部作用域中查找,如果未发现,就会查找上一层作用域
由于JavaScript是基于静态作用域的语言,静态作用域的含义是在函数定义时就确定了作用域,而不是函数执行时再确定
function outer() {
var scope = 10;
return function inner() {
scope +=10;
console.log(scope);
}
}

var scope = 100;
var fn = outer();
fn();                        //输出20
        输出结果为20,而不是110。

这是由于变量scope被定义为outer函数的局部变量。
而在inner函数定义时即确定了其作用域,并引用了其外部函数outer的局部变量scope。
inner函数作为outer函数的执行结果被返回,当outer函数在执行完毕后,定义在其内部的变量scope并没有被回收,而是可以通过函数fn的执行被获取
在这里inner函数,即形成了闭包
闭包是词法闭包的简称,是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。

所以,有另一种说法认为闭包是由函数与其相关的引用环境组合而成的实体。
构成闭包需要有对上下文词法作用域中变量的引用,并在外部函数执行完毕时,被引用的变量并不会被垃圾回收器回收。
var scope = 10;
function calculate(addend) {console.log(scope + addend);}
(function (ca) {
var scope =100;
ca(5)
}) (calculate); //输出显示15
在JavaScript中,函数是第一类对象,这让JavaScript函数可以被存入变量或其他结构,也可以被作为其他函数的返回值,或者被作为参数传递给其他函数
上面的calculate函数作为参数传递给自执行函数时,同样出现了闭包的身影
将函数作为其他函数的返回值和作为参数传递时,常常会不经意间创建闭包,如在使用定时器、事件监听或ajax回调函数时
闭包导致内存未释放的情况

这让闭包可以用来存放中间计算结果,类似实现了计算结果的缓存
// 使用闭包函数计算斐波那契数列
var count = 0;
fib = (function () {
var arr = [0, 1, 1]; //前3位直接返回
return function (n) {
count++;
var res = arr[n]; //arr一直在内存中
if (res) {
return res;
} else {
arr[n] = fib(n - 1) + fib(n - 2);
return arr[n];
}
}
}) ();
console.log(fib(10)); // 结果 55
闭包的另一个使用场景
实现内部变量的封装,即使用匿名函数封装私有成员的单例模式,也称作模块模式。
YAHOO.Util = function () {
var privateVar = ''; //私有属性只能内部访问
var privateMethod = function () { //私有方法只能内部访问
//TODO

    };
    return {
        PublicProperty: '',                 //暴露对外的公共属性
        PublicMethod: function () {         //暴露对外的公共方法可以访问内部属性和方法
            //TODO

        }
    };
} ();
    滥用闭包也会导致一些诸如内存泄漏的性能问题

合理使用好闭包是成为一个优秀前端工程师的必备要求
采用 call、 apply、 bind 改变 this
JavaScript中的 this 关键字表示函数运行时生成的内部对象。和变量的搜索过程不同, this 的值从执行上下文中获取,而不会在作用域链中搜寻。在面向对象的程序设计语言中,this关键字指代当前对象,通常在编译期确定,称为编译期绑定,而在JavaScript中, this 是动态绑定的,称为运行期绑定。

this 可以是全局对象、当前对象或者任意对象,这完全取决于函数的调用方式,不同的调用方式会产生不同的执行上下文,也就会有不同的this 含义
函数的调用方式
作为函数调用
作为对象方法调用
作为构造函数调用
call调用
apply调用
var name = 'tiger';
function Animal() {
var name = 'cat';
console.log(this.name);
}
Animal(); //输出结果为 tiger
这个例子中this并不在函数作用域中查找 ‘name’ 对象, Animal函数在全局上下文中执行,因此Animal内部的this指向全局对象。
该全局对象在浏览器环境中即 window对象,在全局作用域下使用 var 定义的变量会定义在 window 对象下,因此执行Animal 函数得到 this.name 的值为 ‘tiger’。
var name = 'tiger';
function Animal() {
console.log(this.name);
}
Animal.call({name: 'lion'}); //输出结果为 lion
当使用call 调用函数时, this 被指向传入的对象, this.name 的值为 'lion',使用apply 也可以达到相同的效果。
ES5新增的 bind方法,在实现上同 call 和 apply 稍有不同,调用bind 方法会返回一个新的函数,被称为绑定函数。当调用绑定函数时,会以创建时传入的第一个参数作为this指向的对象。
var name = 'tiger';
function Animal() {
console.log(this.name);
}
Animal.bind({name: 'lion'}) (); //输出结果为 lion
由于 call、apply、bind 可以改变执行上下文的特性,所以开发者可以使用基础类型对象内置的原型方法
调用Object原型对象上的toString方法判断对象类型
console.log(Object.prototype.toString.call([]) === '[object Array]'); //true
类数组对象使用数组方法
function Add() {
return [].slice.call(arguments).reduce((a, b) => a +b);
}
var result = Add(1, 3, 5, 7, 9);
console.log(result); //返回结果 25
注意
call、apply、bind方法的第一个参数都是 this指向的对象,在‘’非严格模式‘’下,如果传入的对象是 null 或者 undefined, 则this指向全局对象。

call和apply在实现效果上并没有什么不同,只是参数格式略有差异

猜你喜欢

转载自www.cnblogs.com/itxcr/p/11600164.html