重学前端 this

613 阅读4分钟

一. 什么是this?

在面向对象的编程语言中,this指向当前的对象,表示对当前对象的一个引用,也就是一个指针,代表一个地址。

二. this带来的好处

// 没有this的情况:
var obj1 = {
    name: "jack1",
    eat: function () {
        console.log(obj1.name);
    }
}
var obj2 = {
    name: "jack2",
    eat: function () {
        console.log(obj2.name);
    }
}
obj1.eat();  //obj1
obj2.eat();  //obj2
// 有this
function eat() {
    console.log(this.name);
}
var obj3 = {
    name: "jack3",
    eat
}
var obj4 = {
    name: "jack4",
    eat
}
obj3.eat();  //obj3
obj4.eat();  //obj4

三. this指向

this表示当前对象的引用,是调用时候决定的。在刚接触JavaScript的时候,曾经听过有人说this的指向是谁调用我,我就指向谁,也一度以为这句话是圣经,好友道理。直到遇到了call,apply,箭头函数等,这条圣经已经不适用了,不得不考虑重学this。本文考虑在浏览器的环境下,在node的环境全局变量有些差异。

this的指向分为几种情况:

  1. 默认绑定
  2. 隐式绑定
  3. 显示绑定
  4. new绑定
  5. 箭头函数 个人认为默认绑定是隐式绑定的一种。

四. 默认绑定

  1. 全局函数的直接调用
function foo() {
    console.log(this);  //window
}
foo(); 
  1. 函数内部调用
function foo1() {
    console.log(this);  //window
}
function foo2() {
    console.log(this);  //window
    foo1();
}
foo2();
  1. 对象属性引用
var obj = {
    foo: function() {
        console.log(this);  //window
    }
}
var fn = obj.foo; //fn引用obj.foo的方法
fn();
默认绑定是隐式绑定原因?

浏览器在解析Javascript代码的时候会经过预解析的过程,在预解析中会创建全局对象(Global Object),Javascript最外层定义的函数都会挂载到全局对象下面,作为属性,window也是全局对象的一个属性, 但是window的值是this,也就是我们平时说浏览器的全局对象是window的原因,本质是window只是全局对象的一个引用。

function foo() {
    console.log(this);
}
// 预解析,生成全局对象GO
// GO = {
//     foo,
//     window: this 
// }
foo(); //foo独立调用实际找到的是GO.foo,也就是window.foo
window.foo(); //this结果与上面的结果相同都是指向window

五. 隐式绑定

隐式绑定是圣经中的一个片段,谁调用我,我指向谁。(遇到显示绑定又挂了。。。优先级不够)

  1. 方法作为对象的属性
var obj = {
    name: "jack",
    foo: function() {
        console.log(this); //obj
    }
}
obj.foo();
  1. 方法是另外一个对象的属性
var obj1 = {
    foo: function() {
        console.log(this);
    }
}
var obj2 = {
    name: "jack2",
    foo: obj1.foo
}
obj2.foo();  //obj2

六. 显示绑定

  1. call
var obj = {
    name: "jack"
}
function sum(a,b) {
    console.log(a + b);
    console.log(this);  
}
sum.call(obj,1,2); //3 obj
  1. apply
var obj = {
    name: "jack"
}
function sum(a,b) {
    console.log(a + b);
    console.log(this);  
}
sum.apply(obj,[1,2]); //3 obj

call与apply的区别仅仅是传递参数的形式不同。 3. bind

var obj = {
    name: "jack"
}
function sum(a,b) {
    console.log(a + b);
    console.log(this);  
}
var sum2 = sum.bind(obj,1);
sum2(2); //3 obj

bind方法参数传递是一个参数收集的过程,也是函数柯里化的一种,后面有时间,会分享一起函数的柯里化。 bind方法应用在箭头函数上是没有效果的。

七. new绑定

通过new操作符调用函数的过程:

  • 在内存中创建一个空对象。
  • 这个对象内部的[[prototype]]属性会被赋值为该构造函数的prototype属性。
  • 构造函数内部的this会指向新创建出来的对象。
  • 执行函数体的代码。
  • 如果函数没有返回非空对象,会返回新创建出来的对象。
function Person(name,age) {
    this.name = name;
    this.age = age;
}
var p = new Person("jack",18);
console.log(p); //Person {name: 'jack', age: 18}

八. 箭头函数中的this

箭头函数不使用this的四种标准规则(也就是不绑定this),而是根据外层作用域来决定this。

var name = "外部name";
var obj = {
    name: "jack",
    foo: () => {
        console.log(this.name);
    }
}
obj.foo();

九. this绑定优先级

  1. 显示绑定高于隐式绑定
var obj1 = {
    name: "obj1",
    foo: function() {
        console.log(this.name);
    }
}
var obj2 = {
    name: "obj2"
}
obj1.foo.call(obj2);  //obj2
  1. new高于隐式绑定
var obj = {
    name: "jack",
    foo: function(name) {
        this.name = name;
        console.log(this.name);
    }
}
new obj.foo("foo"); //foo
  1. new绑定高于显示绑定
function foo(name) {
    this.name = name;
    console.log(this.name);
}
var obj = {
    name: "jack"
}
var bar = foo.bind(obj);
new bar("foo"); //foo

十. 特殊绑定null,undefined

当传入null/undefined时, 自动将this绑定成全局对象

function foo() {
    console.log(this);
}
foo.call(null);  //window
foo.call(undefined);  //window

十一. 自定义call

call方法是函数实例的方法,所以定义在Function构造函数的原型上面,后面会分享一期原型,原型链的相关知识。

Function.prototype.myCall = function(obj,...args) {
    obj = obj || window;
    let key = Symbol("key");
    obj[key] = this;  //这里的this指向调用myCall方法的函数,有点绕
    let result = obj[key](...args);
    delete obj[key];
    return result;
}
function foo(age) {
    console.log(this.name,age);
}
foo.myCall({name: "jack"},18);  

十二. 自定义apply

call与apply仅仅是传递参数的区别,实现思路完全是一样的

Function.prototype.myApply = function(obj,args) {
    obj = obj || window;
    let key = Symbol("key");
    obj[key] = this;  //这里的this指向调用myApply方法的函数,有点绕
    let result = obj[key](...args);
    delete obj[key];
    return result;
}
function foo(age) {
    console.log(this.name,age);
}
foo.myApply({name: "jack"},[18]);  

十三. 自定义bind

Function.prototype.myBind = function(obj,...args1) {
    return function(...args2) {
        let key = Symbol("key");
        obj[key] = this;
        // 参数收集的过程,函数柯里化。
        let result = obj[key](...args1,args2);
        delete obj[key];
        return result;
    }
}
function foo(name,age) {
    console.log(this,name,age);
}
foo.bind({name:"abc"},"jack")(18);

十四. 自定义new

function myNew(fn,...args) {
    let obj = {};
    obj.__proto__ = fn.prototype;
    let result = fn.call(obj,...args);
    return typeof result === "object" ? result : obj;
}
function Person(name,age) {
    this.name = name;
    this.age = age;
}
let p = myNew(Person,"jack",18);
console.log(p);