# 作用域和闭包

  • 全局作用域
  • 函数作用域
  • 块级作用域(ES6)

# 作用域

作用域就是一个独立的地盘,让变量不会外泄、暴露出去。也就是说作用域的用处就是隔离变量,不同作用域下同名变量不会有冲突。 作用域一般有两种 全局作用域和函数作用域。

let a =  0;
function fn1() {
   let a1 = 10;
   function fn2(){
       let a1 = 9;
       let a2  =200;
       // a2 a1 a 在使用时会在作用域内,从离调用最近的地方到离调用远的作用域内找。
       console.log(a2 + a1 + a)
   }
   fn2()
}
fn1();
  • let定义的是块级作用域,let之前使用会出现暂时性死区。
  • const用来定义常量,且必须要有初始值。
  • var定义变量,会存在变量提升。
  • let 在全局环境中定义的指,是不会被挂在window对象上的
//例题1
for (var i =0 ;i<10;i++){
    console.log(i)// 0 1 2 3 4 5 6 7 8 9
    setTimeout(function () {
        console.log(i) // 10 10 10 10 10 10 10 10 10 10
    })
}


//例题2
for (var i =0 ;i<10;i++){
}
console.log(i)// 10


//例题3
for (let i =0 ;i<=10;i++){
    console.log(i) // 0 1 2 3 4 5 6 7 8 9 10
    setTimeout(function () {
        console.log(i) // 0 1 2 3 4 5 6 7 8 9 10
    })
}

//例题4

for (let i =0 ;i<=10;i++){
}
console.log(i) // i is not defind


//例题5

//因为let在for循环体外定义,所以存在作用域问题
let i
for ( i = 1; i<=3; i++ ){
    setTimeout(function () {
        console.log(i) // 4 4 4
    },0)
}
//例题6
let i
for ( i = 1; i<=3; i++ ){
   console.log(i)// 1 2 3
}
console.log(i) //4


//例题7
let a = 1;
let obj = {
    a:2,
    add:()=>{
        setTimeout(()=>{
            console.log(this);//window
            console.log(this.a);//undefind 
        },0)
    }
}

obj.add();

# 闭包

利用闭包可以突破作用链域,将函数内部的变量和方法传递到外部。 两种表现(既闭包就是函数定义的地方和函数执行的地方不一致) 因为 js 是有作用域的,函数也拥有自己的作用域,如果我们要想访问到作用域内的变量,需要通过闭包。

  • 函数作为参数被传递
  • 函数作为返回值被返回

闭包内的自由变量寻找的时候,是在函数定义的地方向上级作用域去寻找。

//函数作为返回值被返回
function fn1(){
    let a = 100;
    return function(){
        console.log(a)
    }
}
let a = 200
let result = fn1();
console.log(result());// 打印结果是100
//函数作为参数被传递
function fn2(fn){
    let a = 100
    fn()//打印结果是200
}
let a = 200
function fn(){
    console.log(a)
}

闭包的应用

  • 闭包隐秘数据只提供 API,我们通过调用闭包内提供的 API 来访问函数函数内的变量
function fn(){
    const data = {} //闭包内的数据,外部不可直接访问
    return{
        get(key){
            return data[key]
        },
        set(key,val){
            data[key] = val
        }
    }
}
const c = fn();
c.set('name','kira')
var x = 0;
var foo = {
    x:1,
    bar:function () {
        console.log(this.x);
        var that = this;
        return function () {
           console.log(this.x)
           console.log(that.x)
        }
    }
}
foo.bar()       // 1 foo调用bar,this指向foo
foo.bar()()     // this: 0, that: 1 由于return一个匿名函数,形成了闭包,在匿名函数内 this指向window,匿名函数外this指向缓存的obj对象。
var x = 0;
var bar:function () {
        var n = 999;
        return function () {
           return n;
        }
    }
var outer = bar();
outer() //n为999 函数在哪定义,自由变量在哪取值。

i 是全局作用域
let a,i
for(i=1,i<10,i++){
    a = document.createElement('a')
    a.innerHTML = i  +'<br>'
    a.addEventListener('click',function(){
        alert(i) // 十个标签弹出的结果都是 10,因为遍历先执行,每个a标签被click时,i早就变成来10,因为i是全局作用域
    })
}
document.body.appendChild(a)

