阅读 2604

春招面试复盘,重拾this~ 【JS Plan】

在这里插入图片描述


文章导图先来一下:

在这里插入图片描述

一、this 初探 💭💭💭

1、为什么要使用this?

如何更好的理解this,我想需要先了解this解决了什么问题。在写文章的过程中也看了一些其他人写的文章和博客,其中大都是将this是什么,this的使用场景,this的指向。当然这些是需要掌握的一些this知识点。但在开篇之前想先提个问题?

你知道javascript中为什么需要this吗?

看到这个问题,我在心里想的就是,this是一个执行上下文的属性,在调用时确定指定什么,但直接问我javascript为什么需要this,可能我一下回不上来。下面就来理一理吧

让我们先来看看下面这个例子:

var person = {
  name:'jingda',
  age:'23',
  phone:'1234567',
  FuncHi: function() {
    //实现功能:打印出「你好,我是 jingda,今年 23 岁」
  },
  FuncBye: function() {
    //实现功能:打印出「再见,记得我叫 jingda ,我的电话是1234567
  }
}
复制代码

上面这段代码定义了一个person对象,其中有name,age,phone三个属性,以及FuncHi,FuncBye两个方法,各自实现自己的功能。我们可以看到,此时的两个方法中并没有传递任何参数,因此它是无法达到输出这个对象的属性的,当我们尝试采取一些方法,比如说去传递参数,我们可以把代码写成这样:

