[js] this (1)
this是在运行时进行绑定的,并不是在编写时绑定的。
this的绑定和函数声明的位置没有关系,只取决于函数的调用方式。
四种标准规则
默认绑定
隐式绑定
显式绑定
new绑定
1
var me = {
name: "husky"
}
var you = {
name: "reader"
}
使用 this
function identify() {
return this.name.toUpperCase();
}
function speak() {
var greeting = "hello, I am " + identify.call(this);
console.log(greeting);
}
identify.call(me); //HUSKY
identify.call(you); //READER
speak.call(me); // hello, I am HUSKY
speak.call(you); // hello, I am READER
不使用this
function identify(cxt) {
return cxt.name.toUpperCase();
}
function speak(cxt) {
var greeting = "hello, I am " + identify(cxt);
console.log(greeting);
}
identify(you); //READER
speak(me); //hello, I am HUSKY
2
this并非指向函数自身
function foo(num){
console.log("foo: " + num);
this.count++; //记录foo被调用的次数
}
foo.count = 0;
for(var i=0; i<10; i++){
if(i > 5){
foo(i)
}
}
// foo: 6
// foo: 7
// foo: 8
// foo: 9
console.log(foo.count); // 0
词法作用域
function foo(num){
console.log("foo: " + num);
data.count++; //记录foo被调用的次数
}
var data ={
count: 0
};
for(var i=0; i<10; i++){
if(i > 5){
foo(i);
}
}
// foo: 6
// foo: 7
// foo: 8
// foo: 9
console.log(data.count);// 4
词法标识符
function foo(num){
console.log("foo: " + num);
foo.count++; // foo指向它自身
}
foo.count = 0;
for(var i=0; i<10; i++){
if(i > 5){
foo(i);
}
}
// foo: 6
// foo: 7
// foo: 8
// foo: 9
console.log(foo.count);// 4
this
function foo(num){
console.log("foo: " + num);
this.count++;
}
foo.count = 0;
for(var i=0; i<10; i++){
if(i > 5){
foo.call(foo, i); //使用call()可以确保this指向函数本身
}
}
// foo: 6
// foo: 7
// foo: 8
// foo: 9
console.log(foo.count);// 4
3
this在任何情况下都不指向函数的作用域 (?)
function foo() {
var a = 2;
this.bar();
}
function bar() {
console.log(this.a);
}
foo(); //ReferenceError: a is not defined
4
调用栈
function baz() {
//当前调用栈是:baz
//因此,当前调用位置是全局作用域
console.log("baz");
bar(); //bar的调用位置
}
function bar() {
//当前调用栈是:baz -> bar
//因此,当前调用位置是在baz中
console.log("bar");
foo(); //foo的调用位置
}
function foo() {
//当前调用栈是:baz -> bar -> foo
//因此,当前调用位置是在baz中
console.log("foo");
}
baz(); // baz的调用位置
5
默认绑定
function foo() {
console.log(this.a);//这里的 this 指向 window
}
var a = 2;
foo(); // 2
6
隐式绑定
function foo() {
console.log(this.a);
}
var obj = {
a: 2,
foo: foo
};
obj.foo(); // 2
隐式绑定,对象属性引用链中只有最后一层在调用位置起作用
function foo(){
console.log(this.a);
}
var obj2 = {
a: 100,
foo: foo
};
var obj1 = {
a: 1,
obj2: obj2
}
obj1.obj2.foo(); // 100
隐式绑定的函数会丢失绑定对象
function foo(){
console.log(this.a);
}
var obj ={
a: 2,
foo: foo
}
var bar = obj.foo; //函数别名
var a = "global";
bar(); // "global"
function foo(){
console.log(this.a);
}
function callBack(fn){
fn();
}
var obj = {
a: 2,
foo: foo
}
var a = "global";
callBack(obj.foo); // "global"
参数传递其实就是一种隐式赋值
var a = 1;
function fn(){
console.log(a); //1
a = 2;
}
fn();
console.log(a); // 2
var a = 1;
function fn(a){
console.log(a); //undifined
a = 2;
}
fn();
console.log(a); // 1
function foo(){
console.log(this.a)
}
var obj = {
a: 2,
foo: foo
}
var a = "global";
setTimeout(obj.foo, 1000); //"global"
JavaSceipt环境中内置的setTimeout()函数实现和下面的伪代码类似
function setTimeout(fn, delay){
//等待delay秒
fn(); //调用位置
}
7
显式绑定
call(绑定this, "参数1","参数2","参数3","参数4");
apply(绑定this, ["参数1","参数2","参数3","参数4"]);
function foo(){
console.log(this.a);
}
var obj = {
a: 2
}
foo.call(obj);//2
显式绑定-硬绑定
function foo(){
console.log(this.a);
}
var obj = {
a: 2
}
var bar = function(){
foo.call(obj);
}
bar();// 2
setTimeout(bar, 100); // 2
bar.call(window); // 2
创建一个包裹函数
function foo(something){
console.log(this.a, something);
return this.a + something;
}
var obj = {
a: 2
}
var bar = function(){
return foo.apply(obj, arguments);//将这里的this指向了 obj
}
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 function(){
return fn.apply(obj, arguments);
}
}
var obj = {
a:2
}
var bar = bind(foo, obj);
var b = bar(3); //2, 3
console.log(b); //5
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); //5
8
new 绑定
function foo(a){
this.a = a
}
var bar = new foo(2);
console.log(bar) // foo {a: 2}
console.log(bar.a); //2
9
箭头函数不使用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
箭头函数可以像bind(..)一样确保函数的this被绑定到指定的对象
function foo(){
setTimeout(()=>{
//这里的this在词法上继承自foo(),也就是说只要foo()绑定到了obj1上,箭头函数的this也就绑定到了obj1上
console.log(this.a)
},100)
}
var obj1 = {
a: 2
}
foo.call(obj1); //2
ES6之前就已经使的用一种几乎和箭头函数完全一样的模式
function foo(){
console.log(this); //Object {a: 2}
var self = this; //词法作用域捕获this
setTimeout(function(){
console.log(this); // Window {external: Object, chrome: Object, document: document, obj1: Object, obj2: Object…}
console.log(self.a);
}, 100);
}
var obj1 = {
a: 2
}
foo.call(obj1); //2