this和对象原型

关于this

一、this是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件。this的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。
二、当一个函数被调用时,会创建一个活动记录(有时候也称为执行上下文)。这个记录会包含函数在哪里被调用(调用栈)、函数的调用方式、传入的参数等信息。this就是这个记录的一个属性,会在函数执行的过程中用到。

this全面解析

调用位置

一、调用位置就是函数在代码中被调用的位置(而不是声明的位置)。
二、调用位置就在当前正在执行的函数的前一个调用中
三、你可以把调用栈想象成一个函数调用链

绑定规则

在函数的执行过程中调用位置如何决定this的绑定对象?你必须找到调用位置,然后判断需要应用下面四条规则中的哪一条。

默认绑定——独立函数调用

一、考虑以下代码,函数调用时应用了this的默认绑定,因此this指向全局对象。那么我们怎么知道这里应用了默认绑定呢?可以通过分析调用位置来看看foo()是如何调用的。在代码中,foo()是直接使用不带任何修饰的函数引用进行调用的,因此只能使用默认绑定,无法应用其他规则。如果使用严格模式(strict mode),则不能将全局对象用于默认绑定,因此this会绑定到undefined

function foo(){
	console.log(this.a);
}
var a = 2;
foo();// 2

二、虽然this的绑定规则完全取决于调用位置,但是只有foo()运行在非严格模式下时,默认绑定才能绑定到全局对象;在严格模式下调用foo()则不影响默认绑定

function foo(){
	console.log(this.a);
}
var a = 2;
(function() {
	"use strict";
	foo();// 2
})();

也就是说,如果被调用的地方是严格模式,不会影响默认绑定,如果在定义的地方是严格模式就会影响

隐式绑定

一、另一条需要考虑的规则是调用位置是否有上下文对象,或者说是否被某个对象拥有或者包含
二、考虑以下代码:

function foo() {
	console.log(this.a);
}
var obj = {
	a: 2,
	foo: foo
};
obj.foo();// 2

首先需要注意的是foo()的声明方式,及其之后是如何被当作引用属性添加到obj中的。但是无论是直接在obj中定义还是先定义再添加为引用属性,这个函数严格来说都不属于obj对象。然而,调用位置会使用obj上下文来引用函数,因此你可以说函数被调用时obj对象“拥有”或者“包含”它。当foo()被调用时,它的前面确实加上了对obj的引用。当函数引用有上下文对象时,隐式绑定规则会把函数调用中的this绑定到这个上下文对象。因为调用foo()this被绑定到obj,因此this.aobj.a是一样的。

三、对象属性引用链中只有上一层或者说最后一层在调用位置中起作用,即this只想距离foo()最近的对象obj3

function foo() {
	console.log(this.a);
}
var obj2 = {
	a: 42,
	foo: foo
};
var obj1 = { 
	a: 2,
	obj2: obj2
};
obj1.obj2.foo();// 42
隐式丢失

一、一个最常见的this绑定问题就是被隐式绑定的函数会丢失绑定对象,也就是说它会应用默认绑定,从而把this绑定到全局对象或者undefined上,取决于是否是严格模式。

function foo() {
	console.log(this.a);
}
var obj = {
	a: 2,
	foo: foo
};
var bar = obj.foo;// 函数别名
var a = "opps, gobal";// a时全局对象的属性
bar();// "opps, gobal"

说明:

  1. 虽然barobj.foo的一个引用,但是实际上,它引用的是foo函数本身,因此此时的bar()其实是一个不带任何修饰的函数调用,因此应用了默认绑定。
  2. 这个例子也说明了函数不属于某一个对象,只能说函数被调用时,某个对象引用了他。
  3. var bar = obj.foo;传递的是函数本身,而this由调用宿主和环境来决定,函数执行时bar();执行的时this指向window

二、无论是把函数传入语言内置的函数还是传入自己声明的函数,参数传递其实就是一种隐式赋值,因此我们传入函数时也会被隐式赋值。

function foo() {
	console.log(this.a);
}
function doFoo(fn) {
	// fn其实引用的是foo
	fn();// 执行位置
}
var obj = {
	a: 2,
	foo: foo
};
var a = "opps, gobal";// a时全局对象的属性
doFoo(obj.foo);// "opps, gobal"

三、看this只想,不关心this属于谁,从哪里来,只关心他在调用时调用者是谁。
四、调用回调函数的函数可能会修改this。在一些流行的JavaScript库中事件处理器常会把回调函数的this强制绑定到触发事件的DOM元素上。

显示绑定

一、我们必须在一个对象内部包含一个指向函数的属性,并通过这个属性间接引用函数,从而把this间接(隐式)绑定到这个对象上。那么如果我们不想在对象内部包含函数引用,而想在某个对象上强制调用函数,我们可以使用函数的call(..)apply(..)方法
二、这两个方法的第一个参数是一个对象,是给this准备的,接着在调用函数时将其绑定到this。因为你可以直接指定this的绑定对象,因此我们称之为显式绑定。如果你传入了一个原始值(字符串类型、布尔类型或者数字类型)来当作this的绑定对象,这个原始值会被转换成它的对象形式(也就是new String(..)newBoolean(..)或者new Number(..))。这通常被称为“装箱”。

