this 与面向对象三问3 questions on this and object-oriented programming
OOP、this 指向的四条规则、call/apply/bind。OOP, the four rules for what this points to, call/apply/bind.
这一页有什么On this page4
- 按优先级说出 this 指向的四条判定规则State the four rules that decide what this points to, in priority order
- 分清 call、apply、bind 三者的差别并手写一个 bindTell call, apply and bind apart, and write your own bind
- 说明 JS 的原型继承和 class 的关系Explain how prototype inheritance in JavaScript relates to class
this 是「给你一段代码问输出什么」的常客,而且答错就说明基本功不牢。手写 bind、手写 new、手写继承是现场编码题的高频三件套。这一组也是理解 React 类组件为什么要 bind 的前提。this shows up whenever you are handed code and asked what it prints, and a wrong answer says the basics are not solid. Writing bind, writing new, and writing inheritance by hand are three frequent live coding tasks. This group is also what you need before you can explain why a React class component calls bind.
什么是面向对象编程What is object-oriented programming?
#302 What is Object-Oriented Programming (OOP)
一句话:把数据和操作数据的方法打包在一起,用对象来组织程序。
四个特征(背下来):
- 封装—— 内部细节藏起来, 只暴露必要的接口。JS 里用闭包或
#private字段实现。 - 继承—— 子类复用父类的能力。
- 多态—— 同一个方法名, 不同对象有不同行为。
- 抽象—— 只关心「能做什么」, 不关心「怎么做的」。
JS 的特别之处(这才是考点):它是基于原型(prototype)的, 不是基于类的。class 是 ES6 加的语法糖, 底下还是原型链 ——class A extends B 编译后就是设置A.prototype.__proto__ = B.prototype。
原型链一句话:访问一个属性时,对象自己没有就去__proto__ 上找, 一层层往上直到 null。和作用域链是一个套路, 只是一个查变量、一个查属性。
会追问:「React 为什么从 class 转向函数组件?」—— 因为 UI 更适合用「输入 → 输出」来描述, 而不是「一个有生命周期的对象」; 而且 class 里 this的绑定问题、逻辑按生命周期而不是按关注点拆分, 都是实际痛点(见 #322)。
In one line: bundle data together with the methods that act on it, and organise the program around objects.
Four pillars — memorise these:
- Encapsulation — hide the internals, expose only the interface callers need. In JS you get it from closures or
#privatefields. - Inheritance — a subclass reuses what the parent can already do.
- Polymorphism — same method name, different behaviour per object.
- Abstraction — you care what it can do, not how it does it.
What makes JS different, and this is the real question: it is prototype-based, not class-based. class is syntax sugar added in ES6; the prototype chain is still underneath — class A extends B compiles down to setting A.prototype.__proto__ = B.prototype.
The prototype chain in one line: read a property, and if the object does not have it the lookup walks up __proto__ one level at a time until null. Same idea as the scope chain — one looks up variables, the other looks up properties.
Follow-up: “Why did React move from classes to function components?” — because UI is easier to describe as “input → output” than as an object with a lifecycle. On top of that, this binding and code split by lifecycle instead of by concern were real, daily pain (see #322).
this 指向什么What does this refer to?
#303 What does 'this' refer to
一句话:this 是调用时决定的, 不是定义时。看「谁调用的」。
四条规则,按优先级从高到低—— 这个顺序是标准答案:
new绑定——new Foo(),this是新创建的对象。- 显式绑定——
call/apply/bind指定的那个。 - 隐式绑定——
obj.fn(),this是obj(看点号左边)。 - 默认绑定—— 都不满足时, 严格模式下是
undefined, 否则是window/global。
箭头函数是例外,它不参与这四条—— 它没有自己的 this, 用的是定义时外层作用域的 this, 而且 call / bind改不了它。
最经典的坑:隐式丢失。const fn = obj.method 之后单独调fn(),点号没了,this 就丢了。 把方法当回调传出去(setTimeout(obj.method)、onClick={this.handle}) 都是这个问题 ——这就是 React 类组件必须在构造器里bind 的原因。
会追问:「DOM 事件回调里 this 是什么?」—— 普通函数是绑定事件的那个元素(等于 e.currentTarget), 箭头函数则是外层的 this。
In one line: this is decided when the function is called, not where it was written. Look at who called it.
Four rules, highest priority first — this order is the model answer:
newbinding — withnew Foo(),thisis the object that was just created.- Explicit binding — whatever you handed to
call/apply/bind. - Implicit binding —
obj.fn()makesthistheobj(look left of the dot). - Default binding — when none of the above applies:
undefinedin strict mode, otherwisewindow/global.
Arrow functions are the exception; they do not play by those four rules — an arrow has no this of its own, it uses the this of the scope it was defined in, and call / bind cannot change that.
The classic trap: the implicit binding gets lost. Do const fn = obj.method and then call fn() on its own — the dot is gone, so this is gone. Handing a method off as a callback (setTimeout(obj.method), onClick={this.handle}) is the same bug — and it is exactly why React class components had to bind in the constructor.
Follow-up: “What is this inside a DOM event handler?” — in a normal function it is the element the listener is attached to (the same as e.currentTarget); in an arrow function it is the outer this.
call、apply、bind 的区别What is the difference between call, apply and bind?
#304 What are the differences between call, apply & bind
一句话:三个都是改this。call 和 apply立即执行,bind 返回一个新函数;call 参数一个个传,apply 传数组。
| 是否立即执行 | 参数形式 | |
|---|---|---|
fn.call(ctx, a, b) | 立即 | 逐个(Comma) |
fn.apply(ctx, [a, b]) | 立即 | 数组(Array) |
fn.bind(ctx, a) | 不执行,返回新函数 | 逐个,且可以只绑一部分 |
记法:Apply 收 Array,Call 用 Comma。
bind 的两个额外性质(追问点):
- 能预置参数—— 所以它就是偏函数 (见 #299)。
- 绑过一次就锁死了—— 再
bind或call都改不回来。但new能突破它, 因为new优先级最高。
还会追问:「apply 现在还有用吗?」—— 展开语法出来后大部分被fn(...args) 取代了。但转发不定参数时还常用:fn.apply(this, args)(防抖里就是这么写的,因为要同时转发this 和 args)。
In one line: all three change this. call and apply run the function right away, bind hands you back a new one; call takes its arguments one by one, apply takes an array.
| Runs immediately? | Argument form | |
|---|---|---|
fn.call(ctx, a, b) | Yes | One by one (Comma) |
fn.apply(ctx, [a, b]) | Yes | An array (Array) |
fn.bind(ctx, a) | No — you get a new function | One by one, and you may bind only some of them |
How to remember it: Apply takes an Array, Call takes Commas.
Two extra properties of bind they will probe:
- It can preset arguments — which makes it partial application (see #299).
- Bind once and it is locked — another
bindor acallcannot change it back. Butnewbreaks through, becausenewhas the highest priority.
Another follow-up: “Is apply still useful?” — spread syntax replaced most of it with fn(...args). It is still the normal way to forward an unknown argument list: fn.apply(this, args) — that is how debounce is written, because you have to forward this and args at the same time.
换一道题也能用Works on other problems too
考试不会原题重考。真正能带走的是「看到这种信号 → 伸手去拿这个解法」。The exam will not reuse the same question. What you take away is the reflex: see this signal, reach for that solution.
- OOP 四特征:封装、继承、多态、抽象;JS 是原型继承,class 只是语法糖。OOP has four traits: encapsulation, inheritance, polymorphism and abstraction; JavaScript inherits through prototypes, and class is only syntax sugar.
- this 四条规则按优先级:new > call/apply/bind > obj.fn() > 默认;箭头函数不参与。The four rules for this, in priority order: new > call/apply/bind > obj.fn() > default; an arrow function follows none of them.
- 隐式丢失是最常见的坑,也是 React 类组件要 bind 的原因。Losing the implicit binding is the most common mistake, and it is why a React class component has to call bind.
- Apply 收 Array、Call 用 Comma;bind 返回新函数、能预置参数、绑一次锁死但 new 能突破。Apply takes an Array, Call takes Commas; bind returns a new function, can preset arguments, and binds once for good — only new can override it.