this的概念:
在js中,this的意思为当前,是一个指针型变量,它动态指向当前函数的运行环境。
在不同的场景中调用同一个函数,this的指向也可能会发生变化,但是它永远指向其所在函数的真实调用者;如果没有调用者,就指向全局对象window。
普通函数: 关于this,谁调用就指向谁,没有调用者,就指向全局对象window。
箭头函数: 箭头函数的this指向于函数作用域所指向的对象。
默认绑定
全局环境下的this
在全局作用域下,this始终指向全局对象window,无论是否是严格模式!
congsole.log()完整的写法是window.console.log(),
window可以省略,window调用了console.log()方法,所以此时this指向window。
在全局环境定义的函数中使用 this
var name = 'Jenny';
function person() {
return this.name;
}
console.log(person()); //Jenny
因为调用函数的对象在浏览器中为window,因此this指向window,所以输出Jenny。
而在严格模式下,不能将全局对象用于默认绑定,this会绑定到undefined,只有函数运行在非严格模式下,默认绑定才能绑定到全局对象
隐式绑定
请记住,这里的目标是查看使用 this 关键字的函数定义,并判断 this 的指向。执行绑定的第一个也是最常见的规则称为 隐式绑定。80% 的情况下它会告诉你 this 关键字引用的是什么。
假如我们有一个这样的对象
const user = {
name: 'Tyler',
age: 27,
greet() {
alert(`Hello, my name is ${this.name}`)
}
}
现在,如果你要调用 user 对象上的 greet 方法,你会用到点号。
user.greet()
函数上的this
普通函数内的this分为两种情况,严格模式下和非严格模式下。
function test(){
'use strict'
console.log(this)
}
// 在严格模式下,必须严格的写出被调用的函数的对象,
// 不可以有省略或者说简写。
test() // undefined
window.test() // window
function test2(){
console.log(this)
}
//非严格模式下,通过test2()和window.test2()调用函数对象,this都指向window。
test2() // window
window.test2() // window
- 严格模式下,必须严格的写出被调用的函数的对象,不可以有省略或者说简写。
直接test()调用函数,this指向undefined,window.test()调用函数this指向window。
-
非严格模式下,通过test2()和window.test2()调用函数对象,this都指向window。
对象中的this
- 一层对象
let obj = {
name = 'A'
sayName: function(){
name='B'
console.log(this.name)
}
}
obj.sayName() // A
调用对象中的方法,此时 sayName() 中的this指向调用该方法的对象 obj
函数的定义位置不影响其this指向,this指向只和调用函数的对象有关。
- 二层对象
let obj = {
name: 'A',
sayName: function(){
name='B'
console.log(this.name)
}
obj2:{
name:'C'
sayName2: function(){
name='B'
console.log(this.name)
}
}
}
obj.sayName() // A
// 内部方法的 this 指向被调用函数最近的对象
obj.obj2.sayName2() // C
obj.obj2.sayName2()方法中的this指向obj2。
多层嵌套的对象内部方法的 this 指向被调用函数最近的对象(就近原则)
箭头函数中的this
箭头函数本身没有 this 和 arguments ,箭头函数体内的this对象,是定义该函数时所在的作用域指向的对象,而不是使用时所在的作用域指向的对象。
箭头函数没有自己的this指向,它会捕获自己定义所处的外层执行环境,并且继承这个环境的this,
箭头函数的this指向在被定义的时候就确定了,指向当前 定义时所在的对象,且之后永远都不会改变。即使使用 call() 、 apply() 、 bind() 等方法改变this指向也不可以。
对象内部方法的this: 指向调用这些方法的对象,也就是谁调用就指向谁。
var name = 'window'; // 其实是window.name = 'window'
var A = {
name: 'A',
sayHello: function(){
console.log(this.name)
}
}
A.sayHello();// 输出A
var B = {
name: 'B'
}
A.sayHello.call(B);//输出B
A.sayHello.call();//不传参数指向全局window对象,输出window.name也就是window
箭头函数中的 this 指向
箭头函数在编译时就能确定 this 指向 (编译时绑定)
var name = 'window';
var obj = {
name: 'obj ',
sayHello: () => {
console.log(this.name)
}
}
obj .sayHello(); // window
-
由于sayHello函数是箭头函数,所以自身不能绑定this,因此找它的上一级作用域。如果父级作用域还是箭头函数,就再往上找,一层一层的直到直到this的指向。
-
obj .sayHello(), 因为 JavaScript 没有块作用域,所以在定义 sayThis 的时候,里面的 this 就绑到 window 上去了
如何使sayHello永远绑定A呢:
var name = 'window';
var A = {
name: 'A',
sayHello: function(){
var s = () => console.log(this.name)
return s//返回箭头函数s
}
}
var sayHello = A.sayHello();
sayHello();// 输出A
var B = {
name: 'B';
}
sayHello.call(B); //还是A
sayHello.call(); //还是A
根据“该函数所在的作用域指向的对象”来分析一下:
- 该函数所在的作用域: 箭头函数s 所在的作用域是sayHello,因为sayHello是一个函数。
- 作用域指向的对象:A. sayHello指向的对象是A。
虽然箭头函数的this能够在编译的时候就确定了this的指向,但也需要注意一些潜在的坑
- 绑定事件监听
const button = document.getElementById('mngb');
button.addEventListener('click', ()=> {
console.log(this === window) // true
this.innerHTML = 'clicked button'
})
上述可以看到,我们其实是想要this为点击的button,但此时this指向了window
- 通过箭头函数在原型上添加方法时, this 会指向 window
function Person(name,age) {
this.name = name
this.age = age
}
// 用箭头函数在原型上添加方法时,this 会指向window
Person.prototype.sayName = () => {
// 此时this 指向 window
console.log(this === window) //true
return this.name
}
// 使用用匿名函数添加原型方法,this 会指向调用方法的的对象
Person.prototype.sayMyName = function(){
console.log(this)
return this.name
}
const person = new Person('mm');
console.log(person.sayName()); // window
console.log(person.sayMyName()); // mm
箭头函数其他几点需要注意的地方
-
箭头函数没有 this ,其中的 this指向当前 定义时所在的对象
-
箭头函数没有原型,不可以当作构造函数使用,不能使用new命令,否则会报错,占用内存小。
-
箭头函数中不存在 arguments 对象,如果要用,可以用 rest 参数代替。
-
不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
原型链中的this
this这个值在一个继承机制中,仍然是指向它原本属于的对象,而不是从原型链上找到它时,它所属于的对象。
new 绑定
构造函数的this
new构造调用的过程
无论是通过字面量还是通过new进行构造函数调用创建出来的对象,其实都一样。
用new 来调用,背地里创建一个连接到该函数的 prototype 成员的新对象,同时 this 会被绑定到新对象上
调用new的过程如下:
-
创建一个新对象
-
原型绑定
-
绑定this到这个新对象上
-
返回新对象
new 绑定
当用 new 调用函数时,JavaScript 解释器都会在底层创建一个全新的对象并把这个对象当做 this。如果用 new 调用一个函数,this 会自然地引用解释器创建的新对象。
function User (name, age) {
/*
JavaScript 会在底层创建一个新对象 `this`,它会代理不在 User 原型链上的属性。
如果一个函数用 new 关键字调用,this 就会指向解释器创建的新对象。
*/
//this:{}
this.name = name
this.age = age
}
const me = new User('Tyler', 27)
显式绑定(改变this指向)
如果 greet 函数不是 user 对象的函数,只是一个独立的函数。
function greet () {
alert(`Hello, my name is ${this.name}`)
}
const user = {
name: 'Tyler',
age: 27,
}
.call
“call” 是每个函数都有的一个方法,它允许你在调用函数时为函数指定上下文。
call(a, b, c)方法接收三个参数,第一个是this指向,第二个,三个是传递给函数的实参,可以是数字,字符串,数组等类型的数据类型都可以。
function greet () {
alert(`Hello, my name is ${this.name}`)
}
const user = {
name: 'Tyler',
age: 27,
}
greet.call(user)
// 让 greet 方法调用的时候将 this 指向 user 对象
call 是每个函数都有的一个属性,并且传递给它的第一个参数会作为函数被调用时的上下文。
换句话说,this 将会指向传递给 call 的第一个参数。
让 greet 方法调用的时候将 this 指向 user 对象
.call 不能传入数组
function greet (lang1, lang2, lang3) {
alert(`Hello, my name is ${this.name} and I know ${lang1}, ${lang2}, and ${lang3}`)
}
const user = {
name: 'Tyler',
age: 27,
}
const languages = ['JavaScript', 'Ruby', 'Python']
greet.call(user, languages[0], languages[1], languages[2])
call 的手写实现
/**
*
* 将执行函数 作为 所指向对象的一个参数进行调用执行,将 this 指向context
*
* call 与 apply 的差别是 传参是否为数组
* call 与 bind 的差别是, bind返回的是一个函数,不能立即执行,call 会立即执行
*
*/
Function.prototype.myCall = function (context){
console.log( 'this', this)
console.log('context',context);
// 1. 判断调用 myCall 的调用者是否为函数 即 foo 是否为函数
if( typeof this !== 'function') {
throw new Error('not a function')
}
// 2. 获取传入的参数 先将arguments 转为数组
// arguments 中 第一个参数为 改变指向的对象,其余参数为传入的参数
let args = [...arguments].slice(1)
console.log( 'args', args)
// 3. 判断是否传入参数 context, context 为 改变指向的对象
// 若没有指定指向则默认指向 window
context = context || window
// 4.将调用的函数设置为参数 context 的方法
context.fn = this
let result = null
// 执行调用的函数
result = context.fn(...args)
delete context.fn
return result
}
.apply
apply(a, [b])和call基本上一致,唯一区别在于传参方式,apply把需要传递给fn()的参数放到一个数组(或者类数组)中传递进去,虽然写的是一个数组,但是也相当于给fn()一个个的传递。
const languages = ['JavaScript', 'Ruby', 'Python']
// call() 传参的方式
greet.call(user, languages[0], languages[1], languages[2])
// apply() 传参的方式
greet.apply(user, languages)
apply 的手写实现
Function.prototype.myApply = function(context){
if( typeof this !== 'function'){
throw new Error(' not a function')
}
context = context || window
context.fn =this
// 判断 arguments[1] 是否传入参数
let result = arguments[1] ? context.fn(...arguments[1]) : context.fn();
delete context.fn
return result
}
foo.myApply(obj,[1,2,3,4])
.bind
其使用方法与call一致,唯一的区别在于立即执行还是等待执行。
-
call直接改变函数greet的指向 改变fn中的this,并且把fn立即执行
-
bind 返回一个函数 ,该函数改变了this 的指向,需要手动调用
function greet (lang1, lang2, lang3) {
alert(`Hello, my name is ${this.name} and I know ${lang1}, ${lang2}, and ${lang3}`)
}
const user = {
name: 'Tyler',
age: 27,
}
const languages = ['JavaScript', 'Ruby', 'Python']
// call() 改变greet中的this,并且立即执行
greet.call(user, languages[0], languages[1], languages[2])
// bind()改变greet中的this,greet并不执行
const newFn = greet.bind(user, languages[0], languages[1], languages[2])
newFn() // alerts "Hello, my name is Tyler and I know JavaScript, Ruby, and Python"
bind手写实现
/**
* bind 返回的是函数,无法立即执行
*
*
*/
Function.prototype.myBind = function(context) {
if( typeof this !== 'function') {
throw new Error(' not a function')
}
const self = this
context = context || window
let args = [...arguments].slice(1)
// 返回函数,实际返回的是 通过 call 实现
return function fn() {
return self.myCall(context,...args)
}
}
var newFunc = foo.myBind(obj,1,2,3,4);
newFunc()
小结
相同点:
call、apply和bind都是JS函数的公有的内部方法,他们都是重置函数的this,改变函数的执行环节。
不同点:
-
bind是创建一个新的函数,而call和aplay是用来调用函数;
-
call和apply作用一样,只不过call为函数提供的参数是一个个地罗列出来,而apply为函数提供的参数是一个数组
总结
优先级
new绑定优先级 > 显示绑定优先级 > 隐式绑定优先级 > 默认绑定优先级
判断 this 指向
- 独立函数调用,例如getUserInfo(),此时this指向全局对象window,严格模式需要指定调用对象,否则为undefined
- 对象调用,例如stu.getStudentName(),此时this指向调用的对象stu
- call()、apply()和bind()改变上下文的方法,this指向取决于这些方法的第一个参数,当第一个参数为null时,this指向全局对象window
- 箭头函数没有this,箭头函数里面的this只取决于包裹箭头函数的第一个普通函数的this
- new构造函数调用,this永远指向构造函数返回的实例上,优先级最高。
var name = 'global name';
var foo = function() {
console.log(this.name);
}
var Person = function(name) {
this.name = name;
}
Person.prototype.getName = function() {
console.log(this.name);
}
var obj = {
name: 'obj name',
foo: foo
}
var obj1 = {
name: 'obj1 name'
}
// 独立函数调用,输出:global name
foo();
// 对象调用,输出:obj name
obj.foo();
// apply(),输出:obj1 name
obj.foo.apply(obj1);
// new 构造函数调用,输出:p1 name
var p1 = new Person('p1 name');
p1.getName();
this解析流程图
参考