js基础回顾(二)this关键字

211 阅读7分钟

this关键字

什么是this

 书接上文,在js的使用中this关键字是必不会缺少的一个关键知识点,但是很多初学者估计和我第一次接触this时候差不多,都会脑袋中不自觉的发出疑问this是什么?  this,在js中是一个指针型变量,但是它既不指向函数的静态作用域也不指向函数本身,而是动态的指向了当前的执行上下文,当函数被调用时this将会绑定到当前环境的执行上下文,点讲谁调用它指向谁。

常见的指向问题

普通函数this指向

 一般来讲普通函数的this又分为两种,严格模式和非严格模式。在严格模式下普通函数指向的是unfinished,在非严格模式下指向为全局变量Window

//非严格模式
function bar(){
console.log(this)
}
bar()
//window
//严格模式
“use strict“
function bar(){
console.log(this)
}
bar()
//undefined

对象中内置函数中的this指向

 根据谁调用就是指向谁原则对象调用内置属性函数时,此时函数中的this不会指向Window而是指向了这个对象本身

var age=20
var name="jerry"
let barObject={
age:10,
name:"tom",
bar:function(){
console.log(this.age)
//10
console.log(this.name)
//tom
}
}
console.log(this.age)
//20
console.log(this.name)
//jerry
barObject.bar()

 在js中,this的意思为“这个;当前”,是一个指针型变量,它动态指向当前函数的运行环境。在不同的场景中调用同一个函数,this的指向也可能会发生变化,但是它永远指向其所在函数的真实调用者;如果没有调用者,就指向全局对象window。

构造函数的this指向

 构造函数中的this和普通函数不同他可以通过new关键字进行构造实例,并且将this和构造出来的实例进行绑定指向构造的实例

function Bar(name, age) {
  this.name = name
  this.age = age
  this.foo = function () {
    console.log(this.name + '今年' + this.age + '岁了')
  }
}
let bar1 = new Bar('tom', '19')
let bar2 = new Bar('jerry', '35')
bar1.foo()
//tom今年19岁了
bar2.foo()
//jerry今年35岁了

this丢失

 this由于是谁调用就是指向谁,但是在实际场景中当我们进行链式调用(jQuery,vue)时候直接用function定义回调函数时容易丢失this,导致this被指向全局window,一些变量访问失败,导致我们一些访问数据逻辑报错。

var text = 10
var barObject = {
  text: 20,
  foo: function () {
    console.log(this.text, '1')
    //20,'1'

    // 第一种方法解决,额外定义一个变量指向当前this
    var _this = this
    setTimeout(function () {
    //this丢失现象
      console.log(this.text, '2')
       //10,'2'
      console.log(_this.text, '4')
 //20,'4'

    })
    //通过箭头函数解决
    setTimeout(() => {
      console.log(this.text, '3')
       //20,'3'

    })
  }
}
barObject.foo()

强制更改this指向

 实际代码开发中将经常会用到需要更改this指向的场景,而在js中强制更改this的方法有四个apply,bind,call,和new,其中new的优先级最高。不过new一般是构造函数创建新的实例时更改的this,不适用与我们需要指定指向一个this的场景,常用就是apply,bind,call,这三种方法。

apply

 需要传递两个参数第一个为被指向的对象,数组参数值

function foo(name, age) {
  console.log(name)
  //tom
  console.log(age)
  //13
  //this指向被更改为barObject
  console.log(this.text)
  //10
}
var barObject = {
  text: 10
}
foo.apply(barObject, ['tom', 13])

手写apply

Object.prototype.apply2 = function (context, argums) {
  // 异常捕捉,如果被指向的对象不存在则指向window
  if (typeof context === 'undefined' || context === null) {
    context = window
  }
  let symbol = Symbol()
  // 将调用函数和指向对象进行绑定
  context[symbol] = this
  // 进行调用,同时收集返回值
  let results = context[symbol](...argums)
  // 删除掉新增的元素,保持被指向对象的原样
  delete context[symbol]
  return results
}
//测试代码
function foo(name, age) {
  console.log(name)
  //tom
  console.log(age)
  //13
  //this指向被更改为barObject
  console.log(this.text)
  //10
}
var barObject = {
  text: 10
}
foo.apply2(barObject, ['tom', 13])

call

 需要传递参数第一个为被指向的对象,后面为参数列表

function foo(name, age) {
  console.log(name)
  //tom
  console.log(age)
  //13
  //this指向被更改为barObject
  console.log(this.text)
  //10
}
var barObject = {
  text: 10
}
foo.call(barObject, 'tom', 13)

手写实现

Object.prototype.call2 = function (context, ...argums) {
  // 异常捕捉,如果被指向的对象不存在则指向window
  if (typeof context === 'undefined' || context === null) {
    context = window
  }
  let symbol = Symbol()
  // 将调用函数和指向对象进行绑定
  context[symbol] = this
  // 进行调用,同时收集返回值
  let results = context[symbol](...argums)
  // 删除掉新增的元素,保持被指向对象的原样
  delete context[symbol]
  return results
}
//测试代码
function foo(name, age) {
  console.log(name)
  //tom
  console.log(age)
  //13
  //this指向被更改为barObject
  console.log(this.text)
  //10
}
var barObject = {
  text: 10
}
foo.call2(barObject, 'tom', 13)

bind

 需要传递参数第一个为被指向的对象,后面为参数列表,返回结果为一个改变this之后的函数需要手动执行一下。

