JS基础精讲之【头疼的this】
扫描二维码
随时随地手机看文章
头疼的this 为什么说是头疼的this,对于常年使用C ,C#,Java等这些面向对象语言的程序员来说,几乎天天都和this打交道。在这些语言里,this含义非常明确,就是指向当前的对象实例,我们用起来也是相当的放心。然而,到了JavaScript这个动态语言里,this的写法没变,但是其含义却大大地不同了,JavaScript中的this总是让人迷惑,应该是js众所周知的坑之一。个人也觉得js中的this不是一个好的设计,由于this晚绑定的特性,它可以是全局对象,当前对象,或者…有人甚至因为坑大而不用this,我们一般在面试中面试官又会扔过来一堆各种各样的考察this的题目,很是头疼。那到底什么是this?this是什么? this是指包含它的函数作为方法被调用时所属的对象。这句话理解起来感觉还是很拗口的,但是如果你把它拆分开来变成这三句话后就好理解一点了。
- 1.包含它的函数
- 2.作为方法被调用时
- 3.所属的对象
- this 总是(非严格模式下)指向一个对象,而具体指向哪个对象是在运行时基于函数的
执行环境
动态绑定的,而非函数被声明时的环境; - 除了不常用的with和eval的情况,具体到实际应用中,this指向大概可以分为四种:
- 作为对象的方法调用;
- 作为普通函数调用;
- 构造器调用;
- call 或 apply调用;
- 箭头函数中,this指向函数上层作用域的this;
- 构造器和普通函数的区别在于
被调用的方式
; - A,call(B) => 可以理解成在B的作用域内调用了A方法;
1.1 作为对象的方法调用
当函数作为对象的方法被调用时,this指向该对象
var obj = {
a: 'jianxi',
getName: function(){
console.log(this === obj);
console.log(this.a);
}
};
obj.getName(); // true jainxi
1.2 作为普通函数被调用当函数不作为对象的属性被调用,而是以普通函数的方式,this总是指向全局对象(在浏览器中,通常是Window对象)
window.name = 'jianxi';
var getName = function(){
console.log(this.name);
};
getName(); // jianxi
或者下面这段迷惑性的代码window.name = 'koa'
var obj = {
name: 'jianxi',
getName: function(){
console.log(this.name)
}
};
var getNew = obj.getName;
getNew(); // koa
而在ES5的严格模式下,this被规定为不会指向全局对象,而是undefined
1.3 构造器调用
除了一些内置函数,大部分Js中的函数都可以成为构造器,它们与普通函数没什么不同构造器和普通函数的区别在于被调用的方式
:当new运算符调用函数时,总是返回一个对象,this通常也指向这个对象var MyClass = function(){
this.name = 'jianxi';
}
var obj = new MyClass()
obj.name; // jianxi
但是,如果显式的返回了一个object对象,那么此次运算结果最终会返回这个对象。var MyClass = function () {
this.name = 1;
return {
name: 2
}
}
var myClass = new MyClass();
console.log('myClass.name:', myClass.name); // { name: 2}
只要构造器不显示的返回任何数据,或者返回非对象类型的数据,就不会造成上述问题。1.4 call或apply调用
跟普通的函数调用相比,用call和apply可以动态的改变函数的thisvar obj1 = {
name: 1,
getName: function (num = '') {
return this.name num;
}
};
var obj2 = {
name: 2,
}
// 可以理解成在 obj2的作用域下调用了 obj1.getName()函数
console.log(obj1.getName()); // 1
console.log(obj1.getName.call(obj2, 2)); // 2 2 = 4
console.log(obj1.getName.apply(obj2, [2])); // 2 2 = 4
1.5箭头函数箭头函数不会创建自己的this,它只会从自己的作用域链的上一层继承this。因此,在下面的代码中,传递给setInterval的函数内的this与封闭函数中的this值相同:this.name = 2
var obj = {
name: '1',
getName: () => {
console.log(this.name)
}
}
obj.getName()
1.6常见的坑就像标题一样,有的时候this
会指向undefined情况一var obj = {
name: '1',
getName: function (params) {
console.log(this.name)
}
};
obj.getName();
var getName2 = obj.getName;
getName2()
这个时候,getName2()作为普通函数被调用时,this指向全局对象——window。情况二当我们希望自己封装Dom方法,来精简代码时:
var getDomById = function (id) {
return document.getElementById(id);
};
getDomById('div1') //dom节点
那么我们看看这么写行不行?var getDomById = document.getElementById
getDomById('div1') // Uncaught TypeError: Illegal invocation(非法调用)
这是因为:- 当我们去调用
document
对象的方法时,方法内的this指向document
。 - 当我们用getId应用document内的方法,再以普通函数的方式调用,函数内容的this就指向了全局对象。
document.getElementById = (function (func) {
return function(){
return func.call(document, ...arguments)
}
})(document.getElementById)
// 利用立即执行函数将document保存在作用域中
二、call和apply
不要因为它的“强大”而对它产生抗拒,了解并熟悉它是我们必须要做的,共勉!1.call和apply区别
先来看区别,是因为它们几乎没有区别,下文代码实例call和apply都可以轻易的切换。当它们被设计出来时要做到的事情一摸一样,唯一的区别就在于传参的格式不一样
- apply接受两个参数
- 第一个参数指定了函数体内this对象的指向
- 第二个参数为一个带下标的参数集合(可以是数组或者类数组)
- call接受的参数不固定
- 第一个参数指定了函数体内this对象的指向
- 第二个参数及以后为函数调用的参数
arguments
对象在函数中引用函数的参数。此对象包含传递给函数的每个参数,它本身就是一个类数组,我们apply在实际使用中更常见一些。call是包装在apply上面的语法糖,如果我们明确的知道参数数量,并且希望展示它们,可以使用call。当使用call或者apply的时候,如果我们传入的第一个参数为null,函数体内的this会默认指向宿主对象,在浏览器中则是window
。借用其他对象的方法我们可以直接传null来代替任意对象Math.max.apply(null, [1, 2, 3, 4, 5])
2.call和apply能做什么?
使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数——来时MDN(opens new window)- 调用构造函数来
实现继承
; - 调用函数并且指定上下文的
this
; - 调用函数并且不指定第一个参数;
function Product(name, price) {
this.name = name;
this.price = price;
}
function Food(name, price) {
Product.call(this, name, price); //
this.category = food;
}
var hotDog = new Food('hotDog', 20);
2.调用函数并且指定上下文的 this
此时this被指向了objfunction showName() {
console.log(this.id ':' this.name);
};
var obj = {
id: 1,
name: 'jianxi'
};
showName.call(obj)
3.使用call单纯的调用某个函数Math.max.apply(null, [1,2,3,10,4,5]); // 10
三、模拟实现一个call
先来看一下call帮我们需要做什么?var foo = {
value: 1
};
function show() {
console.log(this.value);
};
show.call(foo); //1
就像解方程,要在已知条件中寻找突破哦口:call
使得this的指向变了,指向了foo;show
函数被执行了;- 传入的参数应为
this
参数列表;
var foo = {
value: 1
}
function show() {
console.log(this.value);
}
Function.prototype.setCall = function (obj) {
console.log(this); // 此时this指向show
obj.func = this; // 将函数变成对象的内部属性
obj.func(obj.value) // 指定函数
delete obj.func // 删除函数,当做什么都没发生~
}
show.setCall(foo)
第二版代码为了解决参数的问题,我们要能获取到参数,并且正确的传入:var foo = {
value: 1
}
function show(a, b) {
console.log(this.value);
console.log(a b)
}
Function.prototype.setCall = function (obj) {
obj.fn = this; // 将函数变成对象的内部属性
var args = [];
for(let i = 1; i < arguments.length; i ){
args.push('arguments[' i ']');
}
eval('obj.fn(' args ')'); // 传入参数
delete obj.fn; // 删除函数,当做什么都没发生~
}
show.setCall(foo, 1, 2); // 1 3
此时,我们就可以做到,传入多个参数的情况下使用call了,但是如果你仅想用某个方法呢?第三版代码Function.prototype.setCall = function (obj) {
var obj = obj || window;
obj.fn = this;
var args = [];
for(var i = 1, len = arguments.length; i < len; i ) {
args.push('arguments[' i ']');
}
var result = eval('obj.fn(' args ')');
delete obj.fn;
return result;
};
// 测试一下
var value = 2;
var obj = { value: 1 };
function bar(name, age) {
console.log(this.value);
return {
value: this.value,
name: name,
age: age
}
}
bar.setCall(null); // 2
console.log(bar.setCall(obj, 'kevin', 18));
三、bind
提到了call和apply,就绕不开bind(),来看一下MDN上对**bind()**的解释:bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。bind 可以被new , 可以进行构造函数我们用Js来模拟一个bind方法,以便加深我们的认识Function.prototype.newBind = function(context) {
if(typeof this !== 'function') {
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
}
var self = this;
var args = Array.prototype.slice.call(arguments, 1); // 间接调用数组方法,获取第一次传的参数
let tempFn = function {}; // 利用一个空函数作为中转
tempFn.prototype = this.prototype; // 修改返回函数的 prototype 为绑定函数的 prototype,实例就可以继承绑定函数的原型中的值
var resultFn = function () {
var innerArgs = Array.prototype.slice.call(arguments);
if (this instanceof tempFn) { // 如果 返回函数被当做构造函数后,生成的对象是 tempFn 的实例,此时应该将 this 的指向指向创建的实例。
return self.apply(this, args.concat(innerArgs));
} else {
return self.apply(context, args.concat(innerArgs))
}
}
resultFn = new tempFn();
return resultFn;
}
这样看上去,bind总会帮我们返回同样的
this
值,还是挺坚挺的哦~如果您看到了最后,不妨收藏、点赞、评论一下吧!!!持续更新,您的三连就是我最大的动力,共勉!