【你不知道的javaScript 上卷 笔记5】javaScript中的this词法

function foo() {
    console.log( a );
}
function bar() { 
    var a = 3;
    foo(); 
}
var a = 2; 
bar();

上面这段代码为什么会输出2,而不是3?因为javaScript 只有词法作用域,foo()的定义在全局作用域,执行时会在它所在词法作用域查找变量a

this 的作用域

function foo() {
  var a = 2;
    this.bar(); 
  console.log(this)  
}
function bar() { 
  console.log( this.a );
}
foo(); // ReferenceError: a is not defined

说明 this并不指向foo函数的词法作用域,在非严格模式的node环境下打印console.log(this) ,发现this指向global 全局对象

Object [global] {
  global: [Circular],
  clearInterval: [Function: clearInterval],
  clearTimeout: [Function: clearTimeout],
  setInterval: [Function: setInterval],
  setTimeout: [Function: setTimeout] { [Symbol(util.promisify.custom)]: [Function] },
  queueMicrotask: [Function: queueMicrotask],
  clearImmediate: [Function: clearImmediate],
  setImmediate: [Function: setImmediate] {
[Symbol(util.promisify.custom)]: [Function]
  }
}

而在严格模式下thisundefined

'use strict'
function foo() {
    var a = 2;
   console.log(this)  //undefined          
 }
 foo();

由此可以猜测this 可以用来绑定函数执行时的上下文,所谓上下文就是函数调用所要的各种环境变量等,在非严格模式下node 的全局上下文是global 而浏览器是windowthis 不遵循词法作用域,而是作用在函数执行的时候。

this 绑定规则

1、默认绑定,this 指向全局对象

var a = 2;
function foo() {
    bar();  
}
function bar() { 
  console.log( this.a );
}
foo(); // 2

2、隐试绑定this 指向obj对象

function foo() { 
  console.log( this.a );
}
var obj = { 
  a: 2,
    foo: foo 
};
obj.foo(); // 2

隐试绑定丢失

function foo() { 
  console.log( this.a );
}
var obj = { 
  a: 2,
    foo: foo 
};
var bar = obj.foo; 
var a = "oops, global"; // a 是全局对象的属性 
bar(); // "oops, global"

foo 并不属于obj对象,将obj.foo; 赋值给bar变量,实际上这是一个声明在全局的表达式,这时调用bar(), this 在非严格模式下默认指向全局对象

3、显示绑定(在某个对象上强制调用函数)

call在调用 foo 时强制把它的 this绑定到 obj

function foo() {
  console.log( this.a );
}
var obj = { 
  a:2
};
foo.call( obj ); // 2

硬绑定

function foo(something) { 
  console.log( this.a, something ); 
  return this.a + something;
}
var obj = { 
  a:2
};
var bar = function() {
    return foo.apply( obj, arguments );
};
var b = bar( 3 ); // 2 3 
console.log( b ); // 5
bar.call(window,3) // 2 3(硬绑定的 bar 不可能再修改它的 this)

辅助函数

function foo(something) { 
  console.log( this.a, something ); 
  return this.a + something;
 }
// 简单的辅助绑定函数 
function bind(fn, obj) {
  return function() { 
    return fn.apply(obj, arguments );
  }; 
 }
var obj = { 
  a:2
};
var bar = bind( foo, obj );
var b = bar( 3 ); // 2 3 
console.log( b ); // 5

Function.prototype.bind ES5 内置的硬绑定函数

function foo(something) { 
  console.log( this.a, something ); 
  return this.a + something;
}
var obj = { 
  a:2
};
var bar = foo.bind( obj );
var b = bar( 3 ); // 2 3 
console.log( b ); // 5

4、new绑定

(1)创建一个全新的对象。

(2) 这个新对象会被执行[[Prototype]]连接。

(3)这个新对象会绑定到函数调用的this。

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

function foo(a) { 
    this.a = a;
}
var bar = new foo(2);
console.log( bar.a ); // 2

简单实现一个new函数

function objectFactory() {

      var obj = new Object(null),
    
      Constructor = [].shift.call(arguments);
    
      obj.__proto__ = Constructor.prototype;
    
      var ret = Constructor.apply(obj, arguments);
    
      return typeof ret === 'object' ? ret : obj;
};

this 绑定的优先级

function foo() { 
    console.log( this.a );
}
var obj1 = { 
  a: 2,
  foo: foo 
};
var obj2 = { 
  a: 3,
  foo: foo
};
obj1.foo(); // 2 
obj2.foo(); // 3
obj1.foo.call( obj2 ); // 3 
obj2.foo.call( obj1 ); // 2

