整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

JavaScript 工具函数大全「新」



一线大厂笔试题灵感来源

目录:

  • 第一部分:数组
  • 第二部分:函数
  • 第三部分:字符串
  • 第四部分:对象
  • 第五部分:数字
  • 第六部分:浏览器操作及其它

筛选自以下两篇文章:

《127 Helpful JavaScript Snippets You Can Learn in 30 Seconds or Less》

《30 seconds of code》

https://github.com/30-seconds/30-seconds-of-code

原本只想筛选下上面的那篇文章,在精简掉了部分多余且无用的工具函数后,感觉不够。于是顺藤摸瓜,找到了原地址: 30 seconds of code

然后将所有代码段都看了遍,筛选了以下一百多段代码片段,并加入了部分自己的理解。

另外,本文工具函数的命名非常值得借鉴。


1. 第一部分:数组

1. all:布尔全等判断

const all = (arr, fn = Boolean) => arr.every(fn);

all([4, 2, 3], x => x > 1); // true
all([1, 2, 3]); // true
复制代码

2. allEqual:检查数组各项相等

const allEqual = arr => arr.every(val => val === arr[0]);

allEqual([1, 2, 3, 4, 5, 6]); // false
allEqual([1, 1, 1, 1]); // true
复制代码

3. approximatelyEqual:约等于

const approximatelyEqual = (v1, v2, epsilon = 0.001) => Math.abs(v1 - v2) < epsilon;

approximatelyEqual(Math.PI / 2.0, 1.5708); // true
复制代码

4. arrayToCSV:数组转CSV格式(带空格的字符串)


const arrayToCSV = (arr, delimiter = ',') =>
  arr.map(v => v.map(x => `"${x}"`).join(delimiter)).join('\n');
  
arrayToCSV([['a', 'b'], ['c', 'd']]); // '"a","b"\n"c","d"'
arrayToCSV([['a', 'b'], ['c', 'd']], ';'); // '"a";"b"\n"c";"d"'
复制代码

5. arrayToHtmlList:数组转li列表

此代码段将数组的元素转换为<li>标签,并将其附加到给定ID的列表中。

const arrayToHtmlList = (arr, listID) =>
  (el => (
    (el = document.querySelector('#' + listID)),
    (el.innerHTML += arr.map(item => `<li>${item}</li>`).join(''))
  ))();
  
arrayToHtmlList(['item 1', 'item 2'], 'myListID');
复制代码

6. average:平均数

const average = (...nums) => nums.reduce((acc, val) => acc + val, 0) / nums.length;
average(...[1, 2, 3]); // 2
average(1, 2, 3); // 2
复制代码

7. averageBy:数组对象属性平均数

此代码段将获取数组对象属性的平均值

const averageBy = (arr, fn) =>
  arr.map(typeof fn === 'function' ? fn : val => val[fn]).reduce((acc, val) => acc + val, 0) /
  arr.length;
  
averageBy([{ n: 4 }, { n: 2 }, { n: 8 }, { n: 6 }], o => o.n); // 5
averageBy([{ n: 4 }, { n: 2 }, { n: 8 }, { n: 6 }], 'n'); // 5
复制代码

8. bifurcate:拆分断言后的数组

可以根据每个元素返回的值,使用reduce()和push() 将元素添加到第二次参数fn中 。

const bifurcate = (arr, filter) =>
  arr.reduce((acc, val, i) => (acc[filter[i] ? 0 : 1].push(val), acc), [[], []]);
bifurcate(['beep', 'boop', 'foo', 'bar'], [true, true, false, true]); 
// [ ['beep', 'boop', 'bar'], ['foo'] ]
复制代码

9. castArray:其它类型转数组

const castArray = val => (Array.isArray(val) ? val : [val]);

castArray('foo'); // ['foo']
castArray([1]); // [1]
castArray(1); // [1]
复制代码

10. compact:去除数组中的无效/无用值

const compact = arr => arr.filter(Boolean);

compact([0, 1, false, 2, '', 3, 'a', 'e' * 23, NaN, 's', 34]); 
// [ 1, 2, 3, 'a', 's', 34 ]
复制代码

11. countOccurrences:检测数值出现次数

const countOccurrences = (arr, val) => arr.reduce((a, v) => (v === val ? a + 1 : a), 0);
countOccurrences([1, 1, 2, 1, 2, 3], 1); // 3
复制代码

12. deepFlatten:递归扁平化数组

const deepFlatten = arr => [].concat(...arr.map(v => (Array.isArray(v) ? deepFlatten(v) : v)));

deepFlatten([1, [2], [[3], 4], 5]); // [1,2,3,4,5]
复制代码

13. difference:寻找差异(并返回第一个数组独有的)

此代码段查找两个数组之间的差异,并返回第一个数组独有的。


const difference = (a, b) => {
  const s = new Set(b);
  return a.filter(x => !s.has(x));
};

difference([1, 2, 3], [1, 2, 4]); // [3]
复制代码

14. differenceBy:先执行再寻找差异

在将给定函数应用于两个列表的每个元素之后,此方法返回两个数组之间的差异。

const differenceBy = (a, b, fn) => {
  const s = new Set(b.map(fn));
  return a.filter(x => !s.has(fn(x)));
};

differenceBy([2.1, 1.2], [2.3, 3.4], Math.floor); // [1.2]
differenceBy([{ x: 2 }, { x: 1 }], [{ x: 1 }], v => v.x); // [ { x: 2 } ]
复制代码

15. dropWhile:删除不符合条件的值

此代码段从数组顶部开始删除元素,直到传递的函数返回为true。

const dropWhile = (arr, func) => {
  while (arr.length > 0 && !func(arr[0])) arr = arr.slice(1);
  return arr;
};

dropWhile([1, 2, 3, 4], n => n >= 3); // [3,4]
复制代码

16. flatten:指定深度扁平化数组

此代码段第二参数可指定深度。

const flatten = (arr, depth = 1) =>
  arr.reduce((a, v) => a.concat(depth > 1 && Array.isArray(v) ? flatten(v, depth - 1) : v), []);

flatten([1, [2], 3, 4]); // [1, 2, 3, 4]
flatten([1, [2, [3, [4, 5], 6], 7], 8], 2); // [1, 2, 3, [4, 5], 6, 7, 8]
复制代码

17. indexOfAll:返回数组中某值的所有索引

此代码段可用于获取数组中某个值的所有索引,如果此值中未包含该值,则返回一个空数组。