var person = {
    ...
    FuncHi: function(name, age){
      console.log('你好,我是 ${name},今年 ${age} 岁')
    },
    FuncBye: function(name, phone){
      console.log('再见,记得我叫 ${name} 我的电话是 ${phone}')
    }
    
	person.FuncHi(person.name, person.age)
	person.FuncBye(person.name, person.phone)
复制代码

可以在控制台打印

在这里插入图片描述

这种传递参数的方式虽然能实现功能,但使用起来就知道,它让代码变得很笨,比如你看调用过程的代码。

于是我们尝试改进一下调用时的代码,我们可以在调用的时候把person当做参数传入到两个方法中,当方法里需要用到person当中的属性时,自己取就可以了。 于是代码改成这样:

var person = {
    ...
    FuncHi: function(p){
      console.log('你好,我是 ${p.name},今年 ${p.age} 岁')
    },
    FuncBye: function(p){
      console.log('再见,记得我叫 ${p.name} 我的电话是 ${p.phone}')
    }
	person.FuncHi(person);
	person.FuncBye(person);
复制代码

这样似乎比刚才的代码,参数缩短了很多。

但是开发者可能最终更想要的代码是

var person = {
    ...
    FuncHi: function(){
      console.log('你好,我是 ${this.name},今年 ${this.age} 岁')
    },
    FuncBye: function(){
      console.log('再见,记得我叫 ${this.name} 我的电话是 ${this.phone}')
    }
	person.FuncHi();
	person.FuncBye();
复制代码

这么一下就把this丢出来是个啥意思嘞?实际上 this 是隐藏的第一个形参。在你调用 person.FuncHi() 时,这个 person 会「变成」 this。也就是说,参数隐藏了,而我们使用的this.相当于person.

但this远远不会就只是由对象.出来的,会涉及很多情形,下面再聊哈。

2、你对this如何定义?

首先要理解的就是单单这个“this”,在javascript中到底指的是什么呢?这里我参考了 MDN上的解释,得出的结果:

this是一个执行上下文的属性,它会在执行的过程中用到。注意它的指向完全取决于在哪里被调用。

(话说,我能有什么定义,打扰了。。。)下一个:

二、不同场合下的this指向 👉👉👉

在这一节会涉及很多this使用场景,因为在浏览器和node下执行的结果可能并不一致,所以这里如果没有特殊说明,即运行在浏览器中。 自己也可尝试哦

1、全局上下文

  • 浏览器

在这里插入图片描述

  • node
console.log(this) //{}
复制代码

2、函数上下文

在非严格模式下:

  • 浏览器

在这里插入图片描述 因为this 的值不是由该设置的,所以 this 的值默认指向全局对象,浏览器中就是 window。

  • node
function f() {
  return this;
}

console.log(f() === globalThis) //true
console.log(globalThis) //Object [global]
复制代码

在严格模式下

浏览器和node下都是undefined: 在这里插入图片描述

function f() {
  "use strict";
  return this;
}

console.log(f() === undefined) //true
复制代码

这里在严格模式下,如果进入执行环境没有设置this的值,this会保持为undefined,就像上面这个例子,f()被直接调用,不是作为对象的属性或者方法调用。

3、构造函数

当一个函数用做构造函数时,也就是使用了new关键字的时候,它的this就被绑定到正在构造的新对象。 首先来看看构造函数是如何工作的?

function constructorFun() {
  this.name = 'jingda'
  //当然你可以设置更多值

  //当函数具有返回对象的return语句时
  //该对象是new表达式的结果
  //否则,表达式的结果是当前绑定到this的对象
}
复制代码

好吧,这里自觉的想到了new的实现原理:

1、创建一个新的空的对象

2、把这个对象链接到原型对象上

3、这个对象被绑定为this

4、如果这个函数不返回任何东西,那么就会默认return this

但是new这个关键字,并不是所有返回值都原封不动地返回的。如果返回的是undefined,null以及基本类型的时候,都会返回新的对象;而只有返回对象的时候,才会返回构造函数的返回值。

好了,new说到这里,继续我们的this,来看下面两个例子:

function C() {
  this.a = 37;
}
var o = new C();
console.log(o.a);//37
function C2() {
  this.a = 37;
  return {a:38};
}
o = new C2();
console.log(o.a);//38
复制代码

因为C中没有return对象,所以this就是指向的创建的实例 ,o.a就是37,而在C2,因为在调用构造函数的过程中,手动的设置了返回对象,与this绑定的默认对象被丢弃了,此时this就是指向return返回的对象,o.a为38

4、原型链中的this

首先大致理解一下原型链 JavaScript 常被描述为一种基于原型的语言 ——每个对象拥有一个原型对象,对象以其原型为模板、从原型继承方法和属性。原型对象也可能拥有原型,并从中继承方法和属性,一层一层、以此类推。这种关系常被称为原型链 (prototype chain)

来个例子:

var o = {
  f: function() {
    return this.a + this.b;
  }
};
var p = Object.create(o);
p.a = 1;
p.b = 4;
console.log(p.f());//5
复制代码

我们通过Object.create(o),指定o作为原型对象,创建出了p这个对象实例。但是在对象p上并没有属于它自己的f属性,这个f属性继承于它的原型,也就是o。

5、对象方法

当函数作为对象里的方法被调用时,this 被设置为调用该函数方法的对象。该函数方法赋值给另一个对象,就会改变this的指向。

var obj = {
  name:"jing",
  foo: function () {
    return this.name;
  }
};
console.log(obj.foo()); //jing
复制代码

当 obj.foo() 被调用时,函数内的this 将绑定到 o 对象。

需要注意的问题!!

先改一下上面的代码,以便理解:

var obj = {
  foo: function () {
    console.log(this)
  }
};
obj.foo();//指向obj
复制代码
  • 当函数作为一个值,而不是对象的属性或是方法
(obj.foo = function () {
  console.log(this);
})()
//指向全局
复制代码
  • 当this所在的方法不是在对象的第一层,这时this只是指向当前一层的对象,而不会继承更上面的层。
var a = {
  p: 'jing',
  b: {
    m: function() {
      console.log(this.p);
    }
  }
};
a.b.m() //undefined
复制代码

a.b.m方法在a对象的第二次,该方法的内部this不是指向a,而是指向a.b,如果我们需要打印出p的值:

var a = {
  b: {
    m: function() {
      console.log(this.p);
    },
    p: 'Hello'
  }
};
a.b.m() //'Hello'
复制代码
  • 将嵌套对象内部的方法赋值给一个变量,this依然会指向全局对象。还是上面的例子,当我们这么写:
var a = {
  b: {
    m: function() {
      console.log(this.p);
    },
    p: 'Hello'
  }
};
var hello = a.b.m;
hello() // undefined
复制代码

上面这个例子,m是多层对象内部的一个方法,如果将它赋值给hello变量,this指向了顶层对象。如何解决这个问题,可以只将m所在的对象赋值给hello,这样调用时,this的指向就不会变。

var hello = a.b;
hello.m() // Hello
复制代码

6、作为一个DOM事件处理函数

当函数被用作事件处理函数时,它的 this 指向触发事件的元素

7、箭头函数下的this

最后不得不提的是箭头函数有关this指向的问题:

ES2015 引入了箭头函数,箭头函数不提供自身的 this 绑定(this 的值将保持为闭合词法上下文的值)。

1、箭头函数没有单独的this,箭头函数不会创建自己的this,它只会从自己的作用域链的上一层继承this。因此,在下面的代码中,传递给setInterval的函数内的this与封闭函数中的this值相同:

function Person(){
  this.age = 1;
  setInterval(() => {
    this.age++; //正确地指向 p 实例
  }, 1000);
}

var p = new Person();
console.log(p.age);
复制代码

2、严格模式下不是输出undefined

var f = () => { 'use strict'; return this; };
f() === window; // 或者 global
复制代码

3、使用call(),apply()方法调用一个函数,只能传递参数,不能绑定this

var person = {
  age:18,
  sum: function(a) {
    var f = a => a + this.age;
    return f(a);
  },
  sumCall: function(a) {
    var f = a => a + this.age;
    var b = {
      age:20
    };
    return f.call(b,a)
    //call的第一个参数是需要绑定到的对象,上,如果成功的话,指向的b age为20,但是下面输出的是19,并没有绑定到b上
  }
}
console.log(person.sum(1));//19
console.log(person.sumCall(1));//19
复制代码

输出两个都是19,也就是说在sumCall中,f.call(b,a),本来想把this绑定到b上,没有成功。

三、this使用需要注意的点 👊👊👊

1、避免多层this(包括数组处理方法中的this)

因为this的指向是不确定的,所以尽量不要在函数中包含多层this:

var o = {
  name:'jing',
  f1: function() {
    console.log(this.name);
    var f2 = function () {
      console.log(this.name);
    }();
  }
}

o.f1()
//node 下执行
// jing
// undefined
复制代码

f2中的this丢失了,指向了全局。

如果要避免,则需要在f2中改用一个指向外层的变量。


var o = {
  name:'jing',
  f1: function() {
    console.log(this.name);
    var that = this;
    var f2 = function() {
      console.log(that.name);
    }();
  }
}

o.f1()
//jing
//jing
复制代码

2、 还有一个就是数组map和foreach方法,

允许提供一个函数作为参数。这个函数内部不应该使用this。会造成和上面一样的丢失this现象,像下面这个例子,f可以去到o的this,但是f中的forEach方法中的this丢失了,指向的全局。因此为undefined。

var o = {
  v: 'hello',
  p: [ 'a1', 'a2' ],
  f: function f() {
    this.p.forEach(function (item) {
      console.log(this.v + ' ' + item);
    });
  }
}

o.f()
//node 下执行
// undefined a1
// undefined a2
复制代码

浏览器中输出这个东西??? 在这里插入图片描述

解决的方法:

  • 1、需要在中forEach改用一个指向外层的变量
f: function f() {
    var that = this;
    this.p.forEach(function (item) {
      console.log(that.v+' '+item);
    });
  }
复制代码
  • 2、将this当作foreach方法的第二个参数,固定它的运行环境
f: function f() {
    this.p.forEach(function (item) {
      console.log(this.v + ' ' + item);
    }, this);
  }
复制代码

3、null undefined传入call apply bind

function foo() {
  console.log(this.a);
}
var a = 2;
foo.call(null) //2
复制代码

这里的call方法传递的参数是null,this指向的是全局 (严格模式下是undefined)

四、测试几个题目(答案在结尾)✍️✍️✍️

1、对象属性方法相关

const object = {
  message:'hello,world',
  getMessage() {
    const message = 'hello,earth';
    return this.message;
  }
};
console.log(object.getMessage());
复制代码

不熟悉的话可以看 场合5,此时调用的是object中的this。

2、原型链相关

function Person(name) {
  this.name = name;
  this.getName = () => this.name;
}
const cat = new Person('jing');
console.log(cat.getName());
const { getName } = cat;
console.log(getName());
复制代码

3、setTimeout()延迟相关

const object = {
  message:'hello world',

  logMessage() {
    console.log(this.message);
  }
};
setTimeout(object.logMessage,1000);
复制代码

4、箭头函数相关

const object = {
  who: 'World',
 
  greet() {
    return `Hello, ${this.who}!`;
  },
 
  farewell: () => {
    return `Goodbye, ${this.who}!`;
  }
};
console.log(object.greet());   
console.log(object.farewell());
复制代码

5、指向window

var length = 4;
function callback() {
  console.log(this.length);
}
const object = {
  length:5,
  method(callback) {
    callback();
  }
};
object.method(callback,1,2);
复制代码

6、最后一个前两天看到的

var myObject = {
  foo:"bar",
  func:function() {
    var self = this;
    console.log(this.foo);
    console.log(self.foo);
    (function() {
      console.log(this.foo);
      console.log(self.foo);
    }());
  }
}
myObject.func();
复制代码

在这里插入图片描述 答案(可以自己运行哈)

1、hello,world

2、jing jing

3、undefined

4、Hello, World! Goodbye, undefined!

5、4(浏览器) undefined (node) 因为node没有window

6、 bar bar undefined bar

有任何问题可以评论区回复,也可以自己查资料,看小黄书。

好啦,this就先到这了。文章也是看了书看博客看题,总结的一些东西,可能写的并不系统。也可能我理解的不好的地方,欢迎评论区批评建议。

我是婧大,一名大三学崽,你的建议和点赞是给我最大的鼓励。🙈

目前在默默学习中,也在寻找合适的实习岗位,如果有小伙伴一起学习,欢迎加我wx:lj18379991972 💖💖💖

在这里插入图片描述

参考链接:

你不知道的javascript (看书哦,this节)

MDN this

网道 javascript教程 this关键字

饥人谷 -- JS 里为什么会有 this

文章分类
前端
文章标签