一文聊透This

    技术2022-07-12  82

    This

    this指向当前属性所在对象。

    var A = { name: '张三', describe: function () { return '姓名:'+ this.name; } }; var name = '李四'; var f = A.describe; f() // "姓名:李四"

    JavaScript 语言之中,一切皆对象,运行环境也是对象,所以函数都是在某个对象之中运行,this就是函数运行时所在的对象(环境)。这本来并不会让用户糊涂,但是 JavaScript 支持运行环境动态切换,也就是说,this的指向是动态的,没有办法事先确定到底指向哪个对象,这才是最让初学者感到困惑的地方。

    this的指向分类

    全局环境window

    只要是单独在全局环境下执行的函数,其this都是指向全局环境。

    构造函数

    构造函数中的this,指的是实例对象。

    var Obj = function (p) { this.p = p; };

    上面代码定义了一个构造函数Obj。由于this指向实例对象,所以在构造函数内部定义this.p,就相当于定义实例对象有一个p属性。

    var o = new Obj('Hello World!'); o.p // "Hello World!"

    对象的方法

    this是没有链式调用的。只会指向当前运行的对象环境,如果在当前对象环境中找不到某变量,则不能进行比较。 就是内层的函数f2(函数内的函数)this不指向外部,而指向顶层对象。

    为了避免在同一对象内多次调用this其指向不统一的现象,可以通过一个变量固定this。在进行使用this。

    var o = { f1: function() { console.log(this); var that = this; var f2 = function() { console.log(that); }(); } } o.f1() // Object // Object

    操作数组时,其回调函数中不要使用绑定this。

    var o = { v: 'hello', p: [ 'a1', 'a2' ], f: function f() { this.p.forEach(function (item) { console.log(this.v + ' ' + item); }); } } o.f() // undefined a1 // undefined a2

    原因同上,内层函数this指向顶层对象。

    解决办法:

    使用中间变量

    传递运行环境

    var o = { v: 'hello', p: [ 'a1', 'a2' ], f: function f() { this.p.forEach(function (item) { console.log(this.v + ' ' + item); }, this); } } o.f() // hello a1 // hello a2

    尽量避免在回调函数中使用this。

    改变this指向的方法

    函数的四种调用方式

    方法调用,作为对象的方法进行调用函数表达式定义,并调用构造函数调用apply、call调用

    1. call

    function函数特有的call方法可以指定函数内部的this指向。call方法的参数应该是一个对象,如果参数为空、null和undefined,则默认传入全局对象。

    call的作用是把函数放入指定作用域执行。

    2. apply

    与call的区别在与传递参数为数组。

    3. bind

    bind绑定作用域后返回新的函数。

    回调函数使用bind()方法。

    手动实现call、apply、bind

    手动实现call 语法:

    fun.call(thisArg, arg1, arg2, …)

    其中thisArg取值可能为:

    不传,或者传null,undefined, this指向window对象传递另一个函数的函数名fun2,this指向函数fun2的引用值为原始值(数字,字符串,布尔值),this会指向该原始值的自动包装对象,如 String、Number、Boolean传递一个对象,函数中的this指向这个对象 Function.prototype.call = function (context) { // 判断context对象类型 如果为undefined或null则进行对象修改为windows // 判断是否是undefined和null if (typeof context === 'undefined' || context === null) { context = window } //在context上定义函数fn context.fn = this; //保存传递进来的参数 var args = []; for(var i = 1; i < arguments.length; i++){ args.push('arguments['+ i +']'); } //通过eval函数执行fn方法 var result = eval('context.fn(' + args.join(',') + ')'); // 删除fn方法 delete context.fn; //返回结果值 return result; } 手动实现apply apply语法:

    fun.apply(thisArg, [argsArray])

    argsArray: 一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 fun 函数。

    Function.prototype.myApply = function (context, arr) { // 判断context对象类型 如果为undefined或null则进行对象修改为windows // 判断是否是undefined和null if (typeof context === 'undefined' || context === null) { context = window } //在context上定义函数fn,指向目标函数 context.fn = this; var result; if (!arr) { result = context.fn(); } else { //保存传递进来的参数 var args = []; for(var i = 1; i < arguments.length; i++){ args.push('arguments['+ i +']'); } //通过eval函数执行fn方法 result = eval('context.fn(' + args.join(',') + ')'); } // 删除fn方法 delete context.fn; //返回结果值 return result; }

    总结: apply和call都是用来改变函数的this指向的。 区别在于传参形式不同。 其实现方法类似于一次性的。 3. 手动实现bind

    bind语法

    func.bind(thisArg[, arg1[, arg2[, …]]])

    bind功能: bind() 方法会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数。(来自于 MDN ) 特点:

    返回一个函数可以传入参数支持柯里化传参当bind绑定后的函数作为构造函数时,绑定的this失效。实例的原型对象指向绑定前的构造函数原型。但是参数会继续传递。 Function.prototype.myBind = function(context) { //判定其是否为函数,如果不是函数则抛出异常 if(typeof this !== "function") { throw new Error("Function.prototype.bind - what is trying to be bound is not callable"); } //获取参数 //这里的this指的是bind前的函数 var self = this; var args = Array.prototype.slice.call(arguments, 1); var fNOP = function () {}; var fBound = function () { //这里的 arguments是bind返回的函数传入的参数 var bindArgs = Array.prototype.slice.call(arguments); //判断返回的函数中的this是全局函数调用还是new 构造函数调用 return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs)); } fNOP.prototype = this.prototype; fBound.prototype = new fNOP(); return fBound; }
    Processed: 0.015, SQL: 9