写在前面:面试官为什么总爱问箭头函数
如果你准备过前端面试,大概率遇到过这类问题:“箭头函数和普通函数有什么区别?”“箭头函数里的this指向哪里?”说实话,箭头函数(Arrow Function)在ES6新特性里,属于看起来简单、问起来扎心的一类。语法上三分钟能学会,但真要把它讲清楚,能讲明白的人不多。
我从面试官的角度拆解过这个问题。大部分候选人能说出来“箭头函数没有自己的this”“不能做构造函数”这两条,但再往下问“为什么setTimeout里用箭头函数能拿到外层this”“什么时候不该用箭头函数”,很多人就卡住了。这说明大家只是背了关键词,没有真正理解箭头函数在JavaScript运行机制里的位置。
这篇文章就把箭头函数的来龙去脉一次讲透。从语法写法到this绑定规则,从能用在哪到不该用在哪,从面试题的常见坑到实际项目里的取舍,全部过一遍。不管你是刚学ES6的新手,还是准备跳槽想复习一轮的老手,这篇文章都值得你花十五分钟读完。读完以后,再有面试官拿箭头函数考你,你可以反手给他讲一遍原理。
1. 箭头函数到底解决了什么问题
1.1 从一段“丑陋”的回调代码说起
在ES6出现之前,JavaScript里写回调函数是一件很痛苦的事情。先不说嵌套层级的问题,光是一个this指向就能让人头皮发麻。举一个非常典型的场景:在一个对象方法里使用setTimeout,想延迟执行后访问这个对象的属性。
var person = { name: '小明', delayShow: function () { setTimeout(function () { console.log('你好,我是' + this.name); }, 1000); } }; person.delayShow(); // 输出:你好,我是undefined这段代码的输出是undefined,原因很简单:setTimeout回调里的this不再指向person对象,而是在调用时由运行时环境决定的(浏览器里指向window,严格模式下是undefined)。
当时的主流解决方案是缓存this,也就是传说中的var self = this:
var person = { name: '小明', delayShow: function () { var self = this; setTimeout(function () { console.log('你好,我是' + self.name); }, 1000); } };还有一种方案是用.bind(this)把回调里的this绑回去。这两种方案都能解决问题,但都显得笨重。每写一个异步回调,都要先声明一个变量去“保存”外层this,代码里充满了self、_this、that这种临时变量。
箭头函数从语言层面解决了这个问题:它自身没有this,访问this时直接从定义位置的外层作用域拿。用箭头函数重写上面的代码,整个逻辑就像呼吸一样自然:
var person = { name: '小明', delayShow: function () { setTimeout(() => { console.log('你好,我是' + this.name); }, 1000); } }; person.delayShow(); // 输出:你好,我是小明这个例子基本概括了箭头函数最大的价值:它让你不再为了this的传递问题费尽心思。回调函数里想用外层的this?写箭头函数就完了。
1.2 语法层面的一次净化
除了this问题的改善,箭头函数在语法层面也做了大量简化。很多人第一次看到箭头函数的时候,觉得这个写法很新奇,其实它就是“函数表达式”的一种更简洁的替代。
普通函数定义一个求和函数,最完整的写法是这样的:
function add(a, b) { return a + b; }用函数表达式写是:
var add = function (a, b) { return a + b; };改成箭头函数以后,中间的过程变量都可以省略:
var add = (a, b) => a + b;一行搞定。当函数体只有一个表达式时,箭头函数可以隐式返回这个表达式的值,不需要写return关键词。当参数只有一个时,参数外层的括号也可以省略:
var double = x => x * 2;这种精简体现在代码里,最直观的感受就是函数式编程里的链式调用变得清爽了很多。举个例子,把一组数字里大于10的挑出来,再乘以2,最后求和:
const nums = [5, 12, 18, 3, 9, 22]; const result = nums .filter(n => n > 10) .map(n => n * 2) .reduce((sum, n) => sum + n, 0); console.log(result); // 104如果用普通函数写,每个回调都要写上function关键字和return,代码量翻倍不说,阅读时视线的跳跃感也非常明显。箭头函数让每一个回调都变得足够短,短到你可以一眼看完整条数据流的处理逻辑。
很多面试题会问“箭头函数相比普通函数的优势”。语法简洁只是表象,this机制上的改进才是根本优势。回答时可以按“语法 + this + 适用场景”三层递进,比干巴巴背三条区别要有深度得多。
2. 必须吃透的核心规则:this的绑定逻辑
2.1 this绑定:定义时确定,而非调用时确定
箭头函数与普通函数在this上最本质的差异,可以浓缩成一句话:普通函数的this在调用时确定,箭头函数的this在定义时确定。
这句话怎么理解?普通函数里的this,取决于你怎么调用它。作为对象方法调用,this指向对象;作为普通函数调用,this指向window或undefined;用call/apply/bind调用,this指向你绑定的目标。同一个函数,在不同调用方式下this指向可以完全不同。
箭头函数不一样。它在定义的时候,就“继承”了当前作用域的this,之后无论你怎么调用它、在哪里调用它,this都不会再变。你可以把箭头函数理解为:它的this是创建时从周围环境“捕获”过来的快照。
从JavaScript引擎的角度来说,箭头函数的this其实就是外层词法作用域的this,所以它在规范里被称为“Lexical this”。这也是为什么箭头函数不能用call/apply/bind去强行改变this的原因——这三个方法确实会被调用,但改变不了箭头函数的this指向,参数倒是可以正常传进去。
看一下这个例子,对比会非常直观:
const obj = { name: 'obj', normalFn: function () { console.log(this.name); }, arrowFn: () => { console.log(this.name); } }; obj.normalFn(); // 'obj',因为normalFn作为obj的方法被调用 obj.arrowFn(); // undefined,因为arrowFn定义时,外层作用域是全局,this指向window一个常见的误解是“箭头函数里面this一定指向window”。准确的说法是:箭头函数的this取决于定义它的外层作用域的this。如果外层作用域是一个对象方法(比如上面提到的delayShow),那这个对象方法的this指向person,里面的箭头函数自然也能拿到person。
我把这个规则总结成一句话:箭头函数不决定this,它只是this的“搬运工”。所以面试时问“箭头函数的this指向哪里”,标准答案不是“指向window”,而是“指向定义时外层执行上下文的this”。
2.2 不可变this带来的连锁反应
因为箭头函数没有自己的this,所以它天然不能作为构造函数使用。new的过程会发生什么?JavaScript引擎会创建一个新对象,然后把构造函数里的this绑定到这个新对象上,最后返回这个新对象。但箭头函数没有this可以绑定,所以直接对它new,引擎会直接抛错:
const Foo = () => { this.name = 'x'; }; const f = new Foo(); // TypeError: Foo is not a constructor同理,箭头函数也不能用prototype。普通函数天生带有prototype属性,作为构造函数时,新对象的原型链会指向函数的prototype。箭头函数根本没有prototype属性:
const fn = () => {}; console.log(fn.prototype); // undefined这两个特点在面试中经常被放在一起问:“箭头函数能不能作为构造函数?为什么不能?”答案是“不能,因为箭头函数没有自己的this,无法绑定实例”。
除了不能用new,箭头函数还有一个被很多人忽视的特性:函数内没有arguments对象。普通函数内部有arguments,里面按顺序装着所有调用参数。但箭头函数里访问arguments,拿到的是外层作用域的arguments,不是自己的参数列表。
const arrowFn = () => { console.log(arguments); // 指向外层作用域的arguments }; function normalFn() { const innerArrow = () => { console.log(arguments); // 指向normalFn的arguments }; innerArrow(1, 2, 3); } normalFn('a', 'b'); // 输出 ['a', 'b'],而不是 [1, 2, 3]如果确实需要在箭头函数里拿到参数列表,ES6提供了rest参数(剩余参数)语法:
const arrowFn = (...args) => { console.log(args); }; arrowFn(1, 2, 3); // [1, 2, 3]这块内容也值得在面试时主动补充,因为很多候选人只知道“箭头函数没有arguments”,但说不清楚“访问arguments会发生什么”。说清楚这一层,回答的含金量立刻就上来了。
2.3 三个容易记混的边界条件
有些边界条件如果没搞清楚,面试现场很容易翻车。第一个是关于call/apply/bind能不能改变箭头函数的this。能调用,但改变不了this指向:
const fn = () => { console.log(this); }; const target = { name: 'target' }; fn.call(target); // 输出全局对象,而不是target fn.apply(target); // 同样的规则第二个边界条件是箭头函数定义时的this也有层级关系。如果外层作用域的this本身是改变的,那箭头函数里的this也会跟着变。看这个例子:
var name = 'window'; const obj = { name: 'obj', fn: function () { return () => { console.log(this.name); }; } }; const innerFn = obj.fn(); // 此刻this指向obj,箭头函数捕获了obj innerFn(); // 'obj' const innerFn2 = obj.fn; const fn2 = innerFn2(); // 此刻this指向window,箭头函数捕获了window fn2(); // 'window'(非严格模式下)第三个边界条件是箭头函数的优先级问题。面试里有一种经典陷阱题:给对象某个方法赋值为普通函数,方法内部再返回箭头函数,此时箭头函数的this到底是谁?答案就是上面例子里的逻辑:看返回箭头函数的那一刻,外层函数的this是谁。理解到这一层,你就能应对绝大多数this相关面试题了。
3. 花样繁多的箭头函数写法
这个部分就是热搜词“箭头函数写法”需要重点展开的内容。箭头函数之所以让一些初学者犯迷糊,就是因为它的语法有太多“可变省略”的规则。我把所有写法变体整理成一个体系,你照着这个体系捋一遍就不会乱了。
3.1 从完整写法到最短写法
先看一个箭头函数的完整结构,包含四个部分:(参数列表) => { 函数体 }。里面的每一个部分都有对应的省略规则。
完整写法:
const fn = (a, b) => { const sum = a + b; return sum; };省略规则第一条:函数体只有一个表达式时,可以去掉花括号和return,结果自动作为返回值:
const fn = (a, b) => a + b;省略规则第二条:参数只有一个时,可以去掉参数括号:
const double = x => x * 2;省略规则第三条:零个参数时,参数位不能空着,写一对空括号即可,但也可以用一个下划线占位。语感上因人而异,我个人的习惯是直接用空括号,因为更直观:
const fn1 = () => Math.random(); const fn2 = _ => Math.random(); // 这种写法合法,但可读性一般综合起来,一个函数的写法可以一路从最啰嗦变成最短:
// 原始版 function multiply(a, b) { return a * b; } // 转换版一:完整箭头函数 const multiply = (a, b) => { return a * b; }; // 转换版二:隐式返回 const multiply = (a, b) => a * b;看到这里你应该明白了,箭头函数本质上是对“函数表达式”的一种语法压缩。压缩的目的是让短小的回调函数在阅读时更聚焦,而不是让你把所有函数都写成一行。
3.2 最容易踩坑的返回对象技巧
有一个写法细节,在面试和实际开发中都特别容易出问题:用箭头函数直接返回一个对象字面量。因为对象字面量和函数体的花括号冲突了,直接这么写会报错:
const getObj = () => { name: 'x' }; // 语法错误或得不到预期结果JavaScript引擎看到{会认为这是函数体的开始,然后解析name:,发现这是一条标签语句,后面的'x'是表达式,最终函数没有返回值,得到undefined。
注意:这行代码在部分浏览器里不报错,但返回的是undefined,因为
name: 'x'被解析成了带标签的语句,不是对象字面量。实际开发中这种写法极其容易埋雷。
解决办法是用一对圆括号把对象包起来,告诉引擎这里是一个表达式:
const getObj = () => ({ name: 'x' });加上括号以后,引擎会知道花括号里是对象字面量而不是函数体。这一点必须刻在脑子里,很多人在实际代码里踩过一次,但没有理解背后的原因,下次照样错。
3.3 高阶用法:解构参数与默认参数
箭头函数的参数位同样支持解构和默认值,这些特性组合在一起时,写法会变得非常简洁且强大。最经典的应用是处理数组里的对象数据:
const users = [ { name: '张三', age: 25 }, { name: '李四', age: 30 }, { name: '王五', age: 28 } ]; const names = users.map(({ name }) => name); console.log(names); // ['张三', '李四', '王五']参数位置直接解构对象的name属性,不用在函数体里多写一行const name = item.name。这种感觉就像是专门为处理集合数据而生的。
默认参数同样支持:
const greet = (name = '陌生人') => `你好,${name}`; console.log(greet()); // 你好,陌生人 console.log(greet('小明')); // 你好,小明箭头函数和模板字符串配合起来,代码可以从几行压缩到一行。这种写法在数据处理场景下非常常见,面试时如果被问到“箭头函数在实际项目里怎么用”,举一个map配合解构参数的例子,比背十条理论都有说服力。
4. 哪些场景该用,哪些场景必须避雷
4.1 适合箭头函数的场景
箭头函数最适合的场景,是那些不需要自己的this、只是作为“纯函数”被传递的地方。我把日常开发中最典型的四个场景列出来。
第一个场景是数组遍历和变换。map、filter、reduce、forEach这些方法接受的回调函数,大部分情况下只依赖参数和返回值,不需要this,用箭头函数写最简单:
const prices = [100, 200, 300, 400]; const discounted = prices.map(price => price * 0.8);第二个场景是定时器和异步操作。比如setTimeout、setInterval、Promise链式调用里,需要访问外层的this时,用箭头函数可以省去bind或者self缓存:
class Countdown { constructor(start) { this.count = start; } start() { setInterval(() => { this.count--; console.log(this.count); }, 1000); } }第三个场景是事件监听回调。在类或组件里注册事件处理函数,需要访问实例成员时,箭头函数能保证this指向正确:
class Button { constructor(dom) { this.dom = dom; this.dom.addEventListener('click', () => { this.handleClick(); }); } handleClick() { console.log('clicked'); } }第四个场景是函数式编程风格的工具函数。很多工具库的设计思路就是传递无状态函数,箭头函数天然契合:
const compose = (f, g) => x => f(g(x)); const addOne = x => x + 1; const double = x => x * 2; const addOneAndDouble = compose(double, addOne); console.log(addOneAndDouble(3)); // 8,先+1变成4,再*2变成84.2 不能用箭头函数的场景
箭头函数虽然好用,但绝不意味着所有地方都该用它。有三个场景,箭头函数会带来非常隐蔽的问题,实际开发里我踩过不止一次。
第一个场景是对象方法。用箭头函数给对象定义方法时,this不会指向对象本身。这个问题在面试中经常被拿来当陷阱题:
const counter = { count: 0, increment: () => { this.count++; // this指向全局对象,count undefined } };为什么会有这个问题?因为箭头函数定义时,外层作用域的this是全局对象,所以对象里的箭头函数拿到的也是全局this,根本拿不到counter。想给这个对象定义方法,必须用普通函数。
第二个场景是原型链上的方法。给构造函数或类的原型定义方法,如果用箭头函数,方法内部的this也拿不到实例:
function Person(name) { this.name = name; } Person.prototype.sayName = () => { console.log(this.name); // 错误,this不指向实例 };这个场景在React类组件的旧代码里经常能看到,使用箭头函数定义类方法在实例上是可以的(后面会详细说),但定义在prototype上就会出问题,两者要区分清楚。
第三个场景是需要动态this的地方。比如用arguments、用call/apply动态控制this,或者用户事件回调里需要this指向触发元素,这些场景下箭头函数都会“帮倒忙”:
<button id="btn">点击</button> <script> const btn = document.getElementById('btn'); btn.addEventListener('click', function () { console.log(this); // 指向button元素 }); btn.addEventListener('click', () => { console.log(this); // 指向window,按钮的this丢失了 }); </script>判断一个场景该不该用箭头函数,就一个标准:如果这个函数需要动态this,用普通函数;如果不需要自己的this或需要继承外层this,用箭头函数。这个标准能覆盖九成以上的场景。
4.3 类与React组件里的特殊用法
在ES6的Class里,给类定义方法时默认用的是普通函数,方法里的this在调用时确定。如果方法被当作回调传递出去,this就会丢失:
class Counter { constructor() { this.count = 0; // 这种写法保证了this被绑定 this.increment = () => { this.count++; }; } add() { this.count++; } } const c = new Counter(); const fn = c.increment; fn(); // 没问题,箭头函数定义了就绑定了thisReact早期的类组件写法大量使用这种模式:在构造函数或类属性位置用箭头函数定义方法,保证事件回调里的this指向实例。这个设计思路和ES6 class里方法绑定是同一个问题的不同解法。
这里注意一个容易混淆的点:类里用箭头函数定义方法,本质上是给实例添加了一个属性,这个属性是一个箭头函数,而箭头函数定义时所在的作用域是构造函数内部,所以this会绑定到实例。和直接定义在prototype上的方法角色不同。前者每个实例都会有一份函数副本,后者是所有实例共享一份。
所以用箭头函数给实例加方法,确实可以解决this绑定问题,代价是内存占用略高。如果类实例数量不多,一般无感;如果实例数量巨大,就要权衡一下。
5. 实战拆解:5道高频面试题的正确打开方式
5.1 经典这道题:循环里的事件绑定
面试中有一道经久不衰的题目:给一组按钮绑定点击事件,点击第几个按钮就弹出它的索引。用var写会出问题:
for (var i = 0; i < 5; i++) { document.getElementById('btn' + i).addEventListener('click', function () { console.log(i); // 永远输出5 }); }原因在于var声明的i是函数级作用域,循环结束后i已经变成5,所有回调访问的都是同一个i。用箭头函数和let组合可以优雅解决:
for (let i = 0; i < 5; i++) { document.getElementById('btn' + i).addEventListener('click', () => { console.log(i); // 正确输出0, 1, 2, 3, 4 }); }这里有两个机制在起作用:let让每次循环创建了独立的块级作用域变量,箭头函数隐式捕获了这个变量。有些面试官会追问:“能不能用var加箭头函数解决?”答案是var不产生块级作用域,单靠箭头函数也不行。所以这道题考察的其实是作用域链和闭包,箭头函数只是顺带用到的工具。
5.2 setTimeout里this指向的连环问
这道题的变体特别多,核心就一个:打印出来的this是谁?
var name = 'window'; const obj = { name: 'obj', fn1: function () { setTimeout(function () { console.log(this.name); }, 100); }, fn2: function () { setTimeout(() => { console.log(this.name); }, 100); }, fn3: () => { setTimeout(() => { console.log(this.name); }, 100); } }; obj.fn1(); // 'window' obj.fn2(); // 'obj' obj.fn3(); // 'window'逐个拆解:fn1里的普通函数在setTimeout里被调用,this指向window;fn2里的箭头函数在定义时继承了fn2的this,而fn2是作为obj的方法调用的,this指向obj;fn3本身是箭头函数,定义时外层this是全局对象,所以即使里面再套箭头函数,拿到的仍是window。
答这道题的时候,把每一步的this推导过程说清楚,面试官会认为你是真的懂,而不是背过答案。
5.3 第二个陷阱:对象方法装饰器
有一道题稍微有点难度,涉及对象方法被取出再调用的场景:
var name = 'window'; const obj = { name: 'obj', method: function () { return () => { console.log(this.name); }; } }; const fn = obj.method(); fn(); // 'obj' const fn2 = obj.method; fn2()(); // 'window'第一行输出obj很好理解,obj.method()执行时this指向obj,返回的箭头函数捕获了这个this。第二行把obj.method从对象里取出来再调用,此时方法内部的this不再指向obj,而是指向window,所以箭头函数捕获的也是window。
这个例子说明:箭头函数的this,取决于定义它的那一刻外层函数的this是什么。外层函数的this由调用方式决定,而不是由箭头函数决定。这个观念如果建立起来了,箭头函数的this问题基本就是一通百通。
5.4 面试反问:箭头函数可以被bind吗
这道题考察的是对bind底层逻辑的理解。很多人背了结论“不能改变箭头函数的this”,但说不清为什么。其实更准确的说法是:bind在改变this之前,会先判断函数是不是箭头函数,如果是,则忽略this绑定,只传递参数。
const arrowFn = (a, b) => { console.log(this, a, b); }; const obj = { name: 'obj' }; const boundFn = arrowFn.bind(obj, 1, 2); boundFn(); // 输出全局对象,1,2所以理论上你可以用bind给箭头函数传参数,但this不会改变。追问一句“为什么设计上要这样限制?”答案是箭头函数的卖点就是this词法绑定,如果允许bind改变this,就和设计初衷矛盾了,也会让开发者难以预测代码行为。
5.5 综合题:什么时候不能用箭头函数
最后这道题偏开放,没有标准答案,需要你结合场景谈理解。一个完整的回答应该包含以下几点:
- 对象方法不能用,因为this指向不对。
- 构造函数不能用,因为没有自己的this,不能被new。
- 想要动态this的场景不能用,比如
arguments对象使用时。 - 事件监听器里需要访问当前元素时,用箭头函数会丢掉this指向。
- 需要函数提升的场景不能用,因为箭头函数是表达式,不参与提升。
同时你也应该补一句:箭头函数适合用在不需要this的地方,比如数据处理函数、工具函数、回调函数。这样组织结构,面试官会认为你既了解限制,也清楚边界。
“谈缺点不是坏事,能清楚地指出工具的边界,说明你真的用过它。面试答这类开放题,重点不是展示你记了多少条,而是展示你的判断标准是什么。”
6. 常见误区与避坑经验
6.1 五个常见理解误区自查表
整理了几个最容易出错的点,你可以对照自查一下。
| 误区 | 真实情况 |
|---|---|
| 箭头函数没有this | 准确说是不绑定自己的this,会继承外层this |
| 箭头函数的this指向window | 不绝对,取决于定义时外层作用域的this |
| 箭头函数不能用new | 对,准确原因是它没有内部this可以绑定新实例 |
| 箭头函数不能用call/apply/bind | 能用,传参正常,但this改不了 |
| 箭头函数不能获取参数 | 可以通过rest参数拿到,只是没有自己的arguments |
牢记这张表,笔试和面试里的选择题基本不会翻车。
6.2 我在项目中踩过的真实坑点
第一坑:隐式返回导致对象被误判。有一次项目中要写一个返回配置对象的函数,新同事随手写成了() => { key: 'value' },结果所有地方拿到的都是undefined。排查了半天才发现是花括号被当成函数体的问题。后来团队规定了两条:返回对象必须加圆括号;函数体超过一行就不要用隐式返回。
第二坑:类属性箭头函数导致实例方法不可被bind。有一次在React类组件里用箭头函数定义了一个事件处理方法,结果测试里simulate event的时候发现方法里读不到实例属性。原因是对应的组件实例被框架代理了,方法里的this被React绑定到了组件实例上,看起来正常,但一旦方法被当成普通函数传出,你无法再用bind修正。所以涉及测试代码或外部调用类方法时,要谨慎使用类属性箭头函数。
第三坑:在接口API边界用了箭头函数导致作用域判断复杂。比如Vue项目里,在methods里用箭头函数定义方法,结果方法里访问this.xxx拿到了undefined。Vue初始化时会把methods里的函数直接挂到实例上,调用时this应该指向实例,但箭头函数不会随调用方式改变this,于是所有数据访问全部失效。后来排查发现问题是Vue官方的methods设计逻辑里方法内部确实需要动态this,不能一刀切用箭头函数。正确做法的原则就是:凡是框架层面会帮你把函数绑定到实例上的位置(例如Vue的methods),请老老实实用普通函数。
避免踩坑的核心原则就一句话:框架替你处理this的场景,用普通函数;需要你自己传递回调和函数作为值的场景,优先用箭头函数。搞清楚this是谁在控制,再决定怎么定义函数。
6.3 代码审查时的quick check清单
代码审查时我一般会快速过一遍这些点:
- 对象字面量里的方法:是否误用了箭头函数?
- 原型方法:是否用箭头函数定义导致this丢失?
- 隐式返回:返回的是不是对象?有没有加圆括号?
- 回调函数里使用
this:是不是真的需要外层this? - 新写的箭头函数:组件的生命周期里是否绑定正确?
这五个点扫一遍,基本上能过滤掉大部分箭头函数引起的问题。
写在最后
箭头函数在JavaScript里不是多么高深的概念,但它牵扯到this机制、作用域链、函数式编程习惯,属于前端基础知识里“牵一发动全身”的节点。面试考它,不只是考你的记忆,更是考你对JavaScript运行模型的理解深度。
给准备面试的朋友一个建议:不要只背“箭头函数和普通函数的区别”这种列表型答案,而是去理解设计者的出发点——怎么让函数的书写和使用更符合人的直觉,怎么减少bug产生的可能性。当你能从设计意图的角度回答问题时,很多原本需要死记的答案都会变得非常自然。
我个人在实际带团队的过程中还有一个习惯:代码审查时看到箭头函数,都会多问一句“这个位置用箭头函数,是想要什么效果?”大部分情况下,答案要么是“继承外层this”,要么是“简洁”。只有当你清楚自己为什么选它,箭头函数才会真正成为你手里的利器,而不是面试题里的一个得分点。