const indexOfAll = (arr, val) => arr.reduce((acc, el, i) => (el === val ? [...acc, i] : acc), []);

indexOfAll([1, 2, 3, 1, 2, 3], 1); // [0,3]
indexOfAll([1, 2, 3], 4); // []
复制代码

18. intersection:两数组的交集


const intersection = (a, b) => {
  const s = new Set(b);
  return a.filter(x => s.has(x));
};

intersection([1, 2, 3], [4, 3, 2]); // [2, 3]
复制代码

19. intersectionWith:两数组都符合条件的交集

此片段可用于在对两个数组的每个元素执行了函数之后,返回两个数组中存在的元素列表。


const intersectionBy = (a, b, fn) => {
  const s = new Set(b.map(fn));
  return a.filter(x => s.has(fn(x)));
};

intersectionBy([2.1, 1.2], [2.3, 3.4], Math.floor); // [2.1]
复制代码

20. intersectionWith:先比较后返回交集

const intersectionWith = (a, b, comp) => a.filter(x => b.findIndex(y => comp(x, y)) !== -1);

intersectionWith([1, 1.2, 1.5, 3, 0], [1.9, 3, 0, 3.9], (a, b) => Math.round(a) === Math.round(b)); // [1.5, 3, 0]
复制代码

21. minN:返回指定长度的升序数组

const minN = (arr, n = 1) => [...arr].sort((a, b) => a - b).slice(0, n);

minN([1, 2, 3]); // [1]
minN([1, 2, 3], 2); // [1,2]
复制代码

22. negate:根据条件反向筛选


const negate = func => (...args) => !func(...args);

[1, 2, 3, 4, 5, 6].filter(negate(n => n % 2 === 0)); // [ 1, 3, 5 ]
复制代码

23. randomIntArrayInRange:生成两数之间指定长度的随机数组

const randomIntArrayInRange = (min, max, n = 1) =>
  Array.from({ length: n }, () => Math.floor(Math.random() * (max - min + 1)) + min);
  
randomIntArrayInRange(12, 35, 10); // [ 34, 14, 27, 17, 30, 27, 20, 26, 21, 14 ]
复制代码

24. sample:在指定数组中获取随机数

const sample = arr => arr[Math.floor(Math.random() * arr.length)];

sample([3, 7, 9, 11]); // 9
复制代码

25. sampleSize:在指定数组中获取指定长度的随机数

此代码段可用于从数组中获取指定长度的随机数,直至穷尽数组。 使用Fisher-Yates算法对数组中的元素进行随机选择。

const sampleSize = ([...arr], n = 1) => {
  let m = arr.length;
  while (m) {
    const i = Math.floor(Math.random() * m--);
    [arr[m], arr[i]] = [arr[i], arr[m]];
  }
  return arr.slice(0, n);
};

sampleSize([1, 2, 3], 2); // [3,1]
sampleSize([1, 2, 3], 4); // [2,3,1]
复制代码

26. shuffle:“洗牌” 数组

此代码段使用Fisher-Yates算法随机排序数组的元素。


const shuffle = ([...arr]) => {
  let m = arr.length;
  while (m) {
    const i = Math.floor(Math.random() * m--);
    [arr[m], arr[i]] = [arr[i], arr[m]];
  }
  return arr;
};

const foo = [1, 2, 3];
shuffle(foo); // [2, 3, 1], foo = [1, 2, 3]
复制代码

27. nest:根据parent_id生成树结构(阿里一面真题)

根据每项的parent_id,生成具体树形结构的对象。

const nest = (items, id = null, link = 'parent_id') =>
  items
    .filter(item => item[link] === id)
    .map(item => ({ ...item, children: nest(items, item.id) }));
复制代码

用法:

const comments = [
  { id: 1, parent_id: null },
  { id: 2, parent_id: 1 },
  { id: 3, parent_id: 1 },
  { id: 4, parent_id: 2 },
  { id: 5, parent_id: 4 }
];
const nestedComments = nest(comments); // [{ id: 1, parent_id: null, children: [...] }]
复制代码


强烈建议去理解这个的实现,因为这是我亲身遇到的阿里一面真题:


2. 第二部分:函数

1. attempt:捕获函数运行异常

该代码段执行一个函数,返回结果或捕获的错误对象。

onst attempt = (fn, ...args) => {
  try {
    return fn(...args);
  } catch (e) {
    return e instanceof Error ? e : new Error(e);
  }
};
var elements = attempt(function(selector) {
  return document.querySelectorAll(selector);
}, '>_>');
if (elements instanceof Error) elements = []; // elements = []
复制代码

2. defer:推迟执行

此代码段延迟了函数的执行,直到清除了当前调用堆栈。

const defer = (fn, ...args) => setTimeout(fn, 1, ...args);

defer(console.log, 'a'), console.log('b'); // logs 'b' then 'a'
复制代码

3. runPromisesInSeries:运行多个Promises

const runPromisesInSeries = ps => ps.reduce((p, next) => p.then(next), Promise.resolve());
const delay = d => new Promise(r => setTimeout(r, d));

runPromisesInSeries([() => delay(1000), () => delay(2000)]);
//依次执行每个Promises ,总共需要3秒钟才能完成
复制代码

4. timeTaken:计算函数执行时间


const timeTaken = callback => {
  console.time('timeTaken');
  const r = callback();
  console.timeEnd('timeTaken');
  return r;
};

timeTaken(() => Math.pow(2, 10)); // 1024, (logged): timeTaken: 0.02099609375ms
复制代码

5. createEventHub:简单的发布/订阅模式

创建一个发布/订阅(发布-订阅)事件集线,有emit,on和off方法。

  1. 使用Object.create(null)创建一个空的hub对象。
  2. emit,根据event参数解析处理程序数组,然后.forEach()通过传入数据作为参数来运行每个处理程序。
  3. on,为事件创建一个数组(若不存在则为空数组),然后.push()将处理程序添加到该数组。
  4. off,用.findIndex()在事件数组中查找处理程序的索引,并使用.splice()删除。
const createEventHub = () => ({
  hub: Object.create(null),
  emit(event, data) {
    (this.hub[event] || []).forEach(handler => handler(data));
  },
  on(event, handler) {
    if (!this.hub[event]) this.hub[event] = [];
    this.hub[event].push(handler);
  },
  off(event, handler) {
    const i = (this.hub[event] || []).findIndex(h => h === handler);
    if (i > -1) this.hub[event].splice(i, 1);
    if (this.hub[event].length === 0) delete this.hub[event];
  }
});
复制代码

