this
ECMAScript规范: this关键字执行为当前执行环境的ThisBinding。
MDN:在绝大数情况下,函数的调用方式决定了this的值。
可以理解为,在JS中,this
的指向是调用是决定的,而不是创建时决定的,这就导致this的指向会让人迷惑,简单来说,this
具有运行期绑定的特性。
调用位置
调用位置就是函数在代码中被调用的位置,而不是声明的位置。
通过分析调用栈(到达当前执行位置所调用的所有函数)可以找到调用位置。
function baz() {
console.log('baz');
bar();
}
function bar() {
console.log('bar');
foo();
}
function foo() {
console.log('foo');
}
baz();
当我们调用baz
时,他会依次调用baz() --> bar() --> foo()
。
对于foo
:调用位置是在bar
函数中。
对于bar
:调用位置是在baz
函数中。
对于baz
:调用位置是在全局作用域。
全局上下文
在全局执行上下文中this
都指向全局对象(非严格模式)。
this
等价于window
对象var === this. === window
console.log(window === this); // true
var a = 1;
this.b = 2;
window.c = 3;
console.log(a + b + c); // 6
function sum() {
return this.a + this.b + this.c;
}
console.log(sum()); // 6
在非严格模式下,全局执行上下文中的this
和window
指向同一个内存对象。
函数上下文
在函数内部,this
的取决于函数被调用的方式
直接调用
非严格模式下,在全局环境下直接调用函数,this
指向window
.
function foo() {
return this;
}
console.log(foo() === window); // true
在严格模式下,在全局环境下直接调用函数,this
为undefiend
.
"use strict"
function foo() {
return this;
}
const res = foo();
console.log(res);
console.log(res === window); // false
箭头函数
所有的箭头函数没有自己的this,箭头函数的this指向和外层的相同
function Person(name) {
this.name = name;
this.say = () => {
var name = 'bj';
return this.name;
}
}
var person = new Person('bajiu');
console.log(person.say()); // bajiu
var a = 1;
function foo() {
setTimeout(() => {
console.log(this.a);
}, 1000)
}
foo(); // 1
foo.call({a: 2}); // 2
对象的属性
this指向调用函数的对象
var name = 'bj'
var person = {
name: 'bajiu',
getName: function() {
return this.name;
}
};
person.getName(); // 函数返回:bajiu
const getName = person.getName;
getName(); // 函数返回:bj
作为构造函数
this 被绑定到正在构建的新对象
new执行大致步骤:
- 创建一个空对象
- 获取构造函数
- 设置空对象的原型
- 绑定
this
并执行构造函数 - 确保返回值为对象,null不算对象
function Person(name) {
this.name = name;
this.age = 25;
this.say = function() {
console.log(`${this.name}: ${this.age}` );
}
}
var person = new Person('bajiu');
console.log(person.name); // bajiu
person.say(); // bajiu: 25
作为一个DOM事件处理函数
this指向出发事件的元素,也就是始事件处理锁绑定到DOM节点。
var ele = document.getElementById('id');
ele.addEventListener('click', function(e) {
console.log(this);
console.log(this === e.target); // true
});
HTML标签内联事件处理函数
this指向所在的DOM元素
<button onclick="console.log(this)">click me</button>
call、apply、bind
三个函数作用大体一致,call
和apply
的区别在于后面参数的形式(如下),bind则是返回了一个新的函数,这个新函数的this指向thisArg。
fun.call(thisArg, arg1. arg2, ……)
fun.apply(thisArg, [arg1, arg2, ……]);
var newFoo = foo.bind(thisArg)
thisArg
在fun
函数运行时指定this
值。需要注意的时,指定的this
值并不定时该函数执行时真正的this
值
- 非严格模式下,第一个参数如果为
null
和undefined
时,this
值会自动指向全局对象(浏览器中就是window
对象),同时值为原始值(数字、字符串、布尔值、symbol)的this
会指向该原始值自动包装的对象。 - 严格模式下,
this
传入值的。
// 非严格模式
var foo = function() {
console.log(this);
}
foo.call(null); // window
foo.call(undefined); // window
foo.call(1); // Number{1};
// 严格模式
'use strict'
var bar = function() {
console.log(this);
}
bar.call(null); // null
bar.call(undefined); // window
bar.call(1); // 1
call
call用于改变函数里面的this的指向,接受两个参数,第一个参数this的指向,可选参数,默认window。第二个参数调用函数需要的参数(数组形式)
Function.prototype.maCall = function(context) {
if(typeof this !== 'function') {
return;
}
const obj = context || window;
obj.fn = this;
cont params = [...arguments].slice(1);
cont result = obj.fn(...params);
delete obj.fn;
return result;
}
apply
apply用于改变函数里里面this的指向,接受多个参数,第一个参数为this的指向,可选参数,默认window。后面参数为调用函数需要的参数
Function.prototype.myApply = function(context) {
if(typeof this !== 'function') {
return;
}
const obj = context || window;
obj.fn = this;
const params = arguments[1] || [];
const res = obj.fn(...params);
delete obj.fn;
return res;
}
bind
this将永久的绑定到指定对象上(bind的第一个参数) 。bind改变this的指向,返回改变指向的函数,且只能改变一次。
bind
是Function
原型链中Function.prototype
的一个属性,每个函数都可以调用它。bind
本身也是一个函数,返回值也是函数,函数的名称为bound 函数名
function foo (a, b) {
console.log(this);
console.log(a, b);
return false;
}
let obj = {};
console.log(foo.bind(obj, 1, 2).name); // bound foo
var person = {
name: 'bajiu',
age: 25
};
function say(job) {
console.log(`${this.name}: ${this.age} ${job}`);
}
var f = say.bind(person);
console.log(f('FE')); // bajiu: 25 FE
手写bind
Function.prototype.myBind = function(context, ...arg) {
if(typeof this !== 'function') {
return;
}
const fn = this;
return function F(...arg2) {
if(this instanceof F) {
return new fn(...arg, ...arg2);
}
return fn.call(context, [...arg, ...arg2])
}
}
升级版手写bind
Function.prototype.bind = function(context, ...params) {
if(typeof this !== 'function') {
throw new Error('this is must function');
}
const _this = this;
var bound = function(...args) {
const finalArg = params.concat(args);
if(this instanceof bound) {
if(_this.prototype) {
function Empty(){};
Empty.prototype = _this.prototype;
bound.prototype = new Empty();
}
var result = _this.apply(this, finalArg);
const type = typeof result;
return result !== null && type === 'object'
|| type === 'function' ? result || this;
}
else {
return _this.apply(context, finalArg);
}
}
return bound;
}
new
new的实现过程大致分为五步
- 创建一个空对象
- 获取构造函数
- 设置空对象的原型
- 绑定
this
并执行构造函数 - 确保返回值为对象
手写new
function create(f, ...params) {
const obj = {};
obj.__proto__ = f.prototype;
const result = f.apply(obj, params);
return typeof result === 'object' ? result : obj;
}
思考题
奇怪的冷知识
var person = {
name: 'bajiu',
age: 25,
getName: () => {
console.log(this.name);
}
}
person.getName(); // 输出 空字符串
person.getName.call({name: 'bj'}); // 输出 空字符串
对this理解比较清楚的同学肯定会回答,最后输出都是undefiend
。
首先明确箭头函数无其自身的this,它的this指向与它上级的this指向相同,那此时this
指向window
, 此时就涉及到了一个冷知识了,window对象上默认是有name
属性的,且值默认为''