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