硬绑定

一、显式绑定的一个变种可以解决我们之前提出的丢失绑定问题。

function foo() {
	console.log(this.a);
}
var obj = {
	a: 2
};
var bar = function() {
	foo.call(obj);
};
bar();// 2
setTimeout(bar, 100);// 2
// 硬绑定的bar不可能再修改他的this
bar.call(window);// 2

我们创建了函数bar(),并在它的内部手动调用了foo.call(obj),因此强制把foothis绑定到了obj。无论之后如何调用函数bar,它总会手动在obj上调用foo。这种绑定是一种显式的强制绑定,因此我们称之为硬绑定。
二、硬绑定的典型应用场景就是创建一个包裹函数,负责接收参数并返回值

function foo(something) {
	console.log(this.a,something);
	return this.a + something;
}
var obj = {
	a: 2
};
var bar = function() {
	return foo.apply(obj, arguments);
};
var b = bar(3);// 2,3
console.log(b);// 5

三、另一种使用方法是创建一个可以重复使用的辅助函数:

function foo(something) {
	console.log(this.a,something);
	return this.a + something;
}
function bind(fn, obj){
	return funciton() {
		return fn.apply(obj, arguments);
	}
}
var obj = {
	a: 2
};
var bar = bind(foo, obj);
var b = bar(3);// 2,3
console.log(b);// 5

四、由于硬绑定是一种非常常用的模式,所以ES5提供了内置的方法Function.prototype.bindbind(..)会返回一个硬编码的新函数,它会把你指定的参数设置为this的上下文并调用原始函数。
五、Function.prototype.bind(..)会创建一个新的包装函数,这个函数会忽略它当前的this绑定(无论绑定的对象是什么),并把我们提供的对象绑定到this上。

API调用的“上下文”

JavaScript语言和宿主环境中许多新的内置函数,都提供了一个可选的参数,通常被称为“上下文”(context),其作用和bind(..)一样,确保你的回调函数使用指定的this

new绑定

一、“构造函数”是类中的一些特殊方法,使用new初始化类时会调用类中的构造函数。
二、在JavaScript中,构造函数只是一些使用new操作符时被调用的函数。它们并不会属于某个类,也不会实例化一个类。实际上,它们甚至都不能说是一种特殊的函数类型,它们只是被new操作符调用的普通函数而已。
三、包括内置对象函数在内的所有函数都可以用new来调用,这种函数调用被称为构造函数调用。实际上并不存在所谓的“构造函数”,只有对于函数的“构造调用”。
四、使用new来调用函数,或者说发生构造函数调用时,会自动执行下面的操作:

  1. 创建(或者说构造)一个全新的对象
  2. 这个新对象会被执行[[Prototype]]连接
  3. 这个新对象会绑定到函数调用的this
  4. 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。

优先级

一、默认绑定的优先级是四条规则中最低的
二、测试隐式绑定和显式绑定哪个优先级更高:

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

显式绑定优先级更高
三、测试new绑定和隐式绑定的优先级谁高:

function foo(something) {
	this.a = something;
}
var obj1 = {
	foo: foo
};
var obj2 = {};
obj1.foo(2);
console.log(obj1.a);// 2
obj1.foo.call(obj2, 3);
console.log(obj2.a);// 3
var bar = new obj1.foo(4);// 执行new的时候,this应该指向新创建的实例对象
console.log(obj1.a);// 2
console.log(bar.a);// 4,实例对象身上有a,且值为4,说明this确实指向了新创建的实例对象,new改变了隐式绑定,new绑定比隐式绑定优先级高。

new绑定比隐式绑定优先级高。
四、测试new绑定和显式绑定的优先级谁高

function foo(something){
	this.a = something;
}
var obj1 = {};
var bar = foo.bind(obj1);// obj1.foo
bar(2)
console.log(obj1.a);// 2
var baz = new bar(3)
console.log(obj1.a);// 2
console.log(baz.a);// 3

new绑定比显式绑定优先级高:bar被硬绑定到obj1上,但是new bar(3)并没有像我们预计的那样把obj1.a修改为3。相反,new修改了硬绑定(到obj1的)调用bar(..)中的this。因为使用了new绑定,我们得到了一个名字为baz的新对象,并且baz.a的值是3。
五、之所以要在new中使用硬绑定函数,主要目的是预先设置函数的一些参数,这样在使用new进行初始化时就可以只传入其余的参数。bind(..)的功能之一就是可以把除了第一个参数(第一个参数用于绑定this)之外的其他参数都传给下层的函数(这种技术称为“部分应用”,是“柯里化”的一种)。

