阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 函数调用方式与this指向

函数调用方式与this指向

作者:陈川 阅读数:8203人阅读 分类: JavaScript

函数调用方式与this指向

JavaScript中函数的调用方式直接影响this的绑定规则。不同的调用场景会导致this指向完全不同的对象,理解这些规则对编写可靠代码至关重要。

默认绑定(独立函数调用)

当函数作为独立函数调用时,this在非严格模式下指向全局对象(浏览器中是window),严格模式下则为undefined

function showThis() {
  console.log(this);
}

showThis(); // 浏览器中输出 window 对象

'use strict';
function strictShow() {
  console.log(this);
}
strictShow(); // 输出 undefined

这种绑定常出现在回调函数中:

setTimeout(function() {
  console.log(this); // 浏览器中输出 window
}, 100);

隐式绑定(方法调用)

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

const user = {
  name: 'Alice',
  greet: function() {
    console.log(`Hello, ${this.name}!`);
  }
};

user.greet(); // 输出 "Hello, Alice!"

嵌套对象的情况:

const company = {
  name: 'TechCorp',
  department: {
    name: 'Dev',
    showName: function() {
      console.log(this.name);
    }
  }
};

company.department.showName(); // 输出 "Dev"

隐式丢失问题

当方法被赋值给变量或作为参数传递时,容易发生隐式绑定丢失:

const counter = {
  count: 0,
  increment: function() {
    this.count++;
    console.log(this.count);
  }
};

const incrementFn = counter.increment;
incrementFn(); // 输出 NaN(this指向全局对象)

// 事件处理函数中的典型问题
document.getElementById('btn').addEventListener('click', counter.increment); 
// 点击时this指向DOM元素而非counter对象

显式绑定(call/apply/bind)

通过callapplybind可以强制指定this的指向。

function introduce(lang) {
  console.log(`I'm ${this.name}, I code with ${lang}`);
}

const dev = { name: 'Bob' };

introduce.call(dev, 'JavaScript'); // 输出 "I'm Bob, I code with JavaScript"
introduce.apply(dev, ['Python']);  // 输出 "I'm Bob, I code with Python"

const boundFn = introduce.bind(dev);
boundFn('Java'); // 输出 "I'm Bob, I code with Java"

bind创建的新函数会永久绑定this

const boundIncrement = counter.increment.bind(counter);
document.getElementById('btn').addEventListener('click', boundIncrement);
// 现在能正确增加counter.count

new绑定(构造函数调用)

使用new调用函数时,this指向新创建的对象实例。

function Person(name) {
  this.name = name;
  this.sayHi = function() {
    console.log(`Hi, I'm ${this.name}`);
  };
}

const alice = new Person('Alice');
alice.sayHi(); // 输出 "Hi, I'm Alice"

构造函数内部的this绑定过程:

  1. 创建新对象
  2. 将新对象的原型指向构造函数的prototype
  3. this绑定到新对象
  4. 执行构造函数代码
  5. 如果构造函数没有返回对象,则返回this

箭头函数的this

箭头函数不绑定自己的this,而是继承外层作用域的this值。

const timer = {
  seconds: 0,
  start: function() {
    setInterval(() => {
      this.seconds++;
      console.log(this.seconds);
    }, 1000);
  }
};

timer.start(); // 每秒正确增加seconds

与普通函数的对比:

const obj = {
  value: 42,
  regularFn: function() {
    setTimeout(function() {
      console.log(this.value); // 输出 undefined(this指向全局)
    }, 100);
  },
  arrowFn: function() {
    setTimeout(() => {
      console.log(this.value); // 输出 42
    }, 100);
  }
};

回调函数中的this处理

现代JavaScript提供了多种处理回调函数this指向的方法:

// 方法1:闭包保存this
class Component {
  constructor() {
    this.value = 100;
    const self = this;
    button.onclick = function() {
      console.log(self.value);
    };
  }
}

// 方法2:箭头函数
class Component {
  constructor() {
    this.value = 200;
    button.onclick = () => {
      console.log(this.value);
    };
  }
}

// 方法3:bind
class Component {
  constructor() {
    this.value = 300;
    button.onclick = this.handleClick.bind(this);
  }
  
  handleClick() {
    console.log(this.value);
  }
}

DOM事件处理中的this

在DOM事件处理函数中,this默认指向触发事件的元素:

document.querySelector('button').addEventListener('click', function() {
  console.log(this); // 输出 <button> 元素
});

使用箭头函数时this不会指向元素:

document.querySelector('button').addEventListener('click', () => {
  console.log(this); // 输出外层作用域的this(通常是window)
});

类中的this绑定

类方法需要特别注意this绑定,特别是在作为回调传递时:

class Logger {
  constructor() {
    this.logs = [];
  }
  
  addLog(message) {
    this.logs.push(message);
    console.log(this.logs);
  }
}

const logger = new Logger();
document.getElementById('btn').addEventListener('click', logger.addLog); // 报错
document.getElementById('btn').addEventListener('click', logger.addLog.bind(logger)); // 正确

类字段语法可以自动绑定this

class Logger {
  logs = [];
  
  addLog = (message) => {
    this.logs.push(message);
    console.log(this.logs);
  }
}

this绑定的优先级

当多种规则同时适用时,绑定优先级从高到低为:

  1. new绑定
  2. 显式绑定(call/apply/bind)
  3. 隐式绑定(方法调用)
  4. 默认绑定
function test() {
  console.log(this.name);
}

const obj1 = { name: 'obj1', test };
const obj2 = { name: 'obj2', test };

obj1.test(); // obj1(隐式绑定)
obj1.test.call(obj2); // obj2(显式绑定优先级更高)
new obj1.test(); // undefined(new绑定优先级最高)

特殊场景下的this

某些API允许指定回调函数的this值:

[1, 2, 3].forEach(function(item) {
  console.log(item, this); // this指向传入的第二个参数
}, { customThis: true });

fetch('/api').then(function() {
  console.log(this); // 严格模式下是undefined
});

模块作用域中的this

// 在ES模块中
console.log(this); // 输出 undefined

立即执行函数中的this

IIFE中的this取决于调用方式:

(function() {
  console.log(this); // 非严格模式是window,严格模式是undefined
})();

const obj = {
  method: function() {
    (function() {
      console.log(this); // 非严格模式是window,不是obj
    })();
  }
};

函数作为getter/setter

当函数作为对象的getter或setter调用时,this指向该对象:

const account = {
  balance: 1000,
  get formattedBalance() {
    return `$${this.balance}`;
  },
  set setBalance(value) {
    this.balance = value;
  }
};

console.log(account.formattedBalance); // 输出 "$1000"
account.setBalance = 2000;

原型链中的this

通过原型链调用的方法,this指向调用该方法的实例:

function Animal(name) {
  this.name = name;
}

Animal.prototype.speak = function() {
  console.log(`${this.name} makes a noise`);
};

const dog = new Animal('Dog');
dog.speak(); // 输出 "Dog makes a noise"

异步上下文中的this

异步函数中的this行为与普通函数一致:

const asyncObj = {
  value: 42,
  async getValue() {
    return this.value; // 正确指向asyncObj
  },
  async getValueArrow: async () => {
    return this.value; // 指向外层作用域的this
  }
};

asyncObj.getValue().then(console.log); // 42
asyncObj.getValueArrow().then(console.log); // undefined(假设外层this不是asyncObj)

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