this知识点总结

this 知识点总结

this是js中比较重要的一个点,理论其实是很简单的,但用起来很容易出错。

this规则

首先this概念是针对对象的,函数与函数之间的调用不存在this关系。同时this的确定是在运行时才能确定,和作用域的编译阶段确认不同。最基本的规则是:当调用者(对象)调用函数时,函数内部的this就指向调用者,否则指向undefined(非严格状态下指向window对象)。除最基本的规则外,this可以被显式的改变,最典型的就是bind、call、apply方法。

this基本规则解析

根据定义,这里套用别人的例子。

  1. 普通this示例。示例中getA属于obj对象,当obj调用getA方法时,方法是obj拥有的方法,此时显示obj中的a。当getA被取出时,此时getA指向的是函数getA所在的地址,提取出来的getA与obj脱离了关系,调用者不再是obj,此时this指向了window。
var a = 1
var obj = {
  a: 10,
  getA: function () {
    console.log(this.a)
  },
}

obj.getA() // 10

var getA = obj.getA
getA() // 1
  1. 箭头函数下示例。箭头函数没有this和arguments,它的this是共享父级或父级的父级作用域下的this。如下面的例子,getA由于是箭头函数,它共用了父级(window)的作用域下的this,而此时即使在严格模式下this也是指向window的。而parentA是普通函数,它的this指向的是obj,内部的箭头函数this指向的是parentA的this,也就是obj。
'use strict'
var a = 1
var obj = {
  a: 10,
  getA: () => {
    console.log(this.a)
  },
  parentA: function () {
    (() => {
      console.log(this.a)
    })()
  }
}

obj.getA() // 1
obj.parentA() // 10

new对象的this指向

javascript中的new发生了这些(原文):

  1. 创建了一个新对象,挂载原型链。

  2. 调用构造函数,将this指向我们新建的对象。这样我们可以在new出来的对象上挂载新的变量和函数。

  3. 构造函数如果返回了一个对象,那么new操作符返回的是这个对象。如果构造函数没有返回对象,那么new操作符返回new创建的新对象。

所以我们新生成的对象下的方法和原型链上的方法,当对象调用这些方法时,this指向的就是我们新的对象,即谁调用指向谁(仅限于对象)。

显式绑定this

显示绑定this的方法包括:bind,call,apply。call和apply是在调用函数时进行动态绑定,而bind会返回一个被绑定到指定对象的新函数。如果绑定的null则会指向原来的this。

(示例取自You-Dont-Know-Js)[https://github.com/getify/You-Dont-Know-JS/blob/master/this %26 object prototypes/ch2.md]

  1. call、apply、bind优先级的确认

示例:

function foo() {
	console.log( this.a );
}

var obj1 = {
	a: 2,
	foo: foo
};

var obj2 = {
	a: 3,
	foo: foo
};

obj1.foo(); // 2
obj2.foo(); // 3

obj1.foo.call( obj2 ); // 3
obj2.foo.call( obj1 ); // 2

var baz = foo.bind(obj1)
baz() // 2
baz.call(obj2) // 2

call和apply调用时函数的this绑定到了call或apply的第一个参数,而bind返回的函数则不管函数如何调用,函数内的this都指向了bind时的参数。也是就是bind的优先级要高于call和apply

  1. new优先级的确认

    看下面的示例。示例中new之后,如果是非显示绑定,那么this指向的是new对象,如果是对象的方法显示绑定后,指向的是绑定后的值。说明new的优先级应该是最低的。

function Foo (a) {
  this.a = a
}

var obj = {
  a: 100
}
var obj0 = {
  a: 0
}

Foo.prototype.bool = function () {
  console.log(this.a)
}

Foo.prototype.bindFunc = function () {
  console.log(this.a)
}.bind(obj)

var obj1 = new Foo(1)
obj1.bool() // 1
obj1.bool.call(obj) // 100

obj1.bindFunc() // 100
obj1.bindFunc.call(obj0) // 100

再看一个例子。这个例子中的bind被用于构造函数。此时Foo函数中的this指向的并不是bindObj,如果是的话bindObj会被设置为11。说明new操作符在构造函数中的优先级是高于bind的。

function Foo (a) {
  this.a = a
}
var bindObj = {
  a: 22
}

Foo.prototype.getA = function () {
  return this.a
}.bind(bindObj)

var Bool = Foo.bind(bindObj)

var obj = new Bool(11)
console.log(bindObj.a) // 22
console.log(obj.a) // 11
console.log(obj.getA()) // 22

我们实现的普通的bind和浏览器不同:

function bind(fn, obj) {
	return function() {
		fn.apply( obj, arguments );
	};
}

这种写法和浏览器实现是有差距的,所以才polyfill才会比较复杂

if (!Function.prototype.bind) {
	Function.prototype.bind = function(oThis) {
		if (typeof this !== "function") {
			// closest thing possible to the ECMAScript 5
			// internal IsCallable function
			throw new TypeError( "Function.prototype.bind - what " +
				"is trying to be bound is not callable"
			);
		}

		var aArgs = Array.prototype.slice.call( arguments, 1 ),
			fToBind = this,
			fNOP = function(){},
			fBound = function(){
				return fToBind.apply(
					(
						this instanceof fNOP &&
						oThis ? this : oThis
					),
					aArgs.concat( Array.prototype.slice.call( arguments ) )
				);
			}
		;

		fNOP.prototype = this.prototype;
		fBound.prototype = new fNOP();

		return fBound;
	};
}

call和apply对比

这块不属于this部分了。主要是对比一下差别。从处理流程上来讲,call是比apply快的。可以看下apply定义https://tc39.github.io/ecma262/#sec-function.prototype.apply
apply 要额外进行一次CreateListFromArrayLike(argArray)。就是要将数组或类似数组的对象转换为内部使用的call所要的list,而call方法则直接把所有参数Push进list。性能是有一部分差距,但没必要特意关注,如果已经是数组类型直接用apply即可,如果是个数少用call也可以,手动去转换类型反而会拖慢速度。

posted @ 2017-12-18 13:22  无梦灬  阅读(284)  评论(0)    收藏  举报