草庐IT

记录--一道js笔试题, 刷新了我对map方法函数的认知

林恒 2023-03-28 原文

这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助

背景

昨天在看一道笔试题的时候本以为很简单,但是结果不是我想象的那样,直接上笔试题。

const array = new Array(5).map((item) => {
  return item = {
    name: '1'
  }
});
console.log(array);
// 请写出输出结果

我想象的答案[{name: '1'}, {name: '1'}, {name: '1'}, {name: '1'}, {name: '1'}];

实际的答案:[empty × 5]

为什么会这样了?

猜想1

我第一个想到的是new Array(5)生成的数组是[undefined, undefined, undefined, undefined, undefined]

const array = [undefined, undefined, undefined, undefined, undefined];
const newArr = array.map((item) => {
  return item = {
     name: '1'
   }  
});
console.log(newArr);
// 结果是[{name: '1'}, {name: '1'}, {name: '1'}, {name: '1'}, {name: '1'}];

猜想1错误

猜想2

new Array(5)生成的数组在每一项都没有值,意思就是生成了[,,,,,]一个这样的数组。

const array = [,,,,,];
const newArr = array.map((item) => {
  return item = {
     name: '1'
   }  
});
console.log(newArr);
// 结果是[empty × 5];

猜想2正确(这里大喊自己牛逼)

为什么

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.

map依次为数组中的每个元素调用一次提供的callback函数,然后根据结果构造一个新的数组。-----仅对已分配值(包括)的数组索引进行调用----。 map函数的回调函数只会被赋过值的项调用。new Array(1) 和 [undefined]不一样。new Array(1)没有为数组中的项赋过值,而[undefined]为数组中的项赋了一个undefined值。 总结

new Array(5)产生的数组是一个没有为数组中的项赋过值的数组。 map仅对已分配值(包括)的数组索引进行callback调用。

对map方法的深入思考

const array = new Array(5)

可以理解成

const array = []
array.length = 5

也可以理解

const array = [,,,,,]

但是这里让我产生一个疑问:

以前我学习 手写map方法的时候

你百度一下,会发现也基本上很多人都是这样手写的:

Array.prototype.MyMap = function(fn, context){
  var arr = Array.prototype.slice.call(this);//由于是ES5所以就不用...展开符了
  var mappedArr = [];
  for (var i = 0; i < arr.length; i++ ){
    mappedArr.push(fn.call(context, arr[i], i, this));
  }
  return mappedArr;
}

这样似乎没啥问题,但是 这个map的手写源码 根本解释不通上面返回[empty × 5]的现象。

我们可以看一下返回结果:

如图所示,我的天,这不是坑人吗!

那真正的map方法应该死怎样实现的呢?

我猜想它应该会去遍历每一项,并且判断当前项是否为empty,是的话就不执行里面的操作,里面指的是for循环里面的代码

好的,问题来了,怎么判断当前项是empty?确实难倒我了,为此,我们去看下map的真正源码吧!

依照 ecma262 草案,实现的map的规范如下:

下面根据草案的规定一步步来模拟实现map函数:

Array.prototype.map = function(callbackFn, thisArg) {
  // 处理数组类型异常
  if (this === null || this === undefined) {
    throw new TypeError("Cannot read property 'map' of null or undefined");
  }
  // 处理回调类型异常
  if (Object.prototype.toString.call(callbackfn) != "[object Function]") {
    throw new TypeError(callbackfn + ' is not a function')
  }
  // 草案中提到要先转换为对象
  let O = Object(this);
  let T = thisArg;

  
  let len = O.length >>> 0;
  let A = new Array(len);
  for(let k = 0; k < len; k++) {
    // 还记得原型链那一节提到的 in 吗?in 表示在原型链查找
    // 如果用 hasOwnProperty 是有问题的,它只能找私有属性
    if (k in O) {
      let kValue = O[k];
      // 依次传入this, 当前项,当前索引,整个数组
      let mappedValue = callbackfn.call(T, KValue, k, O);
      A[k] = mappedValue;
    }
  }
  return A;
``}

这里解释一下, length >>> 0, 字面意思是指"右移 0 位",但实际上是把前面的空位用0填充,这里的作用是保证len为数字且为整数。

举几个特例:

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

总体实现起来并没那么难,需要注意的就是使用 in 来进行原型链查找。同时,如果没有找到就不处理,能有效处理稀疏数组的情况。

最后给大家奉上V8源码,参照源码检查一下,其实还是实现得很完整了。

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) {
      var element = array[i];
      %CreateDataProperty(result, i, %_Call(f, receiver, element, i, array));
    }
  }
  return result;
}

 

 

我们可以看到。其实就是用key in array 的操作判断当前是否为empty。 可不是嘛,key都没有,当然是empty了。

另外我们不能用var arrMap的方式去初始化一个即将返回的新数组,看源码。发现是要通过new Array(len)的方式去初始化

 

 所以我们这样实现map方法 可以这样去优化

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;
  }

 

 

嘿嘿!感觉下一次面试遇到这个问题,我可以装一波逼了

为什么 key in Array 可以判断 当前项是否为 empty呢?

这个就要涉及到 一个对象的常规属性排序属性

由于以前我已经写过文章来解释过 这两个东西,我就不再赘述了,大家可以点击这篇文章进去看看,里面利用一道面试题讲解了常规属性排序属性

百度前端面试题:for in 和 for of的区别详解以及为for in的输出顺序

我们可以看到文章里面这张图。是不是可以发现 2 in bar 是false的 因为 这个 为2的key 根本就不在 element上。

好家伙,又可以跟面试官吹牛逼了!

本文转载于:

https://juejin.cn/post/6921623546636550158

如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

 

有关记录--一道js笔试题, 刷新了我对map方法函数的认知的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  3. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  4. ruby - Facter::Util::Uptime:Module 的未定义方法 get_uptime (NoMethodError) - 2

    我正在尝试设置一个puppet节点,但ruby​​gems似乎不正常。如果我通过它自己的二进制文件(/usr/lib/ruby/gems/1.8/gems/facter-1.5.8/bin/facter)在cli上运行facter,它工作正常,但如果我通过由ruby​​gems(/usr/bin/facter)安装的二进制文件,它抛出:/usr/lib/ruby/1.8/facter/uptime.rb:11:undefinedmethod`get_uptime'forFacter::Util::Uptime:Module(NoMethodError)from/usr/lib/ruby

  5. Ruby 方法() 方法 - 2

    我想了解Ruby方法methods()是如何工作的。我尝试使用“ruby方法”在Google上搜索,但这不是我需要的。我也看过ruby​​-doc.org,但我没有找到这种方法。你能详细解释一下它是如何工作的或者给我一个链接吗?更新我用methods()方法做了实验,得到了这样的结果:'labrat'代码classFirstdeffirst_instance_mymethodenddefself.first_class_mymethodendendclassSecond使用类#returnsavailablemethodslistforclassandancestorsputsSeco

  6. ruby-on-rails - Rails 3.2.1 中 ActionMailer 中的未定义方法 'default_content_type=' - 2

    我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer

  7. ruby - Highline 询问方法不会使用同一行 - 2

    设置:狂欢ruby1.9.2高线(1.6.13)描述:我已经相当习惯在其他一些项目中使用highline,但已经有几个月没有使用它了。现在,在Ruby1.9.2上全新安装时,它似乎不允许在同一行回答提示。所以以前我会看到类似的东西:require"highline/import"ask"Whatisyourfavoritecolor?"并得到:Whatisyourfavoritecolor?|现在我看到类似的东西:Whatisyourfavoritecolor?|竖线(|)符号是我的终端光标。知道为什么会发生这种变化吗? 最佳答案

  8. ruby - 主要 :Object when running build from sublime 的未定义方法 `require_relative' - 2

    我已经从我的命令行中获得了一切,所以我可以运行rubymyfile并且它可以正常工作。但是当我尝试从sublime中运行它时,我得到了undefinedmethod`require_relative'formain:Object有人知道我的sublime设置中缺少什么吗?我正在使用OSX并安装了rvm。 最佳答案 或者,您可以只使用“require”,它应该可以正常工作。我认为“require_relative”仅适用于ruby​​1.9+ 关于ruby-主要:Objectwhenrun

  9. ruby - 在没有 sass 引擎的情况下使用 sass 颜色函数 - 2

    我想在一个没有Sass引擎的类中使用Sass颜色函数。我已经在项目中使用了sassgem,所以我认为搭载会像以下一样简单:classRectangleincludeSass::Script::FunctionsdefcolorSass::Script::Color.new([0x82,0x39,0x06])enddefrender#hamlengineexecutedwithcontextofself#sothatwithintemlateicouldcall#%stop{offset:'0%',stop:{color:lighten(color)}}endend更新:参见上面的#re

  10. ruby - 多个属性的 update_column 方法 - 2

    我有一个具有一些属性的模型:attr1、attr2和attr3。我需要在不执行回调和验证的情况下更新此属性。我找到了update_column方法,但我想同时更新三个属性。我需要这样的东西:update_columns({attr1:val1,attr2:val2,attr3:val3})代替update_column(attr1,val1)update_column(attr2,val2)update_column(attr3,val3) 最佳答案 您可以使用update_columns(attr1:val1,attr2:val2

随机推荐