用法:

const handler = data => console.log(data);
const hub = createEventHub();
let increment = 0;

// 订阅,监听不同事件
hub.on('message', handler);
hub.on('message', () => console.log('Message event fired'));
hub.on('increment', () => increment++);

// 发布:发出事件以调用所有订阅给它们的处理程序,并将数据作为参数传递给它们
hub.emit('message', 'hello world'); // 打印 'hello world' 和 'Message event fired'
hub.emit('message', { hello: 'world' }); // 打印 对象 和 'Message event fired'
hub.emit('increment'); // increment = 1

// 停止订阅
hub.off('message', handler);
复制代码

6. memoize:缓存函数

通过实例化一个Map对象来创建一个空的缓存。

通过检查输入值的函数输出是否已缓存,返回存储一个参数的函数,该参数将被提供给已记忆的函数;如果没有,则存储并返回它。

const memoize = fn => {
  const cache = new Map();
  const cached = function(val) {
    return cache.has(val) ? cache.get(val) : cache.set(val, fn.call(this, val)) && cache.get(val);
  };
  cached.cache = cache;
  return cached;
};
复制代码

Ps: 这个版本可能不是很清晰,还有Vue源码版的:

/**
 * Create a cached version of a pure function.
 */
export function cached<F: Function> (fn: F): F {
  const cache = Object.create(null)
  return (function cachedFn (str: string) {
    const hit = cache[str]
    return hit || (cache[str] = fn(str))
  }: any)
}
复制代码

7. once:只调用一次的函数

const once = fn => {
  let called = false
  return function () {
    if (!called) {
      called = true
      fn.apply(this, arguments)
    }
  }
};
复制代码

用法:

const startApp = function(event) {
  console.log(this, event); // document.body, MouseEvent
};
document.body.addEventListener('click', once(startApp)); // 只执行一次startApp
复制代码

8. flattenObject:以键的路径扁平化对象

使用递归。

  1. 利用Object.keys(obj)联合Array.prototype.reduce(),以每片叶子节点转换为扁平的路径节点。
  2. 如果键的值是一个对象,则函数使用调用适当的自身prefix以创建路径Object.assign()。
  3. 否则,它将适当的前缀键值对添加到累加器对象。
  4. prefix除非您希望每个键都有一个前缀,否则应始终省略第二个参数。
const flattenObject = (obj, prefix = '') =>
  Object.keys(obj).reduce((acc, k) => {
    const pre = prefix.length ? prefix + '.' : '';
    if (typeof obj[k] === 'object') Object.assign(acc, flattenObject(obj[k], pre + k));
    else acc[pre + k] = obj[k];
    return acc;
  }, {});
  
flattenObject({ a: { b: { c: 1 } }, d: 1 }); // { 'a.b.c': 1, d: 1 }
复制代码

9. unflattenObject:以键的路径展开对象

与上面的相反,展开对象。

const unflattenObject = obj =>
  Object.keys(obj).reduce((acc, k) => {
    if (k.indexOf('.') !== -1) {
      const keys = k.split('.');
      Object.assign(
        acc,
        JSON.parse(
          '{' +
            keys.map((v, i) => (i !== keys.length - 1 ? `"${v}":{` : `"${v}":`)).join('') +
            obj[k] +
            '}'.repeat(keys.length)
        )
      );
    } else acc[k] = obj[k];
    return acc;
  }, {});
  
unflattenObject({ 'a.b.c': 1, d: 1 }); // { a: { b: { c: 1 } }, d: 1 }
复制代码

这个的用途,在做Tree组件或复杂表单时取值非常舒服。

3. 第三部分:字符串

1.byteSize:返回字符串的字节长度

const byteSize = str => new Blob([str]).size;

byteSize(''); // 4
byteSize('Hello World'); // 11
复制代码

2. capitalize:首字母大写

const capitalize = ([first, ...rest]) =>
  first.toUpperCase() + rest.join('');
  
capitalize('fooBar'); // 'FooBar'
capitalize('fooBar', true); // 'Foobar'
复制代码

3. capitalizeEveryWord:每个单词首字母大写

const capitalizeEveryWord = str => str.replace(/\b[a-z]/g, char => char.toUpperCase());

capitalizeEveryWord('hello world!'); // 'Hello World!'
复制代码

4. decapitalize:首字母小写

const decapitalize = ([first, ...rest]) =>
  first.toLowerCase() + rest.join('')

decapitalize('FooBar'); // 'fooBar'
decapitalize('FooBar'); // 'fooBar'
复制代码

5. luhnCheck:银行卡号码校验(luhn算法)

Luhn算法的实现,用于验证各种标识号,例如信用卡号,IMEI号,国家提供商标识号等。

与String.prototype.split('')结合使用,以获取数字数组。获得最后一个数字。实施luhn算法。如果被整除,则返回,否则返回。

const luhnCheck = num => {
  let arr = (num + '')
    .split('')
    .reverse()
    .map(x => parseInt(x));
  let lastDigit = arr.splice(0, 1)[0];
  let sum = arr.reduce((acc, val, i) => (i % 2 !== 0 ? acc + val : acc + ((val * 2) % 9) || 9), 0);
  sum += lastDigit;
  return sum % 10 === 0;
};
复制代码

用例:

luhnCheck('4485275742308327'); // true
luhnCheck(6011329933655299); //  false
luhnCheck(123456789); // false
复制代码

补充:银行卡号码的校验规则

关于luhn算法,可以参考以下文章:

银行卡号码校验算法(Luhn算法,又叫模10算法)

银行卡号码的校验采用Luhn算法,校验过程大致如下:

  1. 从右到左给卡号字符串编号,最右边第一位是1,最右边第二位是2,最右边第三位是3….
  2. 从右向左遍历,对每一位字符t执行第三个步骤,并将每一位的计算结果相加得到一个数s。
  3. 对每一位的计算规则:如果这一位是奇数位,则返回t本身,如果是偶数位,则先将t乘以2得到一个数n,如果n是一位数(小于10),直接返回n,否则将n的个位数和十位数相加返回。
  4. 如果s能够整除10,则此号码有效,否则号码无效。

因为最终的结果会对10取余来判断是否能够整除10,所以又叫做模10算法。

当然,还是库比较香: bankcardinfo



6. splitLines:将多行字符串拆分为行数组。

使用String.prototype.split()和正则表达式匹配换行符并创建一个数组。

const splitLines = str => str.split(/\r?\n/);

