【源码共读】第33期 | arrify转数组

463 阅读3分钟

首先看代码

export default function arrify(value) {
        //未传参数,或者传递null返回空数组
	if (value === null || value === undefined) {
		return [];
	}
        //传递数组返回本数组
	if (Array.isArray(value)) {
		return value;
	}
        //传递字符串返回本字符串单元素数组
	if (typeof value === 'string') {
		return [value];
	}
        //传递具有迭代器的参数,扩展符处理参数
	if (typeof value[Symbol.iterator] === 'function') {
		return [...value];
	}
        //默认返回单元素数组
	return [value];
}

不愧是【源码共读】活动号称第二简单的,除了[Symbol.iterator]之外都是常规操作,我们照常先走测试用例再看[Symbol.iterator]

其次看依赖

        "scripts": {
		"test": "xo && ava && tsd"
	},
	"devDependencies": {
		"ava": "^3.15.0",
		"tsd": "^0.14.0",
		"xo": "^0.39.1"
	}

脚本只有一个单元测试脚本,但继发执行了xo ava tsd三个指令,我们分别看看这三个指令干了些什么

xo

一个基于eslint的代码检查工具,0配置

tsd

提升IDE对JavaScript智能感知的能力,可以理解为给IDE看的类型关系

ava

Create your test file

Create a file named test.js in the project root directory:

import test from 'ava';

test('foo', t => {
	t.pass();
});

test('bar', async t => {
	const bar = Promise.resolve('bar');
	t.is(await bar, 'bar');
});

Running your tests

npm test

我们打印看下t有哪些api


ExecutionContext {
  pass: [Function] { skip: [Function: skip] },//测试通过。
  fail: [Function] { skip: [Function: skip] },//断言失败。
  is: [Function] { skip: [Function: skip] },//断言 `value` 是否和 `expected` 相等。
  not: [Function] { skip: [Function: skip] },//断言 `value` 是否和 `expected` 不等。
  deepEqual: [Function] { skip: [Function: skip] },//断言 `value` 是否和 `expected` 深度相等。
  notDeepEqual: [Function] { skip: [Function: skip] },//断言 `value` 是否和 `expected` 深度不等。
  like: [Function] { skip: [Function: skip] },
  throws: [Function] { skip: [Function: skip] },
  throwsAsync: [Function] { skip: [Function: skip] },
  notThrows: [Function] { skip: [Function: skip] },
  notThrowsAsync: [Function] { skip: [Function: skip] },
  snapshot: [Function] { skip: [Function] },
  truthy: [Function] { skip: [Function: skip] },断言 `value` 是否是真值。
  falsy: [Function] { skip: [Function: skip] },//断言 `value` 是否是假值。
  true: [Function] { skip: [Function: skip] },//断言 `value` 是否是 `true`。
  false: [Function] { skip: [Function: skip] },//断言 `value` 是否是 `false`。
  regex: [Function] { skip: [Function: skip] },//断言 `contents` 匹配 `regex`
  notRegex: [Function] { skip: [Function: skip] },//断言 `contents` 不匹配 `regex`。
  assert: [Function] { skip: [Function: skip] },
  log: [Function],
  plan: [Function] { skip: [Function] },
  timeout: [Function],
  teardown: [Function],
  try: [AsyncFunction]
}

所以可以理解测试用例代码意图

import test from 'ava';
import arrify from './index.js';

test('main', t => {
	t.deepEqual(arrify('foo'), ['foo']);//判断生成的数组和 ['foo']是否深度相等
	t.deepEqual(arrify(new Map([[1, 2], ['a', 'b']])), [[1, 2], ['a', 'b']]);
	t.deepEqual(arrify(new Set([1, 2])), [1, 2]);
	t.deepEqual(arrify(null), []);
	t.deepEqual(arrify(undefined), []);

	const fooArray = ['foo'];
	t.is(arrify(fooArray), fooArray);//判断fooArray是否地址相同
});

[Symbol.iterator]

说到[Symbol.iterator],我想到一道经典的JS面试题 forin forof的区别,其中forof正是基于迭代器Iterator的,而迭代器最主要依赖的API就是[Symbol.iterator]。 对象无法forof就是因为没有[Symbol.iterator],如果我们手写一个就可以试下对象的forof。 今年年初的时候因为forin学习下对象的枚举属性,因为forof学习了迭代器,都是参照红宝书四。所以下面代码示例也参照红宝书

class Counter {
    constructor(limit) {
        this.limit = limit;
        }
        [Symbol.iterator]() {
        let count = 1,
            limit = this.limit;
            return {
                next() {
                    if (count <= limit) {
                        return { done: false, value: count++ };
                    } else {
                        return { done: true, value: undefined };
                    }
                  }
            };
        }
        }
let counter = new Counter(3);
for (let i of counter) { console.log(i); }
// 1
// 2
// 3
for (let i of counter) { console.log(i); }
// 1
// 2
// 3

Array Set Map默认都是有迭代器了,如果我们给对象手写迭代器也可以,比如我们把上面示例写入测试用例

import test from 'ava';
import arrify from './index.js';

class Counter {
    constructor(limit) {
        this.limit = limit;
        }
        [Symbol.iterator]() {
        let count = 1,
            limit = this.limit;
            return {
                next() {
                    if (count <= limit) {
                        return { done: false, value: count++ };
                    } else {
                        return { done: true, value: undefined };
                    }
                  }
            };
        }
        }

let foo=new Counter(3) 
console.log(typeof foo)
test('main', t => {
	t.deepEqual(arrify(foo ), [1,2,3]);

});

image.png 可以看到foo的类型是object,但是成功转成了[1,2,3],就是因为生成的对象有[Symbol.iterator],而且手写了迭代规则。如果我们想玩玩,那就可以给Object添加原型

Object.prototype[Symbol.iterator] = function() {
  let index = 0
  let o=this
  return {
    next()
    {
      let keys = Object.keys(o)
      if(index < keys.length) {
        return {
          done: false,
          value: o[keys[index++]]
        }
      } else {
        return {
          done: true,
          value: undefined
        }
      }
    }
  }
}

只有执行过上述代码,我们连window对象都能forof

总结

  • 开源项目没想象的那么难,但开源项目涉及的知识点没想象的那么少
  • 开始学习迭代器的时候不知道有什么用,现在发现在判断参数类型时候还是很有用的
  • xo其实挺麻烦的,我在写测试用例的时候都说删掉之后再执行npm run test的,可能我们开发规范不够,后面还需改善
  • 红宝书有时间还是要多刷