ABCDdouyaer / jiraiya.github.io

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

你不知道的JavaScript上 之【this】

ABCDdouyaer opened this issue · comments

关于this

1.概念

  • this是在运行时绑定,并不是在编写的时候绑定;

  • this的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式;

  • 当一个函数被调用的时候,会创建一个活动记录(有时也称执行上下文),这个记录会包含函数在哪里被调用(调用栈)、函数的调用方式、传入的参数等信息,this就是这个记录的一个属性,会在函数执行的过程中用得到;

this的全面解析

1.调用位置(调用栈)

function baz(){
    //当前调用栈:baz
    //调用位置:全局作用域
    console.log('baz');
    bar(); //调用位置
}

function bar(){
    //当前调用栈:baz->bar
    //调用位置:baz
    console.log('bar');
    foo();//调用位置
}

function foo(){
    //当前调用栈:baz->bar->foo
    //调用位置:bar
    console.log('foo');
}
baz();//调用位置

2.绑定规则

  • 默认绑定:独立函数调用(非严格模式this绑定到全局window,严格模式不能将全局对象用于默认绑定,因此绑定到undefined),只要函数运行于费严格模式,默认绑定才会绑定到全局对象
var a = 2;
function foo1(){
    console.log(this.a)//2
}
foo1();

function foo2(){
    'use strict'
    console.log(this.a)
    //Uncaught TypeError: Cannot read property 'a' of undefined
}
foo2();
var a = 2;
function foo(){
    console.log(this.a)//2
}

(function(){
    'use strict'
    foo();
})()
  • 隐式绑定:函数被调用时,obj‘拥有’或者‘包含’它,它的前面加上了对obj的引用,对象属性引用链中只有上一层或者最后一层在调用位置中起作用
function foo(){
    console.log(this.a);
}
var obj = {
    a: 2,
    foo,
}
obj.foo()//2

var obj1 = {
    a: 42,
    obj,
}
obj1.obj.foo()//2
function foo(){
    console.log(this.a);
}
var obj = {
    a: 2,
    foo,
}
var bar = obj.foo;
//bar实际上引用的函数本身,等同于bar = foo
var a = 'oops, global';
bar();//oops,global
function foo(){
    console.log(this.a);
}
var obj = {
    a: 2,
    foo,
}
var a = 'oops, global';

function dofn(fn){
    fn()
}
//函数参数实际上是一种隐式赋值,类似(fn = foo)
dofn(obj.foo);//oops,global
  • 强制绑定:call、apply、bind(硬绑定)

    • 创建包裹函数

    • 创建辅助函数

    • bind实现

    • API调用的上下文(js宿主环境中许多的内置函数都提供一个可选参数,用于指定执行上下文)

function foo(something){
    return this.say() + something
}

var obj = {
    say: function(){
        return '小明说:'
    }
}

var bar = function(){
    return foo.apply(obj, arguments)
}

console.log(bar('您好!'))//小明说:您好!
function bind(fn, obj){
    return function(){
        fn.apply(obj, arguments)
    }
}

var obj = {
    a: 1,
}

function fn(str){
    console.log(this.a, str)
}
var bar = bind(fn, obj)
bar(2);//1, 2

Function.prototype.hardBind = function(obj){
    const fn = this;
    const arr = [].slice.call(arguments, 1)
    const bound = function(){
        //由于此处已经是obj的引用所以call,apply,mybind都不能在改变this
        return fn.apply(obj, arr)
    }
    bound.prototype = Object.create(fn.prototype);
    return bound;
}
function fn(e){
    console.log(this.a, e)
}
var obj = {
    a: 2
}
var obj1 = {
    a: 3
}
var obj2 = {
    a: 4
}

let fn1 = fn.hardBind(obj, 1).hardBind(obj1, 2)
fn1();// 2, 1
fn1.call(obj2);// 2, 1
const arr = [1, 2, 3];
const obj = {a: 'ooh'};
function fn(e){
    console.log(e, this.a)
}
arr.forEach(fn, obj);
/**
1 'ooh'
2 'ooh'
3 'ooh'
 */
  • new绑定(被new操作符调用的普通函数),new操作符调用函数自动执行下面操作

    • 创建(或者构造)一个全新的对象

    • 这个新对象会执行[[prototype]]连接

    • 这个新对象会绑定到函数调用的this

    • 如果函数没有返回其他对象,那么new表达式中的函数调用自动返回这个新对象

3.判断this(优先级)

  • 函数是否在new中调用(new绑定)?是的话,this绑定的是新创建的对象

  • 函数指定了硬绑定,是的话,this绑定的是指定对象

  • 函数是否在某个上下文对象中调用(隐式绑定),是的话,this绑定的是那个上下文对象

  • 如果都不是,使用默认绑定,严格模式绑定undefined,否则绑定全局对象

4.绑定例外

  • 将null或者undefined作为this的绑定对象,实际应用的是默认规则(非严格全局,严格undefined)【绑定对象不重要情况下可以用null或者undefined占位】
//函数柯里化(该方法容易将全局绑定,比较危险)
const arr = [1, 2];
function foo(a, b){
    console.log(`a:${a}`, `b:${b}`)
}
foo.apply(null, arr);

//安全做法
const arr = [1, 2];
function foo(a, b){
    console.log(`a:${a}`, `b:${b}`)
}
foo.apply(Object.create(null), arr);
  • 间接引用(默认规则)
var a = 2;
function foo(){
    console.log(this.a)
}
var o = {a: 3, foo}
var p = {a: 4}
o.foo();//3
(p.foo = o.foo)();//2

  • 软绑定,如果this为默认规则,则指向软绑定的对象,否则指向当前的调用对象
Function.prototype.softBind = function(obj){
    const fn = this;
    const arr = [].slice.call(arguments, 1)
    const bound = function(){
        const self = (!this || this === (global || window)) ? obj : this;
        return fn.apply(self, arr)
    }
    return bound;
}

function f(e){
    console.log(this.a, e)
}

var obj = {
    a: 2
}
var obj1 = {
    a: 3
}
var obj2 = {
    a: 3
}
let f1 = f.softBind(obj, 1)
f1();// 2, 1
f1.call(obj1);// 3, 1
obj2.f = f.softBind(obj1, 3);
obj2.f();//3, 3