海尔建设此网站的目的是什么意思,做摄影网站的公司,企业seo优化,网站建设 dw 时间轴1、浅谈this
1.1、调用位置
在学习this的绑定过程之前#xff0c;首先要理解调用位置#xff0c;即函数在代码中被调用的位置#xff0c;因此我们需要分析调用栈#xff0c;看以下代码
function baz(){// 当前调用栈是baz// 因此调用位置就是全局作用域console。log(首先要理解调用位置即函数在代码中被调用的位置因此我们需要分析调用栈看以下代码
function baz(){// 当前调用栈是baz// 因此调用位置就是全局作用域console。log(baz);bar(); // -- bar的调用位置
}
function bar(){// 当前调用栈是 baz -- bar// 因此当前调用位置在baz中console.log(bar)foo(); // -- foo的调用位置
}
function foo(){// 当前调用栈是 baz - bar - foo// 因此当前的调用位置在bar中console.log(foo)
}
baz() // -- baz的调用位置2、绑定规则
在函数执行过程中调用位置决定this绑定对象有四条规则
2.1、默认绑定
最常见的函数调用类型是独立函数调用可以把这条规则看作是无法应用其他规则时的默认规则
function foo(){console.log(this.a);
}var a 2
foo() // 2当调用foo时this.a解析成了全局变量a这是因为函数调用时应用了this的默认绑定因此this指向全局对象
2.2、隐式绑定
我们必须在一个对象内部包含一个指向函数的属性并通过这个属性间接引用引用函数从而把this间接隐式绑定到这个对象上
该绑定方式需要考虑调用位置是否有上下文对象或者说是否被某个对象拥有或者包含不过这种说法可能会造成一些误导看以下代码
function foo() {console.log(this.a);
}
var obj {a: 2,foo: foo
}
obj.foo() // 2无论你如何称呼这个模式当 foo()被调用时它的前面确实加上了对obj的引用。当函数引用有上下文对象时隐式绑定规则会把函数调用中的 this 定到这个上下文对象。因为调用 foo()时 this 被绑定到 obj因此 this.a和obj.a是一样的。
对象属性引用链中只有上一层或者说最后一层在调用位置中起作用举例来说
function foo() {console.log(this.a);
}
var obj2 {a: 42,foo: foo
}
var obj1 {a: 2,obj2: obj2
}
obj1.obj2.foo() // 42但是隐式绑定会出现绑定丢失的问题
2.3、显式绑定
隐式绑定需要我们在一个对象内部包含一个指向函数的属性并通过这个属性间接引用引用函数从而把this间接隐式绑定到这个对象上如果我们不想这样做而想在某个对象上强制调用函数应该怎么做我们可以使用call…和apply…方法这两个方法的第一个参数都是一个对象是给this准备的接着在调用函数时将其绑定到this因为可以直接指定this的绑定对象因此我们称之为显式绑定
简单的例子
function foo(){console.log(this.a);
}
var obj {a:2
}
foo.call(obj) // 2通过foo.call…我们可以在调用foo时强制把他的this绑定到obj上
如果传入了一个原始值字符串、数组、数字来当做this的绑定对象这个原始值会被转换成它的对象形式也就是new String…、new Boolean…、new Number…这通常被称为“装箱”
如果想要解决绑定丢失问题我们可以使用显式绑定的一个变种 — 硬绑定
function foo() {console.log(this.a);}var obj {a: 2}var bar function () {foo.call(obj)}bar() // 2setTimeout(bar, 100) // 2// 硬绑定的bar不可能在修改它的thisbar.call(window) // 2我们创建了函数 bar()并在它的内部手动调用了foo.call(obj)因此强制把 foo的 this 绑定到了obj。无论之后如何调用函数bar它总会手动在 obj上调用 foo。这种绑定是一种显式的强制绑定因此我们称之为硬绑定
由于硬绑定是一种非常常用的模式所以ES5提供了内置的方法Function.prototype.bind
function foo(something){console.log(this.a,something);return this.a something
}
var obj {a:2
}
var bar foo.bind(obj)
var b bar(3) // 2 3
console.log(b); // 5bind…会返回一个硬编码的新函数会把所指定的参数设置为this的上下文并调用原始函数
2.4、new绑定
在传统的面向类的语言中“构造函数”是类中的一些特殊方法使用new初始化类时会调用类中的构造函数。在JS中也有一个new操作符但JS中的new的机制实际上和面向类的语言完全不同
在JS中构造函数之所以写使用new操作符时被调用的函数。它们并不属于某个类也不会实例化一个类实际上他们只是被new操作符调用的普通函数而已
使用new来调用函数或者说发生构造函数调用时会执行下边的操作也是new操作符的实现原理
创建一个全新的对象这个新对象会执行prototype连接这个新对象会绑定到函数调用的this如果函数没有返回其他对象那么new表达式中的函数调用会自动返回这个新对象
function foo(a){this.a a
}
var bar new foo(2)
console.log(bar.a) // 2使用new调用foo…时会构建一个新对象并把它绑定到foo…调用中的this上。new是最后一种可以影响调用时this绑定行为的方法称之为new绑定
3、绑定优先级
总得来说优先级如下new 显式绑定 隐式绑定 默认绑定 函数是否在 new 中调用 (new 绑定)? 如果是的话 this 定的是新创建的对象 var bar new foo()函数是否通过 callapply (显式绑定)或者硬绑定调用?如果是的话this 绑定的是指定的对象。 var bar foo.call(obj2)函数是否在某个上下文对象中调用 (隐式绑定)?如果是的话this 定的是那个上下文对象。 var bar obj1.foo()如果都不是的话使用默认绑定。如果在严格模式下就绑定到undefined否则绑定到全局对象。 var bar foo()大部分就是上述四种情况所述但是凡事都有例外
4、绑定例外
在某些情况下this的绑定会出乎意料当我们认为应当使用前三种规则时但实际上使用的可能是默认绑定规则
4.1、被忽略的this
如果我们把null或者underfined作为this的绑定对象传入call、apply或bind这些值在调用时会被忽略实际上应用的是默认绑定规则
function foo() {console.log(this.a);
}
var a 2
foo.call(null)//2那么在什么情况下我们会传入null呢
一种常见的做法是使用apply(…)来“展开”一个数组并当作参数传人一个函数。类似地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 可能是一个不错的选择就像代码所示的那样
然而总是使用null来忽略 this 绑定可能产生一些副作用。如果某个函数确实使用了this (比如第三方库中的一个函数)那默认绑定规则会把 this 定到全局对象 (在浏览器中这个对象是 window)这将导致不可预计的后果 (比如修改全局对象)。
一种“更安全”的做法是传入一个特殊的对象把 this 绑定到这个对象不会对你的程序产生任何副作用。就像网络(以及军队)一样我们可以创建一个“DMZ”(demilitarizedzone非军事区)对象它就是一个空的非委托的对象 如果我们在忽略 this 绑定时总是传入一个 DMZ 对象那就什么都不用担心了因为任何对于 this 的使用都会被限制在这个空对象中不会对全局对象产生任何影响。 在 JavaScript 中创建一个空对象最简单的方法都是 object.create(null)。object.create(null)和{}很像但是并不会创建 object.prototype 这个委托所以它比{}“更空”:
function foo(a, b) {console.log(a: a , b: b);
}
// 我们的 DMZ 空对象
var ø Object.create(null);
// 把数组展开成参数
foo.apply(ø, [2, 3]); // a:2, b:3
// 使用 bind(..) 进行柯里化
var bar foo.bind(ø, 2);
bar(3); // a:2, b:3使用变量名 ø 不仅让函数变得更加“安全”而且可以提高代码的可读性因为 ø 表示“我希望 this 是空”这比 null 的含义更清楚。
4.2、间接引用
另一个需要注意的是你有可能有意或者无意地创建一个函数的“间接引用”在这种情况下调用这个函数会应用默认绑定规则。
间接引用最容易在赋值时发生
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 的返回值是目标函数的引用因此调用位置是 foo() 而不是p.foo() 或者 o.foo()。根据我们之前说过的这里会应用默认绑定。 注意对于默认绑定来说决定 this 绑定对象的并不是调用位置是否处于严格模式而是函数体是否处于严格模式。如果函数体处于严格模式this 会被绑定到 undefined否则this 会被绑定到全局对象。
4.3、软绑定
之前我们已经看到过硬绑定这种方式可以把 this 强制绑定到指定的对象除了使用 new时防止函数调用应用默认绑定规则。问题在于硬绑定会大大降低函数的灵活性使用硬绑定之后就无法使用隐式绑定或者显式绑定来修改 this。 如果可以给默认绑定指定一个全局对象和 undefined 以外的值那就可以实现和硬绑定相同的效果同时保留隐式绑定或者显式绑定修改 this 的能力。可以通过一种被称为软绑定的方法来实现我们想要的效果
if (!Function.prototype.softBind) {Function.prototype.softBind function (obj) {var fn this;// 捕获所有 curried 参数var curried [].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。此外这段代码还支持可选的柯里化
4.4、箭头函数中的this
箭头函数不使用 this 的四种标准规则而是根据外层函数或者全局作用域来决定 this。
function foo() {// 返回一个箭头函数return (a) {//this 继承自 foo()console.log(this.a);};
}
var obj1 {a: 2
};
var obj2 {a: 3
};
var bar foo.call(obj1);
bar.call(obj2); // 2, 不是 3 foo() 内部创建的箭头函数会捕获调用时 foo() 的 this。由于 foo() 的 this 绑定到 obj1bar引用箭头函数的 this 也会绑定到 obj1箭头函数的绑定无法被修改。new 也不行
箭头函数可以像 bind(…) 一样确保函数的 this 被绑定到指定对象此外其重要性还体现在它用更常见的词法作用域取代了传统的 this 机制。实际上在 ES6 之前我们就已经在使用一种几乎和箭头函数完全一样的模式。
function foo() {var self this; // lexical capture of thissetTimeout(function () {console.log(self.a);}, 100);
}
var obj {a: 2
};
foo.call(obj); // 2虽然 self this 和箭头函数看起来都可以取代 bind(…)但是从本质上来说它们想替代的是 this 机制。
5、总结
如果要判断一个运行中函数的 this 绑定就需要找到这个函数的直接调用位置。找到之后就可以顺序应用下面这四条规则来判断 this 的绑定对象。
由 new 调用绑定到新创建的对象。由 call 或者 apply或者 bind调用绑定到指定的对象。由上下文对象调用绑定到那个上下文对象。默认在严格模式下绑定到 undefined否则绑定到全局对象。
一定要注意有些调用可能在无意中使用默认绑定规则。如果想“更安全”地忽略 this 绑定你可以使用一个 DMZ 对象比如 ø Object.create(null)以保护全局对象。ES6 中的箭头函数并不会使用四条标准的绑定规则而是根据当前的词法作用域来决定this具体来说箭头函数会继承外层函数调用的 this 绑定无论 this 绑定到什么。这其实和 ES6 之前代码中的 self this 机制一样。