前言
JavaScript
中的this
问题对于初学者来说是一个重要的问题,但很多人仍然对class
中的this
问题感到困惑。希望的文章可以清楚地向大家解释这一点。
此绑定优先级
关于this
有很多说法。有人说this
指的是谁叫谁。有人说this
与作用域无关,只与执行上下文有关。这两种说法似乎意味着同一件事。
1。使用新创建的实例来调用方法,它指向当前实例
class Cat {
jump() {
console.log('jump',this)
}
}
const cat = new Cat()
cat.jump() // jump Cat {}
2。透明装订
使用call、apply、bind
function jump() {
console.log(this.name)
}
const obj = {
name: '豆芽',
jump,
}
jump = jump.bind(obj)
jump() // 豆芽
3。对象中的方法绑定
function jump() {
console.log(this.name)
}
const obj = {
name: '豆芽',
jump,
}
obj.jump() // 豆芽
4。默认绑定
在严格模式下,this
是
class Foo {
static classMethod() {
return 'hello';
}
}
class Bar extends Foo {
static classMethod() {
return super.classMethod() + ', too';
}
}
Bar.classMethod() // "hello, too"
,否则是全局对象。
在类中绑定属性和方法
class Cat {
constructor(name) {
this.name = name
}
jump() {
console.log('jump', this)
}
static go() {
console.log(this)
}
}
Cat.drink = function() {
console.log('drink', this)
}
Cat.prototype.eat = function() {
console.log('eat', this)
}
Cat.prototype.walk = () => {
console.log('walk', this)
}
let cat = new Cat('豆芽')
如上图所示,Cat
创建的实例方法挂载在__proto__
的实例上,即的原型对象。由于cat.proto和Cat.prototype指向同一个对象,因此当原始方法被安装或由创建的覆盖时,所有都会被共享通过方法,所有实例都通过__proto__
属性生成的原型链查找原型对象上的方法。
但是,静态方法不与实例共享,因为它们没有连接到原型对象。
属性是挂载在实例上的,即每个创建的实例都有自己的属性,且属性的值不同。
在课堂上链接
如果我们打印 typeof Cat
,我们会看到 Cat
是一个函数类型,并且类本身指向一个构造函数。 ES6 中的类 class 实际上只是语法糖 ,可以使用 ES5
实现。由构造函数 Cat
创建的实例 cat
是一个对象。当实例 cat
初始化时,this
的属性将与实例对象 constructor
合并。
class Cat {
constructor(name, age) {
this.name = name
}
run() {
console.log('run', this)
}
}
let cat = new Cat('豆芽')
cat.name // '豆芽'
cat.run() // run Cat {name: '豆芽'}
调用 cat.run()
具有当前上下文 cat
,因此其 this
指向实例 cat
。
class Cat {
constructor(name) {
this.name = name
this.jump = this.jump.bind(this)
this.drink = () => {
console.log('drink',this)
}
}
run() {
console.log('run', this)
}
jump() {
console.log('jump',this)
}
static go() {
console.log('go',this)
}
}
Cat.prototype.walk = () => {
console.log('walk',this)
}
let cat = new Cat('豆芽')
let run = cat.run
let jump = cat.jump
let go = Cat.go
let walk = cat.walk
let drink = cat.drink
run() // run undefined (严格模式下,this为undefined,可以举一个严格模式下function的例子)
jump() // jump Cat {name: "豆芽", jump: ƒ}
Cat.go() // go class Cat {}
go() // go undefined
cat.walk() // walk Window
walk() // walk Window
cat.drink() // drink Cat {name: "豆芽", jump: ƒ, drink: ƒ}
drink() // drink Cat {name: "豆芽", jump: ƒ, drink: ƒ}
分析:
运行方法:当将方法分配给实例变量时,它只是设置了对该方法的引用,因此当该变量运行该方法时,实际上更改了该方法的执行上下文。原始执行上下文是实例cat
。后来,赋值和执行后,上下文变成全局的,this
默认绑定。 class
使用严格模式。该模式下,全局默认绑定到undefined
。当不在严格模式下以及在浏览器中启动时,默认情况下会绑定this
。设置window
。
跳转方法:因为调用构造函数时,执行上下文jump
/jump
被显式绑定。所以jump
的执行上下文仍然是cat实例
。
go方法:go
方法是使用静态方法定义的,不能共享cat
的实例。只能在构造函数中直接调用Cat
。
步行和喝水方法: 这两种方法是使用箭头函数定义的。箭头函数的this
是在定义函数时绑定的,而不是在执行时绑定的。简单来说,当定义一个函数时,this
继承了定义该函数的对象。如果定义了 Cat.prototype.walk
,则
JavaScript
也被定义。目前,this
指向window
。后面分配的变量只是函数的引用,所以它的this
仍然是window
。同样,如果定义了drink
,则this
指向构造函数。
注:
(1)严格制度
在类和模块中,严格模式默认为,因此不需要使用来设置运行模式。只要你的代码是在类或模块中编写的,就只能使用严格模式。鉴于所有未来的代码实际上都在模块中运行,ES6 有效地将整个语言升级到严格模式。
(2)this
指向
的方法包含this
,则默认指向该类的实例。不过,如果单独使用这种方法,一定要非常小心,很可能会报错。
class Logger {
printName(name = 'there') {
this.print(`Hello ${name}`);
}
print(text) {
console.log(text);
}
}
const logger = new Logger();
const { printName } = logger;
printName(); // TypeError: Cannot read property 'print' of undefined
在上面的代码中,方法printName
中的this
默认指向类Logger
的实例。但如果将该方法提取出来单独使用,this
指向该方法运行的环境(因为class
内部处于严格模式,所以this
实际上指向undefined
),导致无法使用。找到 Open 方法 print
并报告错误。
一个相对简单的解决方案是在构造函数中绑定this
,这样print
方法就找不到了。
class Logger {
constructor() {
this.printName = this.printName.bind(this);
}
// ...
}
另一种解决方案是使用箭头函数。
class Obj {
constructor() {
this.getThis = () => this;
}
getVal = () => this;
}
const myObj = new Obj();
myObj.getThis() === myObj // true
myObj.getVal() === myObj // true
箭头函数内的this
始终指向定义它的对象。在上面的代码中,箭头函数位于构造函数内部,其定义在构造函数执行时生效。目前,箭头函数所在的运行环境必须是实例对象,所以this
始终指向实例对象。
(3) 静态方法
类相当于实例原型。实例继承类中定义的所有方法。如果在方法前面加上关键字static
,则表示该方法不是由实例继承,而是通过类直接调用,称为“静态方法”。
class Foo {
static classMethod() {
return 'hello';
}
}
Foo.classMethod() // 'hello'
var foo = new Foo();
foo.classMethod()
// TypeError: foo.classMethod is not a function
上述代码中,Foo
类的方法classMethod
前面有关键字static
,表明该方法是静态方法() )而不是在实例上调用Foo
类。当在实例上调用静态方法时,会抛出错误,指示该方法不存在。
请注意,当静态方法包含this
关键字时,它指的是this
类,而不是实例。
在上面的代码中,静态方法bar
调用this.baz
,其中this
指的是
,相当于❓,而不是❓。 。另外,这个例子表明静态方法可以与非静态方法重名
。
超类的静态方法可以被子类继承。
class Foo {
static classMethod() {
return 'hello';
}
}
class Bar extends Foo {
}
Bar.classMethod() // 'hello'
在上面的代码中,超类Foo
有一个静态方法,子类Bar
可以调用该方法。
静态方法也可以从super
对象调用。
class Foo {
static classMethod() {
return 'hello';
}
}
class Bar extends Foo {
static classMethod() {
return super.classMethod() + ', too';
}
}
Bar.classMethod() // "hello, too"
参考文档:es6.ruanyifeng.com/#docs/class
developer.mozilla.org/zh-CN/docs/…
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。