function foo(name, age) {
  console.log(name)
  //tom
  console.log(age)
    //18
  console.log(this.text)
     //10
}
var barObject = {
  text: 10
}
let bindFn = foo.bind(barObject, 'tom')
bindFn(18)

手写实现

//最简化版
Object.prototype.bind2 = function (context) {
//若调用的不是函数则报错
if (typeof this !== "function") {
 throw new Error("Function.prototype.bind - what is trying to be bound is
not callable"); }
  let current = this
  let args = Array.prototype.slice.call(arguments, 1)
  return function () {
    let args2 = Array.prototype.slice.call(arguments)
    return current.call(context, ...args, ...args2)
  }
}
function foo(name, age) {
  console.log(name)
  //tom
  console.log(age)
    //18
  console.log(this.text)
     //10
}
var barObject = {
  text: 10
}
let bindFn = foo.bind2(barObject, 'tom')
bindFn(18)

但是有个问题,bind返回的函数如果再通过new 关键字构建一个实例时导致this指向了Object这时候就会丢失之前的this导致一部分参数失败。

function foo(name, age) {
  console.log(name)
  //tom
  console.log(age)
  //18
  console.log(this.text)
  //undefined
}
var barObject = {
  text: 10
}
let bindFn = foo.bind(barObject, 'tom')
let bindSon=new bindFn(18)

但是经过测试上面第一次手写bind会正确返回this.text的值,故重新优化一下

Object.prototype.bind2 = function (context) {
if (typeof this !== "function") {
 throw new Error("Function.prototype.bind - what is trying to be bound is
not callable"); }
  let current = this
  let args = Array.prototype.slice.call(arguments, 1)
  // 制作一个空函数
  let FNOP = function () {
  }
  let retrunFn = function () {
    let args2 = Array.prototype.slice.call(arguments)
    // 判断this是不是当前的this
    return current.call(this instanceof FNOP ? this : context, ...args, ...args2)
  }
  FNOP.prototype = this.prototype
  retrunFn.prototype = new FNOP()
  return retrunFn
}

function foo(name, age) {
  console.log(name)
  //tom
  console.log(age)
  //18
  console.log(this.text)
  //undefined
}
var barObject = {
  text: 10
}
let bindFn = foo.bind2(barObject, 'tom')
bindSon = new bindFn(18)

apply、bind、call三者区别

相同点:都可以改变this指向
不同点: 1.apply,call进行更改时都会直接调用一次要改变this的函数,bind不会自动调用需要主动触发。
2.bind和call传递的参数可以为多个参数,apply传递的为一个参数的数组。

new关键字

 new在js中是一个很重要的关键字,通过new可以给构造函数创建一个实例,他主要是做了四步,首先创建一个空对象,然后讲此对象的__proto__指向此构造函数的prototype让两者建立联系,其次更改这个空对象的this指向这个构造函数,最后调用这个构造函数的执行逻辑,若有返回值(引用类型,若返回基本类型处理和无返回值一样),将返回值返回(此时会失去构造函数上的方法和构造函数自身的属性,故构造函数不应该有引用类型返回值),若无返回值则将创建的新对象返回。

function Bar(name, age) {
  this.name1 = name + 'aaa'
  this.age1 = age * 3
  //删除返回值调用构造函数的方法不会报错
  //返回基本类型也会被视为无返回值处理
  return {
    name,
    age
  }
}
Bar.prototype.sayNmae = function () {
  console.log(this.name1)
}
Bar.prototype.sayAge = function () {
  console.log(this.age1)
}
let newBar = new Bar('tom', 19)
console.log(newBar)//{name:tom,age:19}
console.log(newBar.name)
//tom//undefined
console.log(newBar.age)
//19//undefined
console.log(newBar.name1)
//undefined //删除掉返回值返回为tomaaa
console.log(newBar.age1)
//undefined//删除掉返回值返回为57
newBar.sayNmae()
//报错//删除掉返回值返回为tomaaa
newBar.sayAge()
//报错//删除掉返回值返回为57

手写new,因为new关键字无法模拟,只能用函数进行替代

function objectFn() {
  let newObject = new Object()
  Construtor = [].shift.call(arguments)
  newObject.__proto__ = Construtor.prototype
  let salut = Construtor.apply(newObject, arguments)
  console.log(salut, 'salut')
  return typeof salut == 'object' ? salut : newObject
  // return newObject

}
function Bar(name, age) {
  this.name1 = name + 'aaa'
  this.age1 = age * 3
  //删除返回值调用构造函数的方法不会报错
  //返回基本类型也会被视为无返回值处理
  return {
    name,
    age
  }
}
Bar.prototype.sayNmae = function () {
  console.log(this.name1)
}
Bar.prototype.sayAge = function () {
  console.log(this.age1)
}
let newBar = objectFn(Bar, 'tom', 19)
console.log(newBar)//{name:tom,age:19}
console.log(newBar.name)
//tom//undefined
console.log(newBar.age)
//19//undefined
console.log(newBar.name1)
//undefined //删除掉返回值返回为tomaaa
console.log(newBar.age1)
//undefined//删除掉返回值返回为57
newBar.sayNmae()
//报错//删除掉返回值返回为tomaaa
newBar.sayAge()

后话

 本篇文章到此就已经结束了,本人在撰写时结合了自己工作时的经历,但是可能受限于资历尚浅可能会有些见解不能理解到位,如有不同见解欢迎在评论区一起讨论学习。感谢各位