记录this和和闭包

130 阅读4分钟

this的指向

较为容易理解的

var adder = {
  base : 1,

  add : function(a) {
    var f = v => v + this.base;
    return f(a);
  },

  addThruCall: function(a) {
    var f = v => v + this.base;
    var b = {
      base : 2
    };

    return f.call(b, a);
  }
};

console.log(adder.add(1));         // 输出 2
console.log(adder.addThruCall(1)); // 仍然输出 2
  • new 绑定 硬绑定 来看 new 做了什么,手动实现的new
function myNew(){
    //  第一步 默认返回一个 空对象;
    let obj = {};
    //  第二步 获取构造函数
    let Fn = Array.prototype.shift.call(arguments);
    //  第三步 obj的__proto__ 指向 Fn
    obj = Object.create(Fn.prototype); // 1
    // 第四步 执行构造函数,获取构造函数的返回值,改变构造函数中的this指向
    // 来指向创建的对象
    let args = Array.prototype.slice.call(arguments)
    let x = Fn.call(obj, ...args);
    // 第五步 返回值
    // 如果x是引用类型就直接返回
    if((typeof x == 'object' || typeof x == 'function') && x !== null){
        return x
    } else {
        return obj;
    }
}

看关键的一步,let x = Fn.call(obj, ...args);, 使用call来改变构造函数this的指向,所以本质上来说,new 和 call bind apply 归为一类。

  • 隐式绑定 函数上下文
let obj = {
    name: '401',
    func: function(){
        console.log(this.name);  // 401
    }
};
obj.func();
  • 默认绑定 全局上下文
var name = '点点';
let obj = {
    name: '401',
    func: function(){
        console.log(this.name);  //  点点
    }
};
let outFunc = obj.func;
outFunc();

这里为什么是 '点点' ,这样理解,outFunc === obj.func 成立吗?当然成立,这个时候是全局的上下文,所以就是点点。

  • call bind apply 显式绑定 我们一般这样用 例子1: Object.prototype.toString.call(obj);

手动实现个call,来看下怎么改变this的指向:

 Function.prototype.myCall = function(thisArg){
    //  调用myCall的必须是函数
    if(typeof this !== 'function'){
        throw new TypeError('调用myCall必须是个函数!')
    }
    //  获取参数
    let args = [...arguments].slice(1);
    //  结合 例子1  这里 this == toString
    let fn = Symbol(this);
    thisArg[fn] = this;
    //  thisArg 就是call第一个参数 结合例子1 就是obj
    /*
        obj = {
            name:'401',
            func: ...,
            fn: toString,
        }
    */
    //  在这一步,运用了隐式绑定,把this指向了obj;
    //  obj.toString(...args);
    let result = thisArg[fn](...args);
    delete thisArg[fn];
    return result;
 }

一个简单的call实现,运用了this隐式绑定。

优先级:new > call bind apply > 隐式绑定 > 默认绑定

我的理解就是:谁能调用函数?或window,或对象,有些语法糖,比如:call bind apply ,对开发很友好。

闭包

红宝书(p178)对于闭包的定义:闭包是指有权访问另一个函数作用域的中的变量的函数

MDN 一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包closure)。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。

  • 闭包产生的原因 ES5中只存在两种作用域,全局作用域和函数作用域,当访问一个变量时,解释器现在当前作用域中查找,如果没找到,就去父作用域中查找,如果找不到就返回错误,这就是作用域链。(有点类似原型链);

闭包其实就是js保存变量的一种机制,闭包在函数被定义的时候创建;

闭包的表现形式:

  1. 返回一个函数 防抖和节流 比较经典的,当然我们也可以自己写;

  2. 作为函数参数传递 实际运用中的例子话?

  3. 在定时器、事件监听、Ajax请求、跨窗口通信、Web Worker或者任何异步中,只要使用了回调函数,就是在使用闭包,

以下闭包仅仅保存了当前作用域和全局作用域

// 定时器
setTimeout(function timeHandler(){
  console.log('111');
},100)

// 事件监听
$('#app').click(function(){
  console.log('DOM Listener');
})
  1. IIFE(立即执行函数表达式),保存了全局作用域和当前作用域
var a = 2;
(function(){
// a 2
console.log(a);
})()

如何打印出1,2,3,4,5,

for(var i = 1;i<=5;i++){
    setTimeout(function timer(){
        console.log(i);
    },0)
}
  • setTimeout 第三个参数
for(var i = 1;i<=5;i++){
    setTimeout(function timer(j){
        console.log(j);
    },0,i)
}
  • 利用 IIFE
for(var i = 1;i<=5;i++){
    (function(i){
        setTimeout(function timer(){
            console.log(i);
        },0)
    })(i)
}
  • 使用 let
for(let i = 1;i<=5;i++){
    setTimeout(function timer(){
        console.log(i);
    },0)
}
  • 闭包关于内存泄露 闭包使用不当会造成内存泄漏,
function bar(){
    var b = [1,2,3];
    function barz(){
        this.a  = b;
    }
    return barz
}
let func = bar();
func();
console.log(window.a);  // [1,2,3]

类似这样就属于闭包使用不当引起的内存泄露,这段代码有两个内存泄露的地方,

  • 对 b 的引用
  • a 定义到了全局变量上边 引起内存泄露的原因不止闭包,希望我能用新的记录来说明,下一章,记录垃圾回收和内存泄露,毕竟这和我们平时写代码息息相关。
如有问题,欢迎指出,谢谢!

参考:(建议收藏)原生JS灵魂之问, 请问你能接得住几个?(上)