Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

this、call和apply #2

Open
lynnic26 opened this issue Aug 31, 2017 · 0 comments
Open

this、call和apply #2

lynnic26 opened this issue Aug 31, 2017 · 0 comments

Comments

@lynnic26
Copy link
Owner

lynnic26 commented Aug 31, 2017

Why

JS编程中,this关键字经常会让人迷惑,
Function.prototype.callFunction.prototype.apply这两个方法也有这广泛的应用
同时它们也是面试过程中的常客

this

JS中的this总是指向一个对象,具体指向哪个对象是在运行时基于函数的执行环境动态绑定的,而非函数被声明时的环境。
从上面的描述可以得知,只有函数中才存在this

this的指向

this的指向大致分为以下4种

  • 作为对象的方法调用
  • 作为普通的函数调用
  • 构造器调用
  • Function.prototype.call或Function.prototype.apply调用

1. 作为对象的方法调用

当函数作为对象的方法被调用时,this指向该对象

let coder = {
     name: 'Lynnic',
     playBadminton:  function() {
         alert(this === obj);
         alert(this.name + ' plays badminton well');
     }
}
coder.playBadminton();

2. 作为普通函数调用

当函数不作为对象的属性被调用时,也就是普通函数方式,此时的this总是指向全局变量
在浏览器的JavaScript里,这个全局对象是window对象。

window.name = 'globalName';
var foo = {
    name:  'Lynnic',
    getName: function() {
        return this.name;
    }
}
var getName = foo.getName;
console.log( getName() ); // globalName

在ES5的strict模式下,这种情况下的this已经被规定不会指向全局对象,而是undefined

3. 构造器调用

除了宿主提供的一些内置函数,大部分JS函数都可以被当作构造器使用。
构造器的外表和普通函数一摸一样,区别在于被调用的方式。当用new运算符调用该函数时,
会返回一个对象。通常情况下,构造器的this就指向返回的这个对象

var Coder = function (name) {
   this.name = name;
}
var coder = new Coder('Lynnic');
console.log( coder.name );  // Lynnic

特殊情况下,如果构造器显式的返回一个object类型的对象,那么此次运算会返回这个对象,而不是我们之前期待的this

var Coder = function (name) {
   this.name = name;
   return {
       name:  'Kwan'
   }
}
var coder = new Coder('Lynnic');
console.log( coder.name );  // Kwan

如果构造器不显式的返回任何数据,或者是返回一个非对象类型的数据,就不会造成上述问题。

4. Function.prototype.call或Function.prototype.apply

和普通函数相比,这两个函数可以动态的改变传入函数的this
call和apply能很好的体现JS的函数式语言特性。

5.ES6中箭头函数的this

箭头函数里面 this 始终指向外部对象,因为箭头函数没有 this,因此它自身不能进行new实例化,同时也不能使用 call, apply, bind 等方法来改变 this 的指向。

丢失的this

var coder = {
    name: 'Lynnic',
    getName: function() {
        return this.name;
    }
}
console.log( coder.getName() ); // Lynnic
var getName = coder.getName;
console.log( getName() ); // undefined

call和apply

call和apply的区别

call和apply作用一样,区别仅在于传入参数的形式不同。

func.apply(thisArg, [argsArray])

thisArg: 指定了函数体内this对象的指向
argsArray: 为一个带下标的集合,这个集合可以是数组,也可以为类数组(array-like object)。apply把这个集合中的元素作为参数传给被调用的函数

function.call(thisArg, arg1, arg2, ...)

thisArg: 指定了函数体内this对象的指向
arg1, arg2 ...: 每个参数会依次传入函数

  • 当调用一个函数时,JS解释器并不会计较形参和实参的数量、类型以及顺序上的区别
  • call是包装在apply上面的一颗语法糖
  • 当使用call或apply的时候,如果我们传入的第一个参数为null,函数体内的this会指向默认的宿主对象,在浏览器中则是window
  • 有时候我们使用call或者apply的目的不在于指定this指向,而是另有用途,比如调用其它对象的方法。那么我们可以传入null来代替某个具体的对象
// Math.max([value1[, value2[, ...]]])
Math.max.apply(null, [1, 2, 3, 4, 5, 6]);

call和apply的用途

1. 修正this指向

document.getElementById('lynnic').onclick = function() {
    alert(this.id); // lynnic
    var func = function () {  
         alert(this.id);  // lynnic
    }
    func.call( this );  // 如果不用call的话,函数内部的this会指向window
}

2. Function.prototype.bind

Function.prototype.bind的实现

3. 借用其他对象的方法

场景A 借用构造函数实现继承

var A = function( name ) {
    this.name = name;
}
var B = function() {
    A.apply(this, arguments);
}
B.prototype.getName = function() {
    return this.name;
}
var b = new B( 'Lynnic' );
console.log( b.getName() ); // Lynnic

场景B 操作类数组

函数的参数列表 arguments是一个类数组对象,它虽然也有下标,但并非真正的数组,所以本身也不具有数组的一些方法。在这种情况下,可以借用Array.prototype对象上的方法,实现和数组相似的操作。

V8引擎中Array.prototype.push的具体实现

function ArrayPush() {
    var n = TO_UNIT32( this.length );  // 原来的对象的长度
    var m = %_ArgumentsLength();  // push的参数长度
    for(var i = 0; i < m; i++) {
         this[ i + n ] = %_Arguments( i );  // 复制元素
    }
    this.length = n + m;  // 修改length
    return this.length;
}

可以看出,Array.prototype.push根本不管被修改的对象是数组还是类数组对象,只要在里面追加属性(下标)是合法的和修改length可读写,就可以把任意对象传入Array.prototype.push

var a = {};
Array.prototype.push.call(a, 'first');
alert( a.length ); //1
alert( a[0] );  //first

可以借用Array.prototype.push方法的对象至少要满足的两个条件

  • 对象本身要可以存取属性
  • 对象的length属性可读写

例如,number类型的数据无法存取数据,function类型的数据length属性只读不可写,
它们都无法借用到Array.prototype.push方法

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant