[toc]

background

Yesterday, I thought it was very simple when I saw a pen test, but the result was not as I imagined, directly on the pen test.

const array = new Array(5).map((item) = > {
  return item = {
    name: '1'}});console.log(array);
// Write the output
Copy the code

I think the answer: [{name: ‘1’}, {name: ‘1’}, {name: ‘1’}, {name: ‘1’}, {name: ‘1’}].

The actual answer: [empty × 5]

Why is this happening?

Hypothesis 1

The Array generated by new Array(5) is undefined, undefined, undefined, undefined, undefined.

const array = [undefined.undefined.undefined.undefined.undefined];
const newArr = array.map((item) = > {
  return item = {
     name: '1'}});console.log(newArr);
/ / as a result, [{name: '1'}, {name: '1'}, {name: '1'}, {name: '1'}, {name: '1'}].
Copy the code

Guess 1 wrong

Suppose 2

New Array(5) generates an Array with no value in each entry, meaning [,,,,,] is generated.

const array = [,,,,,];
const newArr = array.map((item) = > {
  return item = {
     name: '1'}});console.log(newArr);
// The result is [empty × 5];
Copy the code

Guess 2 is true.

why

map calls a provided callback function once for each element in an array, in order, and constructs a new array from the results. callback is invoked only for indexes of the array which have assigned values (including undefined).

It is not called for missing elements of the array; that is:

  • indexes that have never been set;
  • which have been deleted; or
  • which have never been assigned a value.

The Map calls the supplied callback function once for each element in the array, then constructs a new array based on the result. —– calls —- only for array indexes with assigned values (including). The map callback is only called for items that have been assigned a value. New Array(1) is different from [undefined]. New Array(1) does not assign a value to an item in the Array, whereas [undefined] assigns a value of undefined to an item in the Array. conclusion

New Array(5) produces an Array that has no values assigned to its items. Map only makes callback calls to array indexes with assigned values (including).

An in-depth look at the MAP method

const array = new Array(5)
Copy the code

Can be interpreted as

const array = []
array.length = 5
Copy the code

It’s understandable

const array = [,,,,,]
Copy the code

But this leaves me with a question:

Back when I learned the handwritten map method

You baidu, you will find that basically a lot of people are handwritten like this:

Array.prototype.MyMap = function(fn, context){
  var arr = Array.prototype.slice.call(this);// Since it is ES5, there is no need to... An operator for
  var mappedArr = [];
  for (var i = 0; i < arr.length; i++ ){
    mappedArr.push(fn.call(context, arr[i], i, this));
  }
  return mappedArr;
}

Copy the code

This seems fine, but the map’s handwritten source code does not explain the return of [empty × 5].

We can look at the return result:

As the picture shows, oh my God, this is a trap!

So how should a real map method be implemented?

My guess is that it will go through each item and determine if the current item is empty, in which case it won’t do anything inside, which is the code inside the for loop

Ok, so the question is, how do you know that the current item is empty? For this reason, let’s take a look at the real source code of MAP.

According to the ECMA262 draft, the map specification implemented is as follows:

The map function is simulated step by step according to the provisions of the draft:

Array.prototype.map = function(callbackFn, thisArg) {
  // Handle an array type exception
  if (this= = =null || this= = =undefined) {
    throw new TypeError("Cannot read property 'map' of null or undefined");
  }
  // Handle callback type exceptions
  if (Object.prototype.toString.call(callbackfn) ! ="[object Function]") {
    throw new TypeError(callbackfn + ' is not a function')}// The draft says to convert to objects first
  let O = Object(this);
  let T = thisArg;

  
  let len = O.length >>> 0;
  let A = new Array(len);
  for(let k = 0; k < len; k++) {
    // Remember in the prototype chain section? In means look up in the prototype chain
    // If hasOwnProperty is a problem, it can only find private properties
    if (k in O) {
      let kValue = O[k];
      // Pass this, the current item, the current index, the entire array
      letmappedValue = callbackfn.call(T, KValue, k, O); A[k] = mappedValue; }}return A;
` `}
Copy the code

Length >>> 0. Len is a number and an integer. Len is an integer and len is an integer.

A few exceptions:

null >>> 0  / / 0

undefined >>> 0  / / 0

void(0) > > >0  / / 0

function a (){};  a >>> 0  / / 0[] > > >0  / / 0

var a = {}; a >>> 0  / / 0

123123 >>> 0  / / 123123

45.2 >>> 0  / / 45

0 >>> 0  / / 0

-0 >>> 0  / / 0

-1 >>> 0  / / 4294967295

-1212 >>> 0  / / 4294966084
Copy the code

The overall implementation is not that difficult, but it is important to note that in is used for prototype chain lookup. At the same time, if not found, do not process, can effectively deal with the sparse array.

Finally give you V8 source code, reference to the source code check, in fact, is very complete.

function ArrayMap(f, receiver) {
  CHECK_OBJECT_COERCIBLE(this."Array.prototype.map");

  // Pull out the length so that modifications to the length in the
  // loop will not affect the looping and side effects are visible.
  var array = TO_OBJECT(this);
  var length = TO_LENGTH(array.length);
  if(! IS_CALLABLE(f))throw %make_type_error(kCalledNonCallable, f);
  var result = ArraySpeciesCreate(array, length);
  for (var i = 0; i < length; i++) {
    if (i in array) {
      varelement = array[i]; %CreateDataProperty(result, i, %_Call(f, receiver, element, i, array)); }}return result;
}
Copy the code

We can see that. The key in array operation is used to determine whether the current value is empty. There is no key, of course, empty.

We can’t use var arrMap to initialize a new array to be returned. Discovery is initialized by new Array(len)

So we implement the map method this way to optimize it this way


Array.prototype.MyMap = function(fn, context){

    var arr = Array.prototype.slice.call(this);;
    var mapArr = new Array(this.length);
    
    for (var i = 0; i < arr.length; i++ ){
     if (i in arr) {
        mapArr.push(fn.call(context, arr[i], i, this)); }}return mapArr;
  }

Copy the code

Hey hey! I feel like THE next time I get this question, I can play pussy

Why can key in Array determine if the current item is empty?

This involves the general and sort properties of an object

Since I’ve written about these two things in the past, I won’t go into them again. You can click on this article, which uses an interview question to explain general properties and sorting properties

For in and for of the difference in detail and for in the output order

We can see this picture in the article. You can see that 2 in bar is false because the 2 key is not on the element at all.

Boy, you can brag to the interviewer again!

The last

If you have any questions, please feel free to discuss them in the comments section and join my front-end technology group for discussion. Search “front end sunshine” public account, reply to add group!

Refer to the article

  • Handwritten array map method? http://47.98.159.95/my_blog/blogs/javascript/js-array/006.html

  • Did you do this JS pen test right? Juejin. Cn/post / 684490…