splitLines('This\nis a\nmultiline\nstring.\n'); // ['This', 'is a', 'multiline', 'string.' , '']
复制代码

7. stripHTMLTags:删除字符串中的HTMl标签

从字符串中删除HTML / XML标签。

使用正则表达式从字符串中删除HTML / XML 标记。

const stripHTMLTags = str => str.replace(/<[^>]*>/g, '');

stripHTMLTags('<p><em>lorem</em> <strong>ipsum</strong></p>'); // 'lorem ipsum'
复制代码

4. 第四部分:对象

1. dayOfYear:当前日期天数

const dayOfYear = date =>
  Math.floor((date - new Date(date.getFullYear(), 0, 0)) / 1000 / 60 / 60 / 24);

dayOfYear(new Date()); // 285
复制代码

2. forOwn:迭代属性并执行回调

const forOwn = (obj, fn) => Object.keys(obj).forEach(key => fn(obj[key], key, obj));
forOwn({ foo: 'bar', a: 1 }, v => console.log(v)); // 'bar', 1
复制代码

3. Get Time From Date:返回当前24小时制时间的字符串

const getColonTimeFromDate = date => date.toTimeString().slice(0, 8);

getColonTimeFromDate(new Date()); // "08:38:00"
复制代码

4. Get Days Between Dates:返回日期间的天数

const getDaysDiffBetweenDates = (dateInitial, dateFinal) =>
  (dateFinal - dateInitial) / (1000 * 3600 * 24);
  
getDaysDiffBetweenDates(new Date('2019-01-01'), new Date('2019-10-14')); // 286
复制代码

5. is:检查值是否为特定类型。

const is = (type, val) => ![, null].includes(val) && val.constructor === type;

is(Array, [1]); // true
is(ArrayBuffer, new ArrayBuffer()); // true
is(Map, new Map()); // true
is(RegExp, /./g); // true
is(Set, new Set()); // true
is(WeakMap, new WeakMap()); // true
is(WeakSet, new WeakSet()); // true
is(String, ''); // true
is(String, new String('')); // true
is(Number, 1); // true
is(Number, new Number(1)); // true
is(Boolean, true); // true
is(Boolean, new Boolean(true)); // true
复制代码

6. isAfterDate:检查是否在某日期后

const isAfterDate = (dateA, dateB) => dateA > dateB;

isAfterDate(new Date(2010, 10, 21), new Date(2010, 10, 20)); // true
复制代码

7. isBeforeDate:检查是否在某日期前

const isBeforeDate = (dateA, dateB) => dateA < dateB;

isBeforeDate(new Date(2010, 10, 20), new Date(2010, 10, 21)); // true
复制代码

8 tomorrow:获取明天的字符串格式时间


const tomorrow = () => {
  let t = new Date();
  t.setDate(t.getDate() + 1);
  return t.toISOString().split('T')[0];
};

tomorrow(); // 2019-10-15 (如果明天是2019-10-15)
复制代码

9. equals:全等判断

在两个变量之间进行深度比较以确定它们是否全等。

此代码段精简的核心在于Array.prototype.every()的使用。

const equals = (a, b) => {
  if (a === b) return true;
  if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();
  if (!a || !b || (typeof a !== 'object' && typeof b !== 'object')) return a === b;
  if (a.prototype !== b.prototype) return false;
  let keys = Object.keys(a);
  if (keys.length !== Object.keys(b).length) return false;
  return keys.every(k => equals(a[k], b[k]));
};
复制代码

用法:

equals({ a: [2, { e: 3 }], b: [4], c: 'foo' }, { a: [2, { e: 3 }], b: [4], c: 'foo' }); // true
复制代码

5. 第五部分:数字

1. randomIntegerInRange:生成指定范围的随机整数

const randomIntegerInRange = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;

randomIntegerInRange(0, 5); // 3
复制代码

2. randomNumberInRange:生成指定范围的随机小数

const randomNumberInRange = (min, max) => Math.random() * (max - min) + min;

randomNumberInRange(2, 10); // 6.0211363285087005
复制代码

3. round:四舍五入到指定位数

const round = (n, decimals = 0) => Number(`${Math.round(`${n}e${decimals}`)}e-${decimals}`);

round(1.005, 2); // 1.01
复制代码

4. sum:计算数组或多个数字的总和


const sum = (...arr) => [...arr].reduce((acc, val) => acc + val, 0);

sum(1, 2, 3, 4); // 10
sum(...[1, 2, 3, 4]); // 10
复制代码

5. toCurrency:简单的货币单位转换

const toCurrency = (n, curr, LanguageFormat = undefined) =>
  Intl.NumberFormat(LanguageFormat, { style: 'currency', currency: curr }).format(n);
  
toCurrency(123456.789, 'EUR'); // €123,456.79
toCurrency(123456.789, 'USD', 'en-us'); // $123,456.79  
toCurrency(123456.789, 'USD', 'fa'); // ۱۲۳٬۴۵۶٫۷۹
toCurrency(322342436423.2435, 'JPY'); // ¥322,342,436,423 
复制代码

6. 第六部分:浏览器操作及其它

1. bottomVisible:检查页面底部是否可见

const bottomVisible = () =>
  document.documentElement.clientHeight + window.scrollY >=
  (document.documentElement.scrollHeight || document.documentElement.clientHeight);

bottomVisible(); // true
复制代码

2. Create Directory:检查创建目录

此代码段调用fs模块的existsSync()检查目录是否存在,如果不存在,则mkdirSync()创建该目录。

const fs = require('fs');
const createDirIfNotExists = dir => (!fs.existsSync(dir) ? fs.mkdirSync(dir) : undefined);
createDirIfNotExists('test'); 
复制代码

3. currentURL:返回当前链接url

const currentURL = () => window.location.href;

currentURL(); // 'https://juejin.im'
复制代码

4. distance:返回两点间的距离

该代码段通过计算欧几里得距离来返回两点之间的距离。

const distance = (x0, y0, x1, y1) => Math.hypot(x1 - x0, y1 - y0);

distance(1, 1, 2, 3); // 2.23606797749979
复制代码

5. elementContains:检查是否包含子元素

此代码段检查父元素是否包含子元素。

const elementContains = (parent, child) => parent !== child && parent.contains(child);

elementContains(document.querySelector('head'), document.querySelector('title')); // true
elementContains(document.querySelector('body'), document.querySelector('body')); // false
复制代码

6. getStyle:返回指定元素的生效样式

