JavaScript最不了解的5件事

大家好! 9月底,全新的“ Fullstack JavaScript Developer”课程将在OTUS开始。 鉴于即将开始上课,我们希望与您分享专门为该课程的学生准备的作者文章。

文章作者: Pavel Yakupov



预览版 我想立即指出,本文将研究“忍者”所熟悉的主题,并且该文章的目的更多是使初学者更好地理解该语言的某些细微差别,并且不要迷失在面试过程中经常要完成的任务中-毕竟,诸如此类任务与实际的开发无关,而提供任务的人通常以这种方式尝试了解您对JavaScript的了解程度。





参考内存类型


数据到底如何存储在JavaScript中? 许多编程课程都从经典开始讲解:变量是一种“盒子”,我们在其中存储一些数据。 对于动态类型的语言,似乎似乎无关紧要:解释器将“吞噬”任何数据类型,并在必要时动态更改类型,并且您不应该考虑变量的类型及其处理方式。 当然哪一个是错误的,因此,我们今天将开始讨论那些经常被忽略的功能:变量如何以原始形式(副本)或链接的形式存储在JavaScript中。

我们将立即列出可以以原始形式存储的变量类型:这些变量是booleannullundefinedNumberStringSymbolBigInt 。 当我们遇到单独声明的具有这种数据类型的变量时,我们必须记住,在初始初始化期间,它们创建了一个存储单元-并且可以按值分配,复制,传输和返回它们。

JavaScript的其余部分依赖于引用的内存区域。 为什么需要它们? 该语言的创建者试图创建一种可以尽可能经济地使用内存的语言(在当时,这绝对不是新鲜事物)。 为了说明,假设您需要记住三个新工作同事的名字-完全是新名字,并且为了增强比较效果,您来自印度或中国的新同事会给您提供不同寻常的名字。 现在想象一下,您的同事就像您和您在学校的两个最好的朋友一样被称呼。 在什么情况下更容易记住? 在这里,人的记忆和计算机的工作类似。 以下是一些具体示例:

 let x = 15; //  x x = 17;//   console.log(x)//    //     let obj = {x:1, y:2} //   let obj1 = obj; //  obj  obj1 obj1.x = 2; //    "" console.log(obj1.x); //  ,   console.log(obj.x) //      obj.x ? 

因此,如果您在面试中遇到类似的任务,请尝试立即了解您面前的数据类型-数据来自何处以及如何获取值,作为原始类型还是作为参考。



上下文工作


为了准确了解上下文在JS中的工作方式,您需要研究以下几点:

  1. 全球/本地可见性级别。
  2. 在全局/局部范围内初始化变量时上下文的差异。
  3. 箭头功能。

曾几何时,回到ES5,一切都非常简单:只有一个使用var的变量声明,当在程序流中声明该变量时,它被认为是全局的(这意味着该变量作为属性分配给了一个全局对象,例如windowglobal )。 然后letconst进入了场景,它们的行为有些不同:它们没有分配给全局对象,而是以块范围为重点以不同的方式存储在内存中。 现在已经认为var已过时,因为使用var可能会导致全局范围的阻塞,并且let外观更具可预测性。

1.因此,为了理解,有必要清楚地了解JavaScript(范围)的范围。 如果使用let指令在全局范围内声明了变量,则不会将其分配给window对象,而是将其全局保存。

让我们继续进行面试时最常涉及到上下文问题的任务。

 //:     ? let x = 15; function foo(){ let x = 13; return x; } console.log(x)// 15     foo(); console.log(x)//     x = foo(); console.log(x)//    return   ,    

2.同时,并非所有初学者都知道JavaScript解释器如何读取代码:实际上,它会读取两次,第一次是读取声明为Function Declaration的函数的代码(并准备在第二次实际读取并执行时执行它们) ) 另一个小技巧与varlet :第一次读取带有var指令的变量时,将其设置为undefined 。 但是使用let它根本不可能过早调用:

 console.log(x); console.log(y) var x = 42; let y = 38; //   ? //   undefined  error! 

3. ES6中出现箭头功能迅速流行起来-程序员在Node.js (由于引擎的快速更新)和React (由于库的功能以及不可避免地使用Babel)中迅速采用了它们。 关于上下文,箭头函数遵守以下规则:它们不绑定到this 。 我们举例说明:

 var x = 4; var y = 4; function mult(){ return this.x * this.y; } let foo = mult.bind(this); console.log(foo()); let muliply = ()=>x*y; console.log(muliply()); /*           x  y     let,  function declaration       */ 



