当前位置:首页 > 单片机 > 程序喵大人
[导读]前言:一转眼从事前端已经6年了,从当时的小白到如今大厂的技术专家,中间也走过不少弯路,从今天开始我会持续更新前端技术文章,并且整体的文章会进行体系梳理,整个知识体系分为:基础精讲,框架讲解,框架及工具原理,前端面试题精讲,大厂面试题收录精讲,前端新技术讲解。通过完善的技术精讲助力...

前言:一转眼从事前端已经6年了,从当时的小白到如今大厂的技术专家,中间也走过不少弯路,从今天开始我会持续更新前端技术文章,并且整体的文章会进行体系梳理,整个知识体系分为:基础精讲,框架讲解,框架及工具原理,前端面试题精讲,大厂面试题收录精讲,前端新技术讲解。通过完善的技术精讲助力各位快速成长,同时也希望各位能够在我的帮助下获得比较好的offer,少走弯路,大家一起加油。那么开始我们的第一篇文章吧。


头疼的this

    为什么说是头疼的this,对于常年使用C ,C#,Java等这些面向对象语言的程序员来说,几乎天天都和this打交道。在这些语言里,this含义非常明确,就是指向当前的对象实例,我们用起来也是相当的放心。然而,到了JavaScript这个动态语言里,this的写法没变,但是其含义却大大地不同了,JavaScript中的this总是让人迷惑,应该是js众所周知的坑之一。个人也觉得js中的this不是一个好的设计,由于this晚绑定的特性,它可以是全局对象,当前对象,或者…有人甚至因为坑大而不用this,我们一般在面试中面试官又会扔过来一堆各种各样的考察this的题目,很是头疼。

那到底什么是this?this是什么?

    this是指包含它的函数作为方法被调用时所属的对象。这句话理解起来感觉还是很拗口的,但是如果你把它拆分开来变成这三句话后就好理解一点了。

  • 1.包含它的函数

  • 2.作为方法被调用时

  • 3.所属的对象

一、this的指向

首先我们先要记住this的几个特点:

  • 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可以动态的改变函数的this

var obj1 = { name: 1, getName: function (num = '') { return this.name num; }};
var obj2 = { name: 2,}// 可以理解成在 obj2的作用域下调用了 obj1.getName()函数console.log(obj1.getName()); // 1console.log(obj1.getName.call(obj2, 2)); // 2 2 = 4console.log(obj1.getName.apply(obj2, [2])); // 2 2 = 41.5箭头函数

箭头函数不会创建自己的this,它只会从自己的作用域链的上一层继承this。因此,在下面的代码中,传递给setInterval的函数内的this与封闭函数中的this值相同:

this.name = 2var 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.getElementByIdgetDomById('div1') // Uncaught TypeError: Illegal invocation(非法调用)这是因为:

  • 当我们去调用document对象的方法时,方法内的this指向document

  • 当我们用getId应用document内的方法,再以普通函数的方式调用,函数内容的this就指向了全局对象。

利用call和apply修正情况二

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;

  • 调用函数并且不指定第一个参数;

1.调用构造函数来实现继承

通过“借用”的方式来达到继承的效果:

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被指向了obj

function 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  参数列表;

第一版代码

上面提到的3点,仅仅完成了一点,且传入的参数

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); // 2console.log(bar.setCall(obj, 'kevin', 18));

三、bind

提到了callapply,就绕不开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值,还是挺坚挺的哦~


如果您看到了最后,不妨收藏、点赞、评论一下吧!!!

持续更新,您的三连就是我最大的动力,共勉!





本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: 驱动电源

在工业自动化蓬勃发展的当下,工业电机作为核心动力设备,其驱动电源的性能直接关系到整个系统的稳定性和可靠性。其中,反电动势抑制与过流保护是驱动电源设计中至关重要的两个环节,集成化方案的设计成为提升电机驱动性能的关键。

关键字: 工业电机 驱动电源

LED 驱动电源作为 LED 照明系统的 “心脏”,其稳定性直接决定了整个照明设备的使用寿命。然而,在实际应用中,LED 驱动电源易损坏的问题却十分常见,不仅增加了维护成本,还影响了用户体验。要解决这一问题,需从设计、生...

关键字: 驱动电源 照明系统 散热

根据LED驱动电源的公式,电感内电流波动大小和电感值成反比,输出纹波和输出电容值成反比。所以加大电感值和输出电容值可以减小纹波。

关键字: LED 设计 驱动电源

电动汽车(EV)作为新能源汽车的重要代表,正逐渐成为全球汽车产业的重要发展方向。电动汽车的核心技术之一是电机驱动控制系统,而绝缘栅双极型晶体管(IGBT)作为电机驱动系统中的关键元件,其性能直接影响到电动汽车的动力性能和...

关键字: 电动汽车 新能源 驱动电源

在现代城市建设中,街道及停车场照明作为基础设施的重要组成部分,其质量和效率直接关系到城市的公共安全、居民生活质量和能源利用效率。随着科技的进步,高亮度白光发光二极管(LED)因其独特的优势逐渐取代传统光源,成为大功率区域...

关键字: 发光二极管 驱动电源 LED

LED通用照明设计工程师会遇到许多挑战,如功率密度、功率因数校正(PFC)、空间受限和可靠性等。

关键字: LED 驱动电源 功率因数校正

在LED照明技术日益普及的今天,LED驱动电源的电磁干扰(EMI)问题成为了一个不可忽视的挑战。电磁干扰不仅会影响LED灯具的正常工作,还可能对周围电子设备造成不利影响,甚至引发系统故障。因此,采取有效的硬件措施来解决L...

关键字: LED照明技术 电磁干扰 驱动电源

开关电源具有效率高的特性,而且开关电源的变压器体积比串联稳压型电源的要小得多,电源电路比较整洁,整机重量也有所下降,所以,现在的LED驱动电源

关键字: LED 驱动电源 开关电源

LED驱动电源是把电源供应转换为特定的电压电流以驱动LED发光的电压转换器,通常情况下:LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: LED 隧道灯 驱动电源
关闭