this
this 是函数运行时函数体内部产生的一个对象,只能在函数体内部访问,this 的用法有这么几种
js
function fn() {
this.x = 1;
}
- 在纯粹的函数调用中,this 属于全局调用,this 就是全局对象
js
var x = 1;
function fn() {
console.log(this.x);
}
fn(); // 1
- 作为对象方法调用,函数可以在对象中调用,此时 this 就指向该对象
js
function fn() {
console.log(this.x);
}
var obj = {};
obj.x = 'xxx';
obj.fn = fn;
obj.fn(); // xxx
- 作为构造函数调用,在 js 中任何一个函数都可以作为构造函数,通过 new 产生一个新对象,这时这个 this 就指向这个新对象
js
function fn() {
this.x = 1;
}
var obj = new fn();
obj.x; // 1
深入了解一下 this
先看一段代码
js
var obj = {
fn:function(){
console.log(this.test)
}
test:'test'
}
var fn = obj.fn
var test = '另一个test'
obj.fn() // test
fn() // 另一个test
这在 js 中是一个再正常不过的现象,这是因为在函数体内部使用了 this 关键字,说到底 this 指的是函数运行时所在的环境,那么为什么会这样呢,这就涉及到 js 中 this 的设计原理了,这其实跟内存的数据结构有关系
js
var obj = {
name: '666',
};
上述代码会将一个对象赋值给变量 obj ,我们知道在 js 中非基本数据类型是存在堆中的,而基本数据类型是存在栈中的,在 js 引擎执行到上述代码时,js 引擎会在内存生成一个对象 { name: 666 },然后把这个对象的内存地址赋值给变量 obj,就是说 obj 只是一个内存地址,这个地址指向的是在堆中存放的 { name: 666} 这个对象的地址。在读取 obj.name 时,js 引擎会先去访问变量 obj 从 obj 中拿到内存地址,然后再从改地址中得到 name 属性,并返回该属性。 在 js 中原始对象并不是行我们写的 { name: 666 } 这样,在内存中其实是以一种字典的形式存储的,每一个属性都会对应一个属性描述符,(Vue2 的响应式原理就是基于属性描述符实现的),name 属性的值保存在属性描述符的 value 属性中
这样的数据结构是合理清晰的,但是有一种特殊情况,属性的值可以是一个函数,这就不一样了,再看一段代码
js
var obj = {
fn: function () {},
};
在 js 引擎中会将函数单独保存在内存中,再将函数的地址赋值给 obj 的 fn 属性,这就不一样了,由于函数是一个单独的值,所以就可以在不同的上下文环境中执行,来看一张图
运行环境的影响
在 js 中允许在函数体内部,引用当前环境的其他变量,那么问题就来了,函数可以在不同的环境中运行,所以需要有一个东西或者说某一种机制去获取当前函数内部的运行环境(上下文),this 就很好的解决了这个问题,this 就是在函数体内部,指代当前函数的运行环境。来看一段代码和一张图
js
function fn() {
console.log(this.flag);
}
var flag = 'flag';
var obj = {
fn: fn,
flag: 'flag in obj',
};
// 单独执行
fn(); // flag
// 在 obj 上下文中执行
obj.fn(); // flag in obj
call apply bind
call apply bind 都是用来改变 this 指向 在函数调用时动态改变函数上下文,但是他们直接也有不同,可以分为两类
- call apply 这两个立即调用,对于这两个来说,两个 api 的作用完全一样只不过是传参的方式不同
- bind 是返回一个函数,便于后期调用
call
js
/**
* ctx 上下文
* 剩余参数为传递给函数的参数
*/
function.call(ctx,arg1,arg2...)
js
// 实现一个简单的call
Function.prototype.myCall = function (ctx, ...args) {
// ctx 为空默认为全局 window
ctx = ctx || window;
// 创建一个唯一标识符
const fnSymbol = Symbol();
// 将原始函数保存为 ctx 对象的一个属性,this 就是原始函数
ctx[fnSymbol] = this;
// 调用函数并将结果存储在 result 中
const result = ctx[fnSymbol](...args);
// 删除 ctx 对象属性
delete ctx[fnSymbol];
// 返回结果
return result;
};
apply
apply 与 call 类似 但是 apply 函数需要将参数作为数组传递
js
/**
* ctx 上下文
* 剩余参数
*/
function.apply(ctx,[argsArray])
js
// 实现一个简单的 apply 其实跟 call 一样
Function.prototype.myApply = function (ctx, args = []) {
// 默认指向 window
ctx = ctx || window;
// 创建一个唯一标识
const fnSymbol = Symbol();
// 存储原始函数
ctx[fnSymbol] = this;
// 调用函数存储结果
const result = ctx[fnSymbol](...args);
// 删除属性
delete ctx[fnSymbol];
// 返回结果
return result;
};
bind
bind 与 call apply 不同 bind 不会立即调用 而是会返回一个新函数,该函数将绑定到指定的上下文,当函数被调用时候在指定的上下文运行
js
/**
* ctx 上下文
* 剩余参数为传递给函数的参数
*/
function.bind(ctx,arg1,arg2,....)
js
// 实现一个简单的 bind
Function.prototype.myBind = function (ctx, ...args) {
// 存储 this
const fn = this;
// 返回一个新的函数,该函数将传入的参数与新函数的参数合并,并在新的上下文中使用 apply 调用原始函数
return function (...newArgs) {
return fn.apply(ctx, [...args, ...newArgs]);
};
};