obj1.foo.call( obj2 );输出3,说明绑定的是obj2,显式绑定>隐试绑定

function foo(something) { 
  this.a = something;
}
var obj1 = { 
  foo: foo
};
var obj2 = {};
obj1.foo( 2 );
console.log( obj1.a ); // 2

obj1.foo.call( obj2, 3 ); 
console.log( obj2.a ); // 3

var bar = new obj1.foo( 4 ); 
console.log( obj1.a ); // 2 
console.log( bar.a ); // 4

console.log( obj1.a ); // 2 说明new obj1.foo( 4 ) 没有先操作隐试绑定,new 绑定 > 隐式绑定

function bind(fn, obj) { 
    return function() {
        fn.apply( obj, arguments ); 
    };
}
function foo(something) { 
    this.a = something;
}
var obj1 = {};
var bar = bind(foo,obj1 );
bar( 2 );
console.log( obj1.a ); // 2
var baz = new bar(3); 
console.log( obj1.a ); // 2 
console.log( baz.a ); // undefined

new 绑定不能修改显式绑定后函数的this绑定

但是使用ES内置bind的属性,console.log( baz.a ); 输出3,说明new 绑定成功,new 绑定 >显式绑定 

function foo(something) { 
    this.a = something;
}
var obj1 = {};
var bar = foo.bind( obj1 );
bar( 2 );
console.log( obj1.a ); // 2
var baz = new bar(3); 
console.log( obj1.a ); // 2 
console.log( baz.a ); // 3

实现 ES5中的bind

if (!Function.prototype.bind) {
    Function.prototype.bind = function (oThis) {
      // bind 关在函数的原型链上
        if (typeof this !== "function") { 
            throw new TypeError(
                "Function.prototype.bind - what is trying " + "to be bound is not callable"
            );
        }
      // 获取调用函数的参数
        var aArgs = Array.prototype.slice.call(arguments, 1), 
        fToBind = this, // 将当前this保存起来
        fNOP = function () { }, 
        fBound = function () {
              // 当前对象是否在fNOP原型链上,是将this指向当前对象,不是将this指向oThis
            return fToBind.apply((this instanceof fNOP && oThis ? this : oThis),
            aArgs.concat(Array.prototype.slice.call(arguments)))
        }
        fNOP.prototype = this.prototype; // 继承this的原型
        fBound.prototype = new fNOP(); // 将fBound指向fNOP的实例
        return fBound;
    };
}

 因为return fToBind.apply((this instanceof fNOP && oThis ? this : oThis),这段代码判断了如果new 一个bind硬绑定的函数,将新生成的对象优先绑定到this

软绑定实现,保留隐试绑定和显示绑定

if (!Function.prototype.softBind) {
    Function.prototype.softBind = function (obj) {
        var fn = this;
        var curried = [].slice.call(arguments, 1); 
        var bound = function () {
            return fn.apply(
                (!this || this === (window || global)) ?
                    obj : this,
                curried.concat.apply(curried, arguments)
            );
        };
        bound.prototype = Object.create(fn.prototype);
        return bound;
    };
}

!this || this === (window || global)) ? obj : this, 在绑定对象为全局或者找不到时,采用显示绑定,然则采用隐试绑定。

更安全的this

function foo(a,b) {
    console.log( "a:" + a + ", b:" + b );
}
// 我们的 DMZ 空对象
var ø = Object.create( null ); // 把数组展开成参数
foo.apply( ø, [2, 3] ); // a:2, b:3
// 使用 bind(..) 进行柯里化 
var bar = foo.bind( ø, 2 );
bar( 3 ); // a:2, b:3

this 绑定到空对象减少副作用,如果传入nullthis默认会绑定全局对象,这样会修改全局对象

ES6中的箭头函数

function foo() { 
    setTimeout(() => {
    // 这里的 this 在词法上继承自 foo()
    console.log( this.a ); 
  },100);
}
var obj = {
    a:2
};
foo.call( obj ); // 2

箭头函数的内部this继承自其外部函数的this作用域。

总结

this 词法主要用于绑定函数调用时的执行上下文,主要有默认绑定,隐试绑定,显示绑定,new绑定四种规则,其中运用显示绑定可以实现硬绑定,而ES5为函数对象内置了Function.prototype.bind 属性。编写代码时,尽量编写统一规范的风格:

  1. 只使用词法作用域并完全抛弃错误this风格的代码;

  2. 完全只采用 this 风格,在必要时使用 bind(..),尽量避免使用 self = this 和箭头函数。

猜你喜欢

转载自www.cnblogs.com/seny-33/p/12205207.html