function foo() {
	this.val = p1 + p2;
}
var bar = foo.bind(null, "p1");
var baz = new bar("p2");
baz.val;// p1p2

在这里插入图片描述

MDN:bind()的另一个简单用法是使一个函数拥有预设的初始函数。只要将这些参数(如果有的话)作为bind()的参数写在this后面。当绑定函数被调用时,这些参数会被插入到目标函数的参数列表的开始位置,传递给绑定函数的参数会跟在他们后面。

判断this

根据优先级来判断函数在某个调用位置应用的是哪条规则:

  1. 函数是否在new中调用(new绑定)?如果是的话this绑定的是新创建的对象。
var bar = new foo();
  1. 函数是否通过call、apply(显式绑定)或者硬绑定调用?如果是的话,this绑定的是指定的对象。
var bar = foo.call(obj2);
  1. 函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this绑定的是那个上下文对象。
var bar = obj1.foo();
  1. 如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到undefined,否则绑定到全局对象。
var bar = foo();

绑定例外

被忽略的this

一、如果你把null或者undefined作为this的绑定对象传入callapply或者bind,这些值在调用时会被忽略,实际应用的是默认绑定规则
二、那么什么情况下你会传入null呢?

  1. 使用apply(..)来“展开”一个数组,并当作参数传入一个函数。
  2. bind(..)可以对参数进行柯里化(预先设置一些参数)
function foo(a, b) {
	console.log("a:"+a+",b:"+b);
}
// 把数组展开成参数
foo.apply(null,[2,3]);// a:2,b:3
// 使用bind(...)进行柯里化
var bar = foo.bind(null,2);
bar(3);// a:2,b:3

这两种方法都需要传入一个参数当作this的绑定对象。如果函数并不关心this的话,你仍然需要传入一个占位值,这时null可能是一个不错的选择
三、在ES6中,可以用...操作符代替apply(..)来“展开”数组,foo(...[1,2])foo(1,2)是一样的。可惜目前没有柯里化的相关语法,因此还是需要使用bind(..)
四、把null或者undefined作为this的绑定对象传入callapply或者bind可能会导致许多难以分析和追踪的bug,不建议使用。

更安全的this

一、如果我们在忽略this绑定时总是传入一个特殊的对象(其实就是一个空对象),把this绑定到这个对象,那么任何对于this的使用都会被限制在这个空对象中,不会对全局对象产生任何影响。
二、在JavaScript中创建一个空对象最简单的方法是Object.create(null)Object.create(null){}很像,但是并不会创建Object. prototype这个委托,所以它比{}“更空”

function foo(a, b) {
	console.log("a:"+a+",b:"+b);
}
var x = Object.create(null);
// 把数组展开成参数
foo.apply(x,[2,3]);// a:2,b:3
// 使用bind(...)进行柯里化
var bar = foo.bind(x,2);
bar(3);// a:2,b:3

间接引用

一、你有可能(有意或者无意地)创建一个函数的“间接引用”,在这种情况下,调用这个函数会应用默认绑定规则。间接引用最容易在赋值时发生:

function foo() {
	console.log(this.a);
}
var a = 2;
var o = {a: 3, foo: foo};
var p = {a: 4};
o.foo();//3
(p.foo = o.foo)();// 2

说明:p.foo = o.foo传递的是引用地址,(p.foo = o.foo)();等同于foo(),所以此时应用的是默认绑定。
二、是不是默认绑定要看调用的地方,而默认绑定时this时undefined还是window,要看函数定义的地方,如果函数体处于严格模式,this会被绑定到undefined,否则this会被绑定到全局对象。

软绑定

一、使用硬绑定之后就无法使用隐式绑定或者显式绑定来修改this。如果可以给默认绑定指定一个全局对象和undefined以外的值,那就可以实现和硬绑定相同的效果,同时保留隐式绑定或者显式绑定修改this的能力。可以通过一种被称为软绑定的方法来实现我们想要的效果:

if(!Function.prototype.softBind){
	Function.prototype.softBind = function(obj){
		var fn = this;
		//捕获所有curried参数
		var curride = [].slice.call(arguments,1);
		var bound = function(){
			return fn.apply(
				(!this || this === (window || global)) ? obj : this,curried.concat.apply(curried,arguments)
			);
		};
		bound.prototype = Object.create(fn.prototype);
		return bound;
	};
}

除了软绑定之外,softBind(..)的其他原理和ES5内置的bind(..)类似。它会对指定的函数进行封装,首先检查调用时的this,如果this绑定到全局对象或者undefined,那就把指定的默认对象obj绑定到this,否则不会修改this。此外,这段代码还支持可选的柯里化

this词法

一、箭头函数不使用this的四种标准规则,而是根据外层(函数或者全局)作用域来决定this
二、箭头函数虽然没有prototype,但是instanceof Functiontrue
三、箭头函数在执行时进行动态编译,并注入代码捕获当前环境中的this,并实现硬绑定

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值