改造
i改为块级作用域,这样每次循环都会产生一个块级作用域。
let a
for(let =1,i<10,i++){
    a = document.createElement('a')
    a.innerHTML = i  +'<br>'
    a.addEventListener('click',function(){
        alert(i) // 十个标签弹出的结果都是 1,2,3,4.....
    })
}

# this

this 取出的值是在函数执行的时候定义的 this 的使用场景

  • 作为普通函数(this 指向 window)
  • 使用 call apply bind(传入什么 this 就指向什么)
  • 作为对象方法被调用(this 指对象本身)
  • 在 class 方法中调用(this 指当前实例)
  • 箭头函数(箭头函数的取值是取它上级作用域的值)
   f.__proto__ == Function.prototype // true 所以 bind call apply 都在 Function.prototype 实现的。
function fn1(){
    console.log(this);
}
fn1(); //打印结果window

//通过call改变了this的指向
fn1.call({x:100}) //打印结果 {x:100}

//通过bind改变了this的指向
const fn1 = fn1.bind({x;200})
fn2() //{x:200}

const xx = {
    name:'zhangsan',
    play(){
        //箭头函数中的this是取它上级作用域的值
        setTimeOut(()=>{
            console.log(this)//this指向xx对象
        })
    }
    play(){
        setTimeOut(function(){
            console.log(this)//this指向window
        })
    }

}

# bind实现

bind 的使用

function f() {
    console.log('执行了')
}

Function.prototype.bind1 = function () {
    const self = this; //指向f
    //参数转化为数组
    let args = Array.prototype.slice.call(arguments);
    //把第一项,要执向的对象提取出来
    const start = args.shift();
    console.log(args);

    //bind的执行是返回一个函数
    return function () {
       return  self.apply(start,args)
    }

};
const fn2  = f.bind1({},1,2,4);
//接收返回的函数,并执行
const  res = fn2();
console.log(res);

# call实现

  • 1将函数设为对象的属性
  • 2执行该函数
  • 3删除该函数
//使用call
let obj = {name:"kira"}
function fn (){ console.log(this.name)}
fn.(obj);//打印kira

//把fn挂载到obj上
let obj = {
    name:"kira",
    fn:function(){
        console.log(this.name)
    }
}
obj.fn();
Function.prototype.call1 = function(){
    let args = Array.prototype.slice.call(arguments);
    let context = args.shift();
    context.fn = this;
    const result = context.fn(...args)
    delete context.fn;
    return result;
};
function fn(content){
    console.log(this.name)
}

# apply实现

Function.prototype.apply1 = function(context,args){
    context.fn = this;
    const result = context.fn(...args);
    delete context.fn;
    return result;
};
function fn(content){
    console.log(this.name)
}
let obj = {name:'kira'}
fn.apply1(obj,[1,2,3]);

练习题

变换1
var name = 'global';
var obj = {
    name:'obj',
    method:function() {
        this.name = 'local';
        //console.log(此时this指obj,obj.name = 'local')
        return function() {
            return this.name;
        }
    }
}

console.log(obj.method());


变换2
var name = 'global';
var obj = {
    name:'obj',
    method:function() {
        this.name = 'local';
        console.log(此时this指obj,obj.name = 'local')
        return function() {
            //return function this指向window
            return this.name;
        }
    }
}
console.log(obj.method()());//
var obj = {
    name: 'obj',
    hello() {
        console.log(this.name)
    },
}
var h = obj.hello
h()//此时this指向的window
console.log(h)//打印结果underfund



var obj = {
    name:'obj',
    hi(){
        return () => {
            console.log(this.name)
        }
    },
}
var h2 = obj.hi()
h2()//箭头函数没有自己的this指向
//变换3
function foo(){
  var b = 2;
  //打印结果124
  console.log(b + this.a)
}
function foo1(){
  var a = 4
  // 2
  //this指向{a:122}
  console.log(a + this.a)//126
  //this指向{a:122}
  foo.call(this)
}
foo1.call({a:122})