This

144 阅读7分钟

1.2 This

this的基础四点

  1. 函数预编译环节 this->window

  2. 全局作用域 this->window
  3. call/apply 改变函数运行时this指向
  4. obj.function() 谁调用方法,this指向谁
var name = "222";
var a ={
    name:"111";
    say:function(){
           log(this.name);
		}
}
var fun = a.say;  //函数指针赋给fun
fun();		//222	//在全局环境执行,this指向window
a.say();    //111  a调用,指向a
var b = {
    name:"333",
    say:function(fun){
        fun();
    }
}
b.say(s.say);    //222   s.say放入b中执行,无人调用,走预编译环节
b.say = a.say; 
b.say();         //333	

函数上下文

普通函数调用模式

this的指向,是在函数被调用的时候确定的。也就是执行上下文被创建时确定的。

var a = 10;
var obj = {
  a: 20
}

function fn() {
  console.log(this.a);
}

fn(); // 10
fn.call(obj); // 20

在函数执行过程中,this一旦被确定,就不可更改了。

var a = 10;
var obj = {
  a: 20
}

function fn() {
  this = obj; // 这句话试图修改this,运行后会报错
  console.log(this.a);
}

fn();

对象中的函数调用模式

在一个函数上下文中,this由调用者提供,由调用函数的方式来决定。

  • 调用者函数,被某一个对象所拥有,那么该函数在调用时,内部的this指向该对象。
  • 如果函数独立调用,那么该函数内部的this,则指向undefined。但是在非严格模式中,当this指向undefined时,它会被自动指向全局对象。
var a = 20;
var obj = {
  a: 10,
  c: this.a + 20,
  fn: function () {
    return this.a;
  }
}

console.log(obj.c);  //40
console.log(obj.fn());  //10
var name = 'window';
var doSth = function(){
    console.log(this.name);
}
var student = {
    name: '甲生',
    doSth: doSth,
    other: {
        name: 'other',
        doSth: doSth,
    }
}
student.doSth(); // '甲生'
student.other.doSth(); // 'other'
// 用call类比则为:
student.doSth.call(student);
// 用call类比则为:
student.other.doSth.call(other);

箭头函数中的调用模式

  • 箭头函数的this,总是指向定义时所在的对象,而不是运行时所在的对象 。
//1
function foo() {
  setTimeout( () => {
    console.log("id:", this.id);
  },100);
}

var id = 21; 

foo.call( { id: 42 } ); //id:42

//2
function foo() {
  setTimeout( function (){
    console.log("id:", this.id);
  },100);
}

var id = 21; 

foo.call( { id: 42 } ); //id:21
  • 如果箭头函数被非箭头函数包含,则this绑定的是最近一层非箭头函数this,否则this的值则被设置为全局对象

  • 箭头函数不会创建自己的this,它只会从自己的作用域链的上一层继承this

var name = 'window';
var student = {
    name: '甲生',
    doSth: function(){
        // var self = this;
        var arrowDoSth = () => {
            // console.log(self.name);
            console.log(this.name);
        }
        arrowDoSth();
    },
    arrowDoSth2: () => {
        console.log(this.name);
    }
}
student.doSth(); // '甲生'
student.arrowDoSth2(); // 'window'
  • 无法通过callapplybind绑定箭头函数的this(它自身没有this)。而callapplybind可以绑定缓存箭头函数上层的普通函数的this
//1
var student = {
    name: '甲生',
    doSth: function(){
        console.log(this.name);
        return () => {
            console.log('arrowFn:', this.name);
        }
    }
}
var person = {
    name: 'person',
}
//还是student调用,箭头函数的 this 绑定了上层普通函数的 this 
// '甲生'  Object { name: "甲生", doSth: doSth() }  'arrowFn:' '甲生'
student.doSth().call(person); 
student.doSth.call(person)   //person

//person 调用
// 'person' Object { name: "person" } 'arrowFn:' 'person'
student.doSth.call(person)(); 

//2 
//return 的不是箭头函数时
var student = {
    name: '甲生',
    doSth: function(){
        console.log(this.name);
        return function (){
            console.log(this); 
            console.log('arrowFn:', this.name);
        }
    }
}
var person = {
    name: 'person',
}

//doSth()执行   call() 执行return的函数 所以this 指向person
student.doSth().call(person); // '甲生'  Object { name: "person" } 'arrowFn:' 'person'
//call() 执行doSth  this指向person 最后一个() 执行return的函数、全局执行
student.doSth.call(person)(); // 'person'  Window  'arrowFn:' '<empty string>'

//3
var name = "asdas"; 
var student = {
    name: '甲生',
    doSth: function(){
        console.log(this.name);
        return function (){
            console.log('arrowFn:', this.name);
        }
    }
}
var person = {
    name: 'person',
}
student.doSth().call(person); // '甲生'  'arrowFn:' 'person'
student.doSth.call(person)(); // 'person' 'arrowFn:' 'asdas'

call/apply/bind 调用模式

这三个方法都是Function自带的

call

语法: fun.call(thisArg[, arg1[, arg2[, ...]]])

thisArg :使用call方法指定this指向时注意一下几种情况

  1. 不传,或者传nullundefined, 函数中的this指向window对象
  2. 传递另一个函数的函数名,函数中的this指向这个函数的引用,并不一定是该函数执行时真正的this
  3. 值为原始值(数字,字符串,布尔值)的this会指向该原始值的自动包装对象,如 StringNumberBoolean
  4. 传递一个对象,函数中的this指向这个对象
function a(){
    console.log(this); 
}
function b(){} 

var obj = {name:'甲生'}; 