数据类型及其适用对象


让我们马上说:数组本质上是一个对象,而在JavaScript中,这并不是对象的第一个变体-Map,WeakSet,Set和集合确认了这一点。

因此,数组是一个对象,它与JS中常规对象的区别在于,首先,由于优化了索引,因此工作速度更高;其次,它继承了Array.prototype,后者提供了更多的方法,这就是为什么老大哥» Object.prototype

 console.log(typeof({})) console.log(typeof([])) console.log(typeof(new Set)) console.log(typeof(new Map)) //         

数据类型中奇数队列中的下一个为null 。 如果您问JavaScript,什么类型的数据为null,我们会得到一个明确的答案。 但是,这里没有一些技巧:

 let x = null; console.log(typeof(x)); //! , null   objet, ? console.log(x instanceof Object.prototype.constructor); //false //   !      ) 

值得记住的是, null是一种特殊的数据类型-尽管前面示例的开头严格指出了另一个。 为了更好地理解为什么将这种特殊类型添加到语言中,在我看来,值得探索C ++或C#语法的基础。

当然,在访谈中经常会遇到这样的一项任务,其特点与动态类型有关:

 console.log(null==undefined);//true console.log(null===undefined);//     false 

在JS中进行比较时,与类型转换有关的技巧很多;我们实际上无法将所有技巧都带到这里。 我们建议参考“什么是JavaScript”



在开发过程中语言中留下的不合逻辑的特征


增加线。 实际上,带数字的字符串添加不能归因于语言开发中的错误,但是,在JavaScript的上下文中,这导致了众所周知的示例,这些示例被认为不够逻辑:

codepen.io/pen/?editors=0011

 let x = 15; let y = "15"; console.log(x+y);//  "" console.log(xy); //        

加号仅用数字加行的事实相对不合逻辑,但您只需要记住这一点即可。 这可能特别不寻常,因为在Web开发中流行和广泛使用的其他两种解释语言-PHP和Python-不会在添加字符串和数字的情况下抛出此类技巧,并且在此类操作中表现得更加可预测。

类似的例子鲜为人知,例如,NaN:

  console.log(NaN == NaN); //false console.log(NaN > NaN); //false console.log(NaN < NaN); //false …   ... ,      NaN? console.log(typeof(NaN)); // number 

例如,如果您错误地配置了类型检查,NaN通常会带来不愉快的惊喜。

使用0.1 +0.2的示例更为著名-因为此错误与IEEE 754格式有关,例如,在此类“数学” Python中也使用了该格式。

我们还提供了一个鲜为人知的带有Epsilon编号的bug,其原因与此相同:

 console.log(0.1+0.2)// 0.30000000000000004 console.log(Number.EPSILON);// 2.220446049250313e-16 console.log(Number.EPSILON + 2.1) // 2.1000000000000005 


还有一些稍微复杂的问题:

 Object.prototype.toString.call([])//    ? // ->  '[object Array]' Object.prototype.toString.call(new Date) //     Date? // -> '[object Date]'    



事件处理阶段


许多初学者不了解浏览器事件。 甚至不熟悉的也是浏览器事件工作的最基本原理-拦截,上升和默认事件。 从初学者的角度来看,最神秘的事情是事件的出现,毫无疑问,该事件一开始是有道理的,这就引发了疑问。 弹出窗口的工作方式如下:单击嵌套的DOM元素时,如果在父级上也安装了具有此类事件的处理程序,则不仅会在该事件上触发该事件,还会在父级上触发该事件。
如果发生事件,我们可能需要取消它。

 //    ,      function MouseOn(e){ this.style.color = "red"; e.stopPropagation(); //    } 

另外,对于初学者来说,取消默认情况下发生的事件通常是一个问题。 这在开发表单时尤为重要-例如,因为表单验证需要同时在客户端和服务器端进行:

codepen.io/isakura313/pen/GRKMdaR?editors=0010

 document.querySelector(".button-form").addEventListener( 'click', function(e){ e.preventDefault(); console.log('    . ,  ') } ) 

取消事件的出现也可能带来一些麻烦:例如,您可以创建一个所谓的“死区”,在该死区中,必需的东西将不起作用-例如,某个元素“不太幸运”在附近的事件。

谢谢大家的关注! 以下是一些有用的链接,您可以从中获得许多有用的信息:




仅此而已。 我们将在9月12日举行的免费网络研讨会上等您。

Source: https://habr.com/ru/post/zh-CN466873/


All Articles