const getStyle = (el, ruleName) => getComputedStyle(el)[ruleName];

getStyle(document.querySelector('p'), 'font-size'); // '16px'
复制代码

7. getType:返回值或变量的类型名

const getType = v =>
  v === undefined ? 'undefined' : v === null ? 'null' : v.constructor.name.toLowerCase();
  
getType(new Set([1, 2, 3])); // 'set'
getType([1, 2, 3]); // 'array'
复制代码

8. hasClass:校验指定元素的类名

const hasClass = (el, className) => el.classList.contains(className);
hasClass(document.querySelector('p.special'), 'special'); // true
复制代码

9. hide:隐藏所有的指定标签

const hide = (...el) => [...el].forEach(e => (e.style.display = 'none'));

hide(document.querySelectorAll('img')); // 隐藏所有<img>标签
复制代码

10. httpsRedirect:HTTP 跳转 HTTPS

const httpsRedirect = () => {
  if (location.protocol !== 'https:') location.replace('https://' + location.href.split('//')[1]);
};

httpsRedirect(); // 若在`http://www.baidu.com`, 则跳转到`https://www.baidu.com`
复制代码

11. insertAfter:在指定元素之后插入新元素

const insertAfter = (el, htmlString) => el.insertAdjacentHTML('afterend', htmlString);

// <div id="myId">...</div> <p>after</p>
insertAfter(document.getElementById('myId'), '<p>after</p>'); 
复制代码

12. insertBefore:在指定元素之前插入新元素

const insertBefore = (el, htmlString) => el.insertAdjacentHTML('beforebegin', htmlString);

insertBefore(document.getElementById('myId'), '<p>before</p>'); // <p>before</p> <div id="myId">...</div>
复制代码

13. isBrowser:检查是否为浏览器环境

此代码段可用于确定当前运行时环境是否为浏览器。这有助于避免在服务器(节点)上运行前端模块时出错。

const isBrowser = () => ![typeof window, typeof document].includes('undefined');

isBrowser(); // true (browser)
isBrowser(); // false (Node)
复制代码

14. isBrowserTab:检查当前标签页是否活动

const isBrowserTabFocused = () => !document.hidden;

isBrowserTabFocused(); // true
复制代码

15. nodeListToArray:转换nodeList为数组

const nodeListToArray = nodeList => [...nodeList];

nodeListToArray(document.childNodes); // [ <!DOCTYPE html>, html ]
复制代码

16. Random Hexadecimal Color Code:随机十六进制颜色


const randomHexColorCode = () => {
  let n = (Math.random() * 0xfffff * 1000000).toString(16);
  return '#' + n.slice(0, 6);
};

randomHexColorCode(); // "#e34155"
复制代码

17. scrollToTop:平滑滚动至顶部

该代码段可用于平滑滚动到当前页面的顶部。

const scrollToTop = () => {
  const c = document.documentElement.scrollTop || document.body.scrollTop;
  if (c > 0) {
    window.requestAnimationFrame(scrollToTop);
    window.scrollTo(0, c - c / 8);
  }
};

scrollToTop();
复制代码

18. smoothScroll:滚动到指定元素区域

该代码段可将指定元素平滑滚动到浏览器窗口的可见区域。

const smoothScroll = element =>
  document.querySelector(element).scrollIntoView({
    behavior: 'smooth'
  });
  
smoothScroll('#fooBar'); 
smoothScroll('.fooBar'); 
复制代码

19. detectDeviceType:检测移动/PC设备

const detectDeviceType = () =>
  /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)
    ? 'Mobile'
    : 'Desktop';
复制代码

20. getScrollPosition:返回当前的滚动位置

默认参数为window ,pageXOffset(pageYOffset)为第一选择,没有则用scrollLeft(scrollTop)

const getScrollPosition = (el = window) => ({
  x: el.pageXOffset !== undefined ? el.pageXOffset : el.scrollLeft,
  y: el.pageYOffset !== undefined ? el.pageYOffset : el.scrollTop
});

getScrollPosition(); // {x: 0, y: 200}
复制代码

21. size:获取不同类型变量的字节长度

这个的实现非常巧妙,利用Blob类文件对象的特性,获取对象的长度。

另外,多重三元运算符,是真香。

const size = val =>
  Array.isArray(val)
    ? val.length
    : val && typeof val === 'object'
    ? val.size || val.length || Object.keys(val).length
    : typeof val === 'string'
    ? new Blob([val]).size
    : 0;

size([1, 2, 3, 4, 5]); // 5
size('size'); // 4
size({ one: 1, two: 2, three: 3 }); // 3

复制代码

22. escapeHTML:转义HTML

当然是用来防XSS攻击啦。