a.call() === a(); //window
a.call(null); //window
a.call(undefined);//window
a.call(1); //Number { 1 }
a.call(''); //String {""}
a.call(true); //Boolean {true}
a.call(b);// function b(){}
a.call(obj); //Object { name: "甲生" }

apply

语法:fun.apply(thisArg[, argsArray])

bind

bind是返回一个绑定函数可稍后执行,callapply是立即调用

  • 当点击网页时,EventClick被触发执行,输出JSLite.io p1 p2, 说明EventClick中的thisbind改变成了obj对象。
  • 如果你将EventClick.bind(obj,'p1','p2') 变成 EventClick.call(obj,'p1','p2') 的话,页面会直接输出 JSLite.io p1 p2
var obj = {name:'JSLite.io'};
/**
 * 给document添加click事件监听,并绑定EventClick函数
 * 通过bind方法设置EventClick的this为obj,并传递参数p1,p2
 */
document.addEventListener('click',EventClick.bind(obj,'p1','p2'),false);
//当点击网页时触发并执行
function EventClick(a,b){
    console.log(
            this.name, //JSLite.io
            a, //p1
            b  //p2
    )
}
// JSLite.io p1 p2

构造函数中的 this

  1. var obj = new Base();
    
    var obj  = {};	1、创建一个空对象
    obj.__proto__ = Base.prototype;  2、将空对象的_proto_指向Base函数的prototype
    Base.call(obj);  3、改变this指向
    

    新实例的内部包含一个指向构造函数原型对象的指针(-proto-)

  2. 如果函数没有返回对象类型Object(包含Functoin, Array, Date, RegExg, Error),那么new表达式中的函数调用会自动返回这个新的对象。

new 操作符调用时,this 指向生成的新对象。 特别提醒一下,new调用时的返回值,如果没有显式返回对象或者函数,才是返回生成的新对象

function Student(name){
    this.name = name;
    // return function f(){};
    // return {};
}
var result = new Student('甲生');
console.log(result); 
// 如果返回函数f,则result是函数f,如果是对象{},则result是对象{}
//没有 return 时,才是 { name:"甲生" }

原型链中的 this

function Student(name){
    this.name = name;
}
var s1 = new Student('甲生');
Student.prototype.doSth = function(){
    console.log(this.name);
}
s1.doSth(); // '甲生'

这就是对象上的方法调用模式。自然是指向生成的新对象。 如果该对象继承自其它对象。同样会通过原型链查找。

从ECMAScript 解读 this

Reference简介

ECMAScript 的类型分为 语言类型规范类型

  • 语言类型: Undefined, Null, Boolean, String, Number, 和 Object。
  • 规范类型:相当于meta-values, 用来用算法描述 ECMAScript 语言结构和 ECMAScript 语言类型的。规范类型包括:Reference(引用), List, Completion, Property Descriptor(属性描述符), Property Identifier(属性标识符), Lexical Environment(词法环境), 和 Environment Record(环境记录)。

1、Reference组成部分

  • base value
    • 属性所在对象或者就是 Environment Record(环境记录)
    • 值只可能是 undefined, Object, Boolean, String, Number, or Environment Record 其中的一种。
  • reference name
    • 属性名称
  • strict reference
    • 布尔值严格引用标记

2、获取 Reference 组成部分方法

  • GetBas() 返回 reference 的 base value。
  • IsPropertyReference () 如果 base value 是一个对象,就返回true。
  • GetValue() 调用 GetValue,返回的将是具体的值,而不再是一个 Reference
//例1
var foo = 1;

// 对应的Reference是:
var fooReference = {
    base: EnvironmentRecord,
    name: 'foo',
    strict: false
};
GetValue(fooReference) // 1;

//例2
var foo = {
    bar: function () {
        return this;
    }
};
 
foo.bar(); // foo

// bar对应的Reference是:
var BarReference = {
    base: foo,
    propertyName: 'bar',
    strict: false
};

如何确定 this 的值

当函数调用时如何确定 this 的值

  1. 计算 MemberExpression 的结果赋值给 ref
  2. 判断 ref 是不是一个 Reference 类型
  • true,
    • 如果 IsPropertyReference(ref) 是 true, 那么 this 的值为 GetBase(ref)
    • 如果 IsPropertyReference(ref) 是 false,base value 值是 Environment Record, 那么 this 的值为 ImplicitThisValue(ref) (该方法始终返回 undefined)
  • false
    • 那么 this 的值为 undefined

MemberExpression :

  • PrimaryExpression // 原始表达式 可以参见《JavaScript权威指南第四章》
  • FunctionExpression // 函数定义表达式
  • MemberExpression [ Expression ] // 属性访问表达式
  • MemberExpression . IdentifierName // 属性访问表达式
  • new MemberExpression Arguments // 对象创建表达式
function foo() {
    console.log(this)
}

foo(); // MemberExpression 是 foo

function foo() {
    return function() {
        console.log(this)
    }
}

foo()(); // MemberExpression 是 foo()

var foo = {
    bar: function () {
        return this;
    }
}

foo.bar(); // MemberExpression 是 foo.bar

简单理解: MemberExpression 其实就是()左边的部分

最后的例子

var value = 1;

var foo = {
  value: 2,
  bar: function () {
    return this.value;
  }
}

//示例1
console.log(foo.bar());

var Reference = {
  base: foo,
  name: 'bar',
  strict: false
};
this = GetBase(ref) = foo;  //2

//示例2
console.log((foo.bar)());   //2

//因为使用了 GetValue,所以返回的不是 Reference 类型,this 为 undefined
//示例3
console.log((foo.bar = foo.bar)());  //1
//示例4
console.log((false || foo.bar)());  //1
//示例5
console.log((foo.bar, foo.bar)());   //1

参考:juejin.cn/post/684490… github.com/mqyqingfeng…