arrify source code reading | 19 lines of core code analysis and harvest beyond code

Foreword

At the beginning of the new year, Vientiane is updated, it is advisable to climb high and look far, and it is advisable to read the source code.

In the new year, the first Flag is source code reading.

The reason why I am obsessed with the source code is on the one hand because I read several large JavaScript-related books last year through daily updates, and sorted out and filtered many knowledge points in MDN. On the other hand, in the first phase of the source code co-reading activity, I started to pay attention to Mr. Ruochuan, who provided a lot of directions for source code reading.

As the saying goes, There is a big tree on the back to enjoy the shade. It is planned to focus on 1~2 months of energy and conduct a wave of source code reading.

This article is an interpretation of arrify source code, and in the process of interpretation, new ideas about implementing code and the use of tsd type verification tool library.

Core code

The core code of arrify is very short, a total of 19 lines. These 19 lines include a total of 5 conversion methods.

5 conversion methods

? When the value does not exist, it is converted to an empty array

if (value === null || value === undefined) {
  return [];
}

? When the value is Array type, the value does not need additional conversion

if (Array.isArray(value)) {<!-- -->
  return value;
}

? When the value is a string type, return the final value converted into an array type

if (typeof value === 'string') {
  return [value];
}

? When the value is a type that has an iterator, use the spread operator to take out the traversable value and put it in the array

if (typeof value[Symbol.iterator] === 'function') {<!-- -->
return [...value];
}

? Does not belong to any of the above four situations, and returns the final value converted into an array type

return [value];

Why do you need to add Symbol.iterator-related validation?

Some built-in types have default iterator behavior, and Symbol.iterator can define default iterators for them.

Use typeof to check whether the type of the Symbol.iterator method is a function, which can distinguish such type values.

What are the built-in types

One of MDN lists five:

  • [Array.prototype@@iterator]
  • [TypedArray.prototype[@@iterator()]]
  • [String.prototype[@@iterator()]]
  • [Map.prototype[@@iterator()]]
  • [Set.prototype[@@iterator()]]

Let’s use the Set type as a small example

const set1 = new Set();
let res = typeof set1[Symbol.iterator] === 'function';
console. log(res);

Run results

// => true

Discussion: Conversion method? Can it be replaced?

Since the built-in types with iterator behavior include the Array type, doesn’t the conversion method ? include the method ??

const list1 = [1, 2, 3];
let res = typeof list1[Symbol.iterator] === 'function';
console. log(res);

Run results

// => true

Conclusion

I ran the modified code with a test case, and the result was consistent with the one before the modification. So I personally feel that it is possible to keep only the method?.

What do you guys think?

Supplement:

I re-read the source code, combined with Brother Chuan’s reply, indeed, what Brother Chuan said is right

Judging the array in advance and returning it directly, there is no need to […value] again

Add something more interesting:

Recently I was looking for AI to help optimize the code, so I put this source code in, the following is the optimization result:

// Optimized code
export default function arrify(value) {<!-- -->
  // Returns an empty array if the value is null or undefined
  if (value == null) return [];
  
  // If it is an array, return the array directly
  if (Array.isArray(value)) return value;
  
  // If it is a string, convert it to a single-element array and return
  if (typeof value === 'string') return [value];
  
  // If it has an iterator property, expand it into an array and return
  if (typeof value[Symbol.iterator] === 'function') return [...value];
  
  // Otherwise, return the value as a single-element array
  return [value];
}

Optimization ideas:

1. Change the condition of checking null in the first condition to ==, which can check both null and undefined.
1. Reduce unnecessary nesting, return values directly in each branch, and avoid using multiple return statements.
1. Use more concise syntax, such as destructuring assignment and expansion operator, to make the code more compact and easy to read.

Test case

Let’s run through all the test cases and see if the results are as expected.

console.log(arrify(null));
console.log(arrify(undefined));
console.log(arrify('1'));
console.log(arrify(['1']));
console.log(arrify(true));
console.log(arrify(1));
console.log(arrify({}));
console.log(arrify([1, 'foo']));
console.log(arrify(new Set(['1', true])));
console.log(arrify(new Set([1, 2])));
console.log(arrify(['1']));
console.log(arrify(['1']).push(''));
console.log(arrify(false ? [1, 2] : null));
console.log(arrify(false ? [1, 2] : undefined));
console.log(arrify(false ? [1, 2] : '1'));
console.log(arrify(false ? [1, 2] : ['1']));
console.log(arrify(false ? [1, 2] : true));
console.log(arrify(false ? [1, 2] : 3));
console.log(arrify(false ? [1, 2] : {}));
console.log(arrify(false ? [1, 2] : [1, 'foo']));
console.log(arrify(false ? [1, 2] : new Set(['1', true])));
console.log(arrify(false ? [1, 2] : false ? true : '1'));

Run results

[]
[]
[ '1' ]
[ '1' ]
[ true ]
[ 1 ]
[ {<!-- -->} ]
[ 1, 'foo' ]
[ '1', true ]
[ 1, 2 ]
[ '1' ]
2
[]
[]
[ '1' ]
[ '1' ]
[ true ]
[ 3 ]
[ {<!-- -->} ]
[ 1, 'foo' ]
[ '1', true ]
[ '1' ]

tsd | type checking tool library

Using the expectType method provided by tsd can help check whether the expected type is consistent with the type of the expression or variable, and the syntax is relatively simple:

expectType<expected type> (expression or variable)

So you can see the following code in the source code:

expectType<[]>(arrify(null));

The result of arrify(null) in the previous conversion method is [], so the above use case is passed.

There is also an expectAssignable method in the code, which can help check whether the type of the expression or variable can be assigned to the expected type.

expectAssignable<number[] | [boolean]>(arrify(false ? [1, 2] : true));
expectAssignable<number[]>(arrify(false ? [1, 2] : true));

The result of running the above two lines of code is that the first line passes and the second line fails. Because boolean results are not assignable to numeric types.

Summary

Recently, I have read a lot of source code. One of the gains other than learning design patterns is to try independent thinking by myself, such as another implementation method discussed above.

“Read the book a hundred times and you will see its meaning”, divergent thinking, and verifying the proficiency of technology can also help break through some bottlenecks.

Before that, I was always struggling to follow the path that others had already walked, without my own creativity. Also in this step, a lot of imprints dissipated.

If you are interested in the source code, but have no learning goals and directions, here you can manually learn the source code with Brother Chuan.