你不知道的JavaScript上 之【this】
ABCDdouyaer opened this issue · comments
关于this
1.概念
-
this是在运行时绑定,并不是在编写的时候绑定;
-
this的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式;
-
当一个函数被调用的时候,会创建一个活动记录(有时也称执行上下文),这个记录会包含函数在哪里被调用(调用栈)、函数的调用方式、传入的参数等信息,this就是这个记录的一个属性,会在函数执行的过程中用得到;
this的全面解析
1.调用位置(调用栈)
function baz(){
//当前调用栈:baz
//调用位置:全局作用域
console.log('baz');
bar(); //调用位置
}
function bar(){
//当前调用栈:baz->bar
//调用位置:baz
console.log('bar');
foo();//调用位置
}
function foo(){
//当前调用栈:baz->bar->foo
//调用位置:bar
console.log('foo');
}
baz();//调用位置
2.绑定规则
- 默认绑定:独立函数调用(非严格模式this绑定到全局window,严格模式不能将全局对象用于默认绑定,因此绑定到undefined),只要函数运行于费严格模式,默认绑定才会绑定到全局对象
var a = 2;
function foo1(){
console.log(this.a)//2
}
foo1();
function foo2(){
'use strict'
console.log(this.a)
//Uncaught TypeError: Cannot read property 'a' of undefined
}
foo2();
var a = 2;
function foo(){
console.log(this.a)//2
}
(function(){
'use strict'
foo();
})()
- 隐式绑定:函数被调用时,obj‘拥有’或者‘包含’它,它的前面加上了对obj的引用,对象属性引用链中只有上一层或者最后一层在调用位置中起作用
function foo(){
console.log(this.a);
}
var obj = {
a: 2,
foo,
}
obj.foo()//2
var obj1 = {
a: 42,
obj,
}
obj1.obj.foo()//2
function foo(){
console.log(this.a);
}
var obj = {
a: 2,
foo,
}
var bar = obj.foo;
//bar实际上引用的函数本身,等同于bar = foo
var a = 'oops, global';
bar();//oops,global
function foo(){
console.log(this.a);
}
var obj = {
a: 2,
foo,
}
var a = 'oops, global';
function dofn(fn){
fn()
}
//函数参数实际上是一种隐式赋值,类似(fn = foo)
dofn(obj.foo);//oops,global
-
强制绑定:call、apply、bind(硬绑定)
-
创建包裹函数
-
创建辅助函数
-
bind实现
-
API调用的上下文(js宿主环境中许多的内置函数都提供一个可选参数,用于指定执行上下文)
-
function foo(something){
return this.say() + something
}
var obj = {
say: function(){
return '小明说:'
}
}
var bar = function(){
return foo.apply(obj, arguments)
}
console.log(bar('您好!'))//小明说:您好!
function bind(fn, obj){
return function(){
fn.apply(obj, arguments)
}
}
var obj = {
a: 1,
}
function fn(str){
console.log(this.a, str)
}
var bar = bind(fn, obj)
bar(2);//1, 2
Function.prototype.hardBind = function(obj){
const fn = this;
const arr = [].slice.call(arguments, 1)
const bound = function(){
//由于此处已经是obj的引用所以call,apply,mybind都不能在改变this
return fn.apply(obj, arr)
}
bound.prototype = Object.create(fn.prototype);
return bound;
}
function fn(e){
console.log(this.a, e)
}
var obj = {
a: 2
}
var obj1 = {
a: 3
}
var obj2 = {
a: 4
}
let fn1 = fn.hardBind(obj, 1).hardBind(obj1, 2)
fn1();// 2, 1
fn1.call(obj2);// 2, 1
const arr = [1, 2, 3];
const obj = {a: 'ooh'};
function fn(e){
console.log(e, this.a)
}
arr.forEach(fn, obj);
/**
1 'ooh'
2 'ooh'
3 'ooh'
*/
-
new绑定(被new操作符调用的普通函数),new操作符调用函数自动执行下面操作
-
创建(或者构造)一个全新的对象
-
这个新对象会执行[[prototype]]连接
-
这个新对象会绑定到函数调用的this
-
如果函数没有返回其他对象,那么new表达式中的函数调用自动返回这个新对象
-
3.判断this(优先级)
-
函数是否在new中调用(new绑定)?是的话,this绑定的是新创建的对象
-
函数指定了硬绑定,是的话,this绑定的是指定对象
-
函数是否在某个上下文对象中调用(隐式绑定),是的话,this绑定的是那个上下文对象
-
如果都不是,使用默认绑定,严格模式绑定undefined,否则绑定全局对象
4.绑定例外
- 将null或者undefined作为this的绑定对象,实际应用的是默认规则(非严格全局,严格undefined)【绑定对象不重要情况下可以用null或者undefined占位】
//函数柯里化(该方法容易将全局绑定,比较危险)
const arr = [1, 2];
function foo(a, b){
console.log(`a:${a}`, `b:${b}`)
}
foo.apply(null, arr);
//安全做法
const arr = [1, 2];
function foo(a, b){
console.log(`a:${a}`, `b:${b}`)
}
foo.apply(Object.create(null), arr);
- 间接引用(默认规则)
var a = 2;
function foo(){
console.log(this.a)
}
var o = {a: 3, foo}
var p = {a: 4}
o.foo();//3
(p.foo = o.foo)();//2
- 软绑定,如果this为默认规则,则指向软绑定的对象,否则指向当前的调用对象
Function.prototype.softBind = function(obj){
const fn = this;
const arr = [].slice.call(arguments, 1)
const bound = function(){
const self = (!this || this === (global || window)) ? obj : this;
return fn.apply(self, arr)
}
return bound;
}
function f(e){
console.log(this.a, e)
}
var obj = {
a: 2
}
var obj1 = {
a: 3
}
var obj2 = {
a: 3
}
let f1 = f.softBind(obj, 1)
f1();// 2, 1
f1.call(obj1);// 3, 1
obj2.f = f.softBind(obj1, 3);
obj2.f();//3, 3