this 是 JS 中常见的属性,但是经常会使人晕头转向,本文总结了一些常见的使用场景,希望能加深大家的认识。本文主要包含以下内容:
- this 常见场景
- 手动实现 call、bind
全局 this
- 浏览器宿主的全局环境中,this 指向 window 对象
- Node 命令行中,this 是全局命名空间,可以通过 global 来访问;在 Node 环境执行的 JS 脚本,this 是个空对象,不同于 global
函数方法里的 this
函数只是一个包含指针的变量名,它的结果根据执行上下文决定
函数中读写变量,是通过作用域链查找的。所以只要梳理出作用域链就能容易分辨出 this 指向
原型中的 this
同一函数创建的所有实例均共享一个原型。如果你给原型赋值了一个数组,那么所有实例都能获取到这个数组。除非你在某个实例中对其进行了重写(实事上是进行了覆盖)。
function Thing() {}
Thing.prototype.things = [];
var thing1 = new Thing();
var thing2 = new Thing();
thing1.things.push("foo");
console.log(thing2.things); //logs ["foo"]
复制代码
通常上面的做法是不正确的,改变 thing1 的同时也影响了 thing2。
function Thing() {
this.things = [];
}
var thing1 = new Thing();
var thing2 = new Thing();
thing1.things.push("foo");
console.log(thing1.things); //logs ["foo"]
console.log(thing2.things); //logs []
复制代码
在JavaScript中,函数可以嵌套函数,也就是你可以在函数里面继续定义函数。但内层函数是通过闭包获取外层函数里定义的变量值的,而不是直接继承this。
function Thing() {}
Thing.prototype.foo = "bar";
Thing.prototype.logFoo = function () {
var info = "attempting to log this.foo:";
function doIt() {
console.log(info, this.foo);
}
doIt(); // this 指向 window
}
var thing = new Thing();
thing.logFoo(); //logs "attempting to log this.foo: undefined"
复制代码
将实例的方法作为参数传递时,实例上下文并不会带过去。因为只是传递了一个函数的指针。
function Thing() {
}
Thing.prototype.foo = "bar";
Thing.prototype.logFoo = function () {
console.log(this.foo);
}
function doIt(method) {
method(); // 方法调用时,this 都是指向上下文
}
var thing = new Thing();
thing.logFoo(); //logs "bar"
doIt(thing.logFoo); //logs undefined
复制代码
解决办法是在传递参数时,使用 bind 方法显示指明上下文
function Thing() {
}
Thing.prototype.foo = "bar";
Thing.prototype.logFoo = function () {
console.log(this.foo);
}
function doIt(method) {
method();
}
var thing = new Thing();
doIt(thing.logFoo.bind(thing)); //logs bar
复制代码
call 实现
call 的作用是改变函数 this 的指向,调用函数并返回结果
Function.prototype.Call = function (context) {
context = context || global;
// this 指向 Call 的调用方,即函数 bar
// step1:将函数 bar 作为 context 的属性,this 指向 context
context.fn = this;
let args = [];
// arguments 和 this 是函数默认的两个属性
// 第一个参数是 context,所以 i 从 1 开始
for (let i = 1, len = arguments.length; i < len; i++) {
args.push(arguments[i]);
}
// step2:调用函数
const res = context.fn(...args);
// 如果不删除,会发生什么?
delete context.fn;
return res;
};
const bar = function (name, age) {
this.name = name;
this.age = age;
console.log(this.name, this.age);
console.log(this.value);
};
const foo = {
value: 1
};
bar.Call(foo, "sam", 29);
// sam 29
// 1
复制代码
? 与 apply 的区别是从第二个参数开始的,call 的接受参数列表,apply 接受参数数组
bind 实现
bind 的作用
- bind 改变函数的 this 指向
- 返回新的函数,如果新函数是构造函数(被 new 调用),this 需要指向实例
- 原型链的桥接
Function.prototype.bindThis = function (context) {
const self = this; // this 是 bindThis 的调用方,一般是函数
const args = Array.prototype.slice.call(arguments, 1);
// 更改 this
const fn = function () {
return self.apply(
// 这里的 this 是实例
// 新函数如果为 new 的构造函数,this 会有所不同
// new 会将 this 指向新实例
this instanceof fn ? this : context,
args.concat(Array.prototype.slice.call(arguments))
);
};
// 原型链桥接,并通过空函数防止原型链公用
const fNOP = function () {};
fNOP.prototype = this.prototype;
fn.prototype = new fNOP();
return fn;
};
function foo(a, b, c) {
console.log("name: >>", this.name + a + b + c);
}
const bar = {
name: "balabala"
};
foo.bindThis(bar)("变身", "美丽", "智慧")
// name: >> balabala变身美丽智慧
复制代码
小结
this 是一个非常“神奇”的属性,涉及到的知识面也很多,需要我们对原型,作用域等知识有清晰的认识。
如果你看完这篇文章仍然觉得很迷惑,别担心,这是正常的,这部分知识本来就比较抽象,需要你多看多练习,慢慢的就会理解 ?
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END