const escapeHTML = str =>
  str.replace(
    /[&<>'"]/g,
    tag =>
      ({
        '&': '&',
        '<': '<',
        '>': '>',
        "'": ''',
        '"': '"'
      }[tag] || tag)
  );

escapeHTML('<a href="#">Me & you</a>'); // '<a href="#">Me & you</a>'
复制代码



❤️ 看完三件事

如果你觉得这篇内容对你挺有启发,我想邀请你帮我三个小忙:

  1. 点赞,让更多的人也能看到这篇内容(收藏不点赞,都是耍流氓 -_-)
  2. 关注公众号「前端劝退师」,不定期分享原创知识。
  3. 也看看其它文章


原链接:https://juejin.im/post/5da1a04ae51d45783d6122bf

阶函数

高阶函数可以接收函数作为参数,同时也可以返回一个新的函数。

高阶函数之所以高阶,是因为高阶函数的参数和返回值对象可以是函数,这超越了普通函数处理的数据类型,例如字符串(strings)、数字(numbers)、布尔值(booleans)等。

JavaScript 中,函数的应用场景很丰富:

  • 作为变量存储
  • 在数组中使用
  • 作为对象属性(即方法)
  • 作为参数传递
  • 作为其他函数的返回值

理解高阶函数的关键在于,函数即数据

数据是函数运作的基本

数据:字符串(Strings)

sayHi = (name) => `Hi, ${name}!`;
result = sayHi('User');
console.log(result); // 'Hi, User!'

数据:数字(Numbers)

double = (x) => x * 2;
result = double(4);
console.log(result); // 8

数据:布尔值(Booleans)

getClearance = (allowed) => allowed ? 'Access granted' : 'Access denied';
result1 = getClearance(true);
result2 = getClearance(false);
console.log(result1); // 'Access granted'
console.log(result2); // 'Access denied'

数据:对象(Objects)

getFirstName = (obj) => obj.firstName;
result = getFirstName({ 
 firstName: 'Yazeed'
});
console.log(result); // 'Yazeed'

数据:数组(Arrays)

len = (array) => array.length;
result = len([1, 2, 3]);
console.log(result); // 3

在所有的主流语言中,以上这五种数据类型被称为 “头等对象”(原文:first-class citizen, https://www.wikiwand.com/en/First-class_citizen)。

为什么是“头等”呢?因为这五种数据类型既可以作为参数传递,又可以存储在变量或者数组中,还可以作为变量用于计算,是数据的基本形式。

函数也是数据

函数作为参数

isEven = (num) => num % 2 === 0;
result = [1, 2, 3, 4].filter(isEven);
console.log(result); // [2, 4]

请观察 filter 函数是如何使用 isEven 函数来判断要保留哪些内容的。这里的 isEven 是一个函数,作为参数传入了 filter 函数中。

filter 函数每次在做判断的时候都会调用 isEven 函数,用 isEven 函数返回的布尔值来决定当前数值的去留。

函数作为返回值

add = (x) => (y) => x + y;

add 函数需要两个参数,但不需要它们俩同时传入,第一次传参传入 x 就会返还一个新函数,这个函数需要传入 y 参数。

能够这样操作的基础在于 JavaScript 语言允许函数本身作为返回值存在,就像函数可以返回字符串(strings)、数字(numbers)、布尔值(booleans)等,JS 函数还可以返回另一个函数。

当然,我们也可以使用“双重调用”的方式,一次性提供 x 和 y 两个参数:

result = add(10)(20);
console.log(result); // 30

或者分两次调用,先传参数 x,再传参数 y:

add10 = add(10);
result = add10(20);
console.log(result); // 30

在上面这个例子中, add10 函数是第一次调用 add 函数的返回值,可以尝试用 console.log把结果打出来观察一下。

add10 函数会接收 y 参数,然后返回 x + y 值。一旦 y 值到位,函数会立马进行运算并返回结果。

可重复利用性

高阶函数的魅力在于它的可重复利用性,如果不是高阶函数,map、filter、reduce 等强大的数组函数就不可能存在。

假设我们有一组用户,如下所示,然后我们要对该数组进行操作。

users = [
 {
 name: 'Yazeed',
 age: 25
 },
 {
 name: 'Sam',
 age: 30
 },
 {
 name: 'Bill',
 age: 20
 }
];

Map

没有高阶函数的话,我们必须回到 for 循环的怀抱才能实现 map 函数的操作。

getName = (user) => user.name;
usernames = [];
for (let i = 0; i < users.length; i++) {
 const name = getName(users[i]);
 usernames.push(name);
}
console.log(usernames);
// ["Yazeed", "Sam", "Bill"]

用 map 函数就简单多啦!

usernames = users.map(getName);
console.log(usernames);
// ["Yazeed", "Sam", "Bill"]

Filter

在没有高阶函数的情况下,必须要用 for 循环来实现 filter 函数的功能。

startsWithB = (string) => string.toLowerCase().startsWith('b');
namesStartingWithB = [];
for (let i = 0; i < users.length; i++) {
 if (startsWithB(users[i].name)) {
 namesStartingWithB.push(users[i]);
 }
}
console.log(namesStartingWithB);
// [{ "name": "Bill", "age": 20 }]


用 filter 函数就简单多啦!

namesStartingWithB = users.filter((user) => startsWithB(user.name));
console.log(namesStartingWithB);
// [{ "name": "Bill", "age": 20 }]

Reduce

reduce 函数也是的……没有高阶函数的话,很多高端操作都是无法实现的!

total = 0;
for (let i = 0; i < users.length; i++) {
 total += users[i].age;
}
console.log(total);
// 75


那这样是不是简单多啦?

totalAge = users.reduce((total, user) => user.age + total, 0);
console.log(totalAge);
// 75


总结

  • 字符串(strings)、数字(numbers)、布尔值(booleans)、数组(arrays)、对象(objects)可以作为变量(variables)、数组(arrays)、属性( properties)或者方法(methods)存储起来。
  • JavaScript 语言中,函数也是像数据一样同等对待的。
  • 因此函数可以作为另外一个函数的参数或者返回值使用,这样的做法叫高阶函数
  • map、filter、 reduce 等函数就是高阶函数的最佳代表,它们让数组的处理(改变,搜索,相加等)变得简单不少!

版权

原文链接:https://mp.weixin.qq.com/s/JAsy4TVv3tzL0q8RxXBf7Q

作者:jingruzhang

. this 适合你吗?

我看到许多文章在介绍 JavaScript 的 this 时都会假设你学过某种面向对象的编程语言,比如 Java、C++ 或 Python 等。但这篇文章面向的读者是那些不知道 this 是什么的人。我尽量不用任何术语来解释 this 是什么,以及 this 的用法。

也许你一直不敢解开 this 的秘密,因为它看起来挺奇怪也挺吓人的。或许你只在 StackOverflow 说你需要用它的时候(比如在 React 里实现某个功能)才会使用。

在深入介绍 this 之前,我们首先需要理解函数式编程和面向对象编程之间的区别。

2. 函数式编程 vs 面向对象编程

你可能不知道,JavaScript 同时拥有面向对象和函数式的结构,所以你可以自己选择用哪种风格,或者两者都用。

我在很早以前使用 JavaScript 时就喜欢函数式编程,而且会像躲避瘟疫一样避开面向对象编程,因为我不理解面向对象中的关键字,比如 this。我不知道为什么要用 this。似乎没有它我也可以做好所有的工作。

而且我是对的。

在某种意义上 。也许你可以只专注于一种结构并且完全忽略另一种,但这样你只能是一个 JavaScript 开发者。为了解释函数式和面向对象之间的区别,下面我们通过一个数组来举例说明,数组的内容是 Facebook 的好友列表。

假设你要做一个 Web 应用,当用户使用 Facebook 登录你的 Web 应用时,需要显示他们的 Facebook 的好友信息。你需要访问 Facebook 并获得用户的好友数据。这些数据可能是 firstName、lastName、username、numFriends、friendData、birthday 和 lastTenPosts 等信息。

const data = [
 {
 firstName: 'Bob',
 lastName: 'Ross',
 username: 'bob.ross', 
 numFriends: 125,
 birthday: '2/23/1985',
 lastTenPosts: ['What a nice day', 'I love Kanye West', ...],
 },
 ...
]

假设上述数据是你通过 Facebook API 获得的。现在需要将其转换成方便你的项目使用的格式。我们假设你想显示的好友信息如下:

  • 姓名,格式为`${firstName} ${lastName}`
  • 三篇随机文章
  • 距离生日的天数


3. 函数式方式

函数式的方式就是将整个数组或者数组中的某个元素传递给某个函数,然后返回你需要的信息:

const fullNames = getFullNames(data)
// ['Ross, Bob', 'Smith, Joanna', ...]

首先我们有 Facebook API 返回的原始数据。为了将其转换成需要的格式,首先要将数据传递给一个函数,函数的输出是(或者包含)经过修改的数据,这些数据可以在应用中向用户展示。

我们可以用类似的方法获得随机三篇文章,并且计算距离好友生日的天数。

函数式的方式是:将原始数据传递给一个函数或者多个函数,获得对你的项目有用的数据格式。

4. 面向对象的方式

对于编程初学者和 JavaScript 初学者,面向对象的概念可能有点难以理解。其思想是,我们要将每个好友变成一个对象,这个对象能够生成你一切开发者需要的东西。

你可以创建一个对象,这个对象对应于某个好友,它有 fullName 属性,还有两个函数 getThreeRandomPosts 和 getDaysUntilBirthday。

function initializeFriend(data) {
 return {
 fullName: `${data.firstName} ${data.lastName}`,
 getThreeRandomPosts: function() {
 // get three random posts from data.lastTenPosts
 },
 getDaysUntilBirthday: function() {
 // use data.birthday to get the num days until birthday
 }
 };
}
const objectFriends = data.map(initializeFriend)
objectFriends[0].getThreeRandomPosts() 
// Gets three of Bob Ross's posts

面向对象的方式就是为数据创建对象,每个对象都有自己的状态,并且包含必要的信息,能够生成需要的数据。

5. 这跟 this 有什么关系?

你也许从来没想过要写上面的 initializeFriend 代码,而且你也许认为,这种代码可能会很有用。但你也注意到,这并不是真正的面向对象。

其原因就是,上面例子中的 getThreeRandomPosts 或 getdaysUntilBirtyday 能够正常工作的原因其实是闭包。因为使用了闭包,它们在 initializeFriend 返回之后依然能访问 data。关于闭包的更多信息可以看看这篇文章:作用域和闭包(https://github.com/getify/You-Dont-Know-JS/blob/master/scope%20%26%20closures/ch5.md)。

还有一个方法该怎么处理?我们假设这个方法叫做 greeting。注意方法(与 JavaScript 的对象有关的方法)其实只是一个属性,只不过属性值是函数而已。我们想在 greeting 中实现以下功能:

function initializeFriend(data) {
 return {
 fullName: `${data.firstName} ${data.lastName}`,
 getThreeRandomPosts: function() {
 // get three random posts from data.lastTenPosts
 },
 getDaysUntilBirthday: function() {
 // use data.birthday to get the num days until birthday
 },
 greeting: function() {
 return `Hello, this is ${fullName}'s data!`
 }
 };
}

这样能正常工作吗?

不能!

我们新建的对象能够访问 initializeFriend 中的一切变量,但不能访问这个对象本身的属性或方法。当然你会问,

难道不能在 greeting 中直接用 data.firstName 和 data.lastName 吗?

当然可以。但要是想在 greeting 中加入距离好友生日的天数怎么办?我们最好还是有办法在 greeting 中调用 getDaysUntilBirthday。

这时轮到 this 出场了!



6. 终于——this 是什么

this 在不同的环境中可以指代不同的东西。默认的全局环境中 this 指代的是全局对象(在浏览器中 this 是 window 对象),这没什么太大的用途。而在 this 的规则中具有实用性的是这一条:

如果在对象的方法中使用 this,而该方法在该对象的上下文中调用,那么 this 指代该对象本身。

你会说“在该对象的上下文中调用”……是啥意思?

别着急,我们一会儿就说。

所以,如果我们想从 greeting 中调用 getDaysUntilBirtyday 我们只需要写 this.getDaysUntilBirthday,因为此时的 this 就是对象本身。

附注:不要在全局作用域的普通函数或另一个函数的作用域中使用 this!this 是个面向对象的东西,它只在对象的上下文(或类的上下文)中有意义。

我们利用 this 来重写 initializeFriend:

function initializeFriend(data) {
 return {
 lastTenPosts: data.lastTenPosts,
 birthday: data.birthday, 
 fullName: `${data.firstName} ${data.lastName}`,
 getThreeRandomPosts: function() {
 // get three random posts from this.lastTenPosts
 },
 getDaysUntilBirthday: function() {
 // use this.birthday to get the num days until birthday
 },
 greeting: function() {
 const numDays = this.getDaysUntilBirthday() 
 return `Hello, this is ${this.fullName}'s data! It is ${numDays} until ${this.fullName}'s birthday!`
 }
 };
}

现在,在 initializeFriend 执行结束后,该对象需要的一切都位于对象本身的作用域之内了。我们的方法不需要再依赖于闭包,它们只会用到对象本身包含的信息。

好吧,这是 this 的用法之一,但你说过 this 在不同的上下文中有不同的含义。那是什么意思?为什么不一定会指向对象自己?

有时候,你需要将 this 指向某个特定的东西。一种情况就是事件处理函数。比如我们希望在用户点击好友时打开好友的 Facebook 首页。我们会给对象添加下面的 onClick 方法:

function initializeFriend(data) {
 return {
 lastTenPosts: data.lastTenPosts,
 birthday: data.birthday,
 username: data.username, 
 fullName: `${data.firstName} ${data.lastName}`,
 getThreeRandomPosts: function() {
 // get three random posts from this.lastTenPosts
 },
 getDaysUntilBirthday: function() {
 // use this.birthday to get the num days until birthday
 },
 greeting: function() {
 const numDays = this.getDaysUntilBirthday() 
 return `Hello, this is ${this.fullName}'s data! It is ${numDays} until ${this.fullName}'s birthday!`
 },
 onFriendClick: function() {
 window.open(`https://facebook.com/${this.username}`)
 }
 };
}

注意我们在对象中添加了 username 属性,这样 onFriendClick 就能访问它,从而在新窗口中打开该好友的 Facebook 首页。现在只需要编写 HTML:

<button id="Bob_Ross">
 <!-- A bunch of info associated with Bob Ross -->
</button> 

还有 JavaScript:

const bobRossObj = initializeFriend(data[0])
const bobRossDOMEl = document.getElementById('Bob_Ross')
bobRossDOMEl.addEventListener("onclick", bobRossObj.onFriendClick)

在上述代码中,我们给 Bob Ross 创建了一个对象。然后我们拿到了 Bob Ross 对应的 DOM 元素。然后执行 onFriendClick 方法来打开 Bob 的 Facebook 主页。似乎没问题,对吧?

有问题!

哪里出错了?

注意我们调用 onclick 处理程序的代码是 bobRossObj.onFriendClick。看到问题了吗?要是写成这样的话能看出来吗?

bobRossDOMEl.addEventListener("onclick", function() {
 window.open(`https://facebook.com/${this.username}`)
})

现在看到问题了吗?如果把事件处理程序写成 bobRossObj.onFriendClick,实际上是把 bobRossObj.onFriendClick 上保存的函数拿出来,然后作为参数传递。它不再“依附”在 bobRossObj 上,也就是说,this 不再指向 bobRossObj。它实际指向全局对象,也就是说 this.username 不存在。似乎我们没什么办法了。

轮到绑定上场了!



7. 明确绑定 this

我们需要明确地将 this 绑定到 bobRossObj 上。我们可以通过 bind 实现:

const bobRossObj = initializeFriend(data[0])
const bobRossDOMEl = document.getElementById('Bob_Ross')
bobRossObj.onFriendClick = bobRossObj.onFriendClick.bind(bobRossObj)
bobRossDOMEl.addEventListener("onclick", bobRossObj.onFriendClick)

之前,this 是按照默认的规则设置的。但使用 bind 之后,我们明确地将 bobRossObj.onFriendClick 中的 this 的值设置为 bobRossObj 对象本身。

到此为止,我们看到了为什么要使用 this,以及为什么要明确地绑定 this。最后我们来介绍一下,this 实际上是箭头函数。

8. 箭头函数

你也许注意到了箭头函数最近很流行。人们喜欢箭头函数,因为很简洁、很优雅。而且你还知道箭头函数和普通函数有点区别,尽管不太清楚具体区别是什么。

简而言之,两者的区别在于:

在定义箭头函数时,不管 this 指向谁,箭头函数内部的 this 永远指向同一个东西。

嗯……这貌似没什么用……似乎跟普通函数的行为一样啊?

我们通过 initializeFriend 举例说明。假设我们想添加一个名为 greeting 的函数:

function initializeFriend(data) {
 return {
 lastTenPosts: data.lastTenPosts,
 birthday: data.birthday,
 username: data.username, 
 fullName: `${data.firstName} ${data.lastName}`,
 getThreeRandomPosts: function() {
 // get three random posts from this.lastTenPosts
 },
 getDaysUntilBirthday: function() {
 // use this.birthday to get the num days until birthday
 },
 greeting: function() {
 function getLastPost() {
 return this.lastTenPosts[0]
 }
 const lastPost = getLastPost() 
 return `Hello, this is ${this.fullName}'s data!
 ${this.fullName}'s last post was ${lastPost}.`
 },
 onFriendClick: function() {
 window.open(`https://facebook.com/${this.username}`)
 }
 };
}

这样能运行吗?如果不能,怎样修改才能运行?

答案是不能。因为 getLastPost 没有在对象的上下文中调用,因此getLastPost 中的 this 按照默认规则指向了全局对象。

你说没有“在对象的上下文中调用”……难道它不是从 initializeFriend 返回的内部调用的吗?如果这还不叫“在对象的上下文中调用”,那我就不知道什么才算了。

我知道“在对象的上下文中调用”这个术语很模糊。也许,判断函数是否“在对象的上下文中调用”的好方法就是检查一遍函数的调用过程,看看是否有个对象“依附”到了函数上。

我们来检查下执行 bobRossObj.onFriendClick() 时的情况。“给我对象 bobRossObj,找到其中的 onFriendClick 然后调用该属性对应的函数”。

我们同样检查下执行 getLastPost() 时的情况。“给我名为 getLastPost 的函数然后执行。”看到了吗?我们根本没有提到对象。

好了,这里有个难题来测试你的理解程度。假设有个函数名为 functionCaller,它的功能就是调用一个函数:

functionCaller(fn) {
 fn()
}

如果调用 functionCaller(bobRossObj.onFriendClick) 会怎样?你会认为 onFriendClick 是“在对象的上下文中调用”的吗?this.username有定义吗?

我们来检查一遍:“给我 bobRosObj 对象然后查找其属性 onFriendClick。取出其中的值(这个值碰巧是个函数),然后将它传递给 functionCaller,取名为 fn。然后,执行名为 fn 的函数。”注意该函数在调用之前已经从 bobRossObj 对象上“脱离”了,因此并不是“在对象的上下文中调用”的,所以 this.username 没有定义。

这时可以用箭头函数解决这个问题:

function initializeFriend(data) {
 return {
 lastTenPosts: data.lastTenPosts,
 birthday: data.birthday,
 username: data.username, 
 fullName: `${data.firstName} ${data.lastName}`,
 getThreeRandomPosts: function() {
 // get three random posts from this.lastTenPosts
 },
 getDaysUntilBirthday: function() {
 // use this.birthday to get the num days until birthday
 },
 greeting: function() {
 const getLastPost = () => {
 return this.lastTenPosts[0]
 }
 const lastPost = getLastPost() 
 return `Hello, this is ${this.fullName}'s data!
 ${this.fullName}'s last post was ${lastPost}.`
 },
 onFriendClick: function() {
 window.open(`https://facebook.com/${this.username}`)
 }
 };
}

上述代码的规则是:

在定义箭头函数时,不管 this 指向谁,箭头函数内部的 this 永远指向同一个东西。

箭头函数是在 greeting 中定义的。我们知道,在 greeting 内部的 this 指向对象本身。因此,箭头函数内部的 this 也指向对象本身,这正是我们需要的结果。

9. 结论

this 有时很不好理解,但它对于开发 JavaScript 应用非常有用。本文当然没能介绍 this 的所有方面。一些没有涉及到的话题包括:

  • call 和 apply;
  • 使用 new 时 this 会怎样;
  • 在 ES6 的 class 中 this 会怎样。

我建议你首先问问自己在这些情况下的 this,然后在浏览器中执行代码来检验你的结果。

原文地址:https://mp.weixin.qq.com/s/L9eac6rzkSE_JqxXyg3FQw