有时写代码会考虑到底是用方法还是函数当然从本质上来说在 JavaScript 里都是函数只不过前者在原型链上罢了方法给人一种面向对象编程的感觉是某一个类或者某一个对象特有的东西一般不会去把它 apply 到别的对象上函数则有种面向接口编程的感觉只要传入的对象实现了函数所假象出来的某种接口那么就可以由这个函数来操作

比如说下面这个函数

function propOf(target, name) {
  let result = [];
  for (let item of target) {
    result.push(item[name]);
  }
  return result;
}

它用于遍历某一个迭代器把每一项的 name 属性拿出来

那么只要是迭代器并且产生的每一项都是一个 Object就都可以作为这个函数的参数

propOf([ { name: 'Foo' }, { name: 'Bar' } ], 'name'); // => [ 'Foo', 'Bar' ]

propOf(document.querySelectorAll('[href]'), 'href');
// => [ 'https://s.w.org/', 'https://ljh.io/', ... ]

如果它是某个类的方法那么用起来就需要写一堆奇怪的东西来绑定 this 了就像是有时候我们不得不写成

Array.prototype.map.call(elements, ...);

当然ES6 下可以写得更优雅

[ ...elements ].map(...);

但是本质上是先把它变成了 Array

有些比较通用的函数虽说可以操作实现了某一类接口的任意数据类型但是为了在使用的时候更加面向对象我们往往不会考虑把它抽离出来作为单独的函数依然写在某一个类的原型上如果抽离出来作为 utility就会像上面的 propOf() 一样在调用的时候需要手动把对象作为第一个参数传入这两种风格调用方法和把对象作为第一个参数传入的差异会在阅读上造成一定的困扰

propOf(document.querySelectorAll('[href]'), 'href');

就是这样的一个例子这种情况下往往需要拆分成两行才比较易读

let elements = document.querySelectorAll('[href]');
propOf(elements, 'href');

有没有更优雅的方式呢两年前有个提案 ECMAScript This-Binding Syntax提议增加一个 :: 运算符来绑定 this有了这个运算符上面的代码就可以改写成

function propOf(name) {
  let result = [];
  for (let item of this) {
    result.push(item[name]);
  }
  return result;
}

document.querySelectorAll('[href]')::propOf('href');

还可以像链式调用一样接一路

const { map, filter } = Array.prototype;

let sslUrls = document.querySelectorAll('a')
                     ::map(node => node.href)
                     ::filter(href => href.substring(0, 5) === 'https');

console.log(sslUrls);

如果想要体验这个运算符可以用 Babel 的 Function bind transform