JS变量和类型全面解析

原文作者:ConardLi大佬导读变量和类型是学习JavaScript最先接触到的东西,但是往往看起来最简单的东西往往还隐藏着很多你不了解、或者容易犯错的知识,比如下面几个问题: JavaScript中的变量在内存中的具体存储形式是什么?0.1+0.2为什么不等于0.3?发生小数计算错误的具体原因是什么?Symbol的特点,以及实际应用场景是什么?[] == ![]、[undefined] == false为什么等于true?代码中何时会发生隐式类型转换?转换的规则是什么?如何精确的判断变量的类型?如果你还不能很好的解答上面的问题,那说明你还没有完全掌握这部分的知识,那么请好好阅读下面的文章吧。 本文从底层原理到实际应用详细介绍了JavaScript中的变量和类型相关知识。 一、JavaScript数据类型ECMAScript标准规定了7种数据类型,其把这7种数据类型又分为两种:原始类型和对象类型。 原始类型 Null:只包含一个值:nullUndefined:只包含一个值:undefinedBoolean:包含两个值:true和falseNumber:整数或浮点数,还有一些特殊值(-Infinity、+Infinity、NaN)String:一串表示文本值的字符序列Symbol:一种实例是唯一且不可改变的数据类型(在es10中加入了第七种原始类型BigInt,现已被最新Chrome支持) 对象类型 Object:自己分一类丝毫不过分,除了常用的Object,Array、Function等都属于特殊的对象二、为什么区分原始类型和对象类型2.1 不可变性上面所提到的原始类型,在ECMAScript标准中,它们被定义为primitive values,即原始值,代表值本身是不可被改变的。 以字符串为例,我们在调用操作字符串的方法时,没有任何方法是可以直接改变字符串的: var str = 'ConardLi';str.slice(1);str.substr(1);str.trim(1);str.toLowerCase(1);str[0] = 1;console.log(str); // ConardLi在上面的代码中我们对str调用了几个方法,无一例外,这些方法都在原字符串的基础上产生了一个新字符串,而非直接去改变str,这就印证了字符串的不可变性。 那么,当我们继续调用下面的代码: str += '6'console.log(str); // ConardLi6你会发现,str的值被改变了,这不就打脸了字符串的不可变性么?其实不然,我们从内存上来理解: 在JavaScript中,每一个变量在内存中都需要一个空间来存储。 内存空间又被分为两种,栈内存与堆内存。 栈内存: 存储的值大小固定空间较小可以直接操作其保存的变量,运行效率高由系统自动分配存储空间JavaScript中的原始类型的值被直接存储在栈中,在变量定义时,栈就为其分配好了内存空间。 由于栈中的内存空间的大小是固定的,那么注定了存储在栈中的变量就是不可变的。 在上面的代码中,我们执行了str += '6'的操作,实际上是在栈中又开辟了一块内存空间用于存储'ConardLi6',然后将变量str指向这块空间,所以这并不违背不可变性的特点。 2.2 引用类型堆内存: 存储的值大小不定,可动态调整空间较大,运行效率低无法直接操作其内部存储,使用引用地址读取通过代码进行分配空间相对于上面具有不可变性的原始类型,我习惯把对象称为引用类型,引用类型的值实际存储在堆内存中,它在栈中只存储了一个固定长度的地址,这个地址指向堆内存中的值。 var obj1 = {name:"ConardLi"}var obj2 = {age:18}var obj3 = function(){...}var obj4 = [1,2,3,4,5,6,7,8,9] 由于内存是有限的,这些变量不可能一直在内存中占用资源,这里推荐下这篇文章JavaScript中的垃圾回收和内存泄漏,这里告诉你JavaScript是如何进行垃圾回收以及可能会发生内存泄漏的一些场景。当然,引用类型就不再具有不可变性了,我们可以轻易的改变它们: obj1.name = "ConardLi6";obj2.age = 19;obj4.length = 0;console.log(obj1); //{name:"ConardLi6"}console.log(obj2); // {age:19}console.log(obj4); // []以数组为例,它的很多方法都可以改变它自身。 ...

November 2, 2019 · 5 min · jiezi

JavaScript 中的强制类型转换

翻译:疯狂的技术宅原文:https://www.valentinog.com/bl…本文首发微信公众号:前端先锋欢迎关注,每天都给你推送新鲜的前端技术文章JavaScript 原语JavaScript 建立在一系列基本单元之上。你应该对其中的一些已经很熟悉了,比如字符串和数字:var greet = “Hello”;var year = 89;字符串和数字是该语言所谓“原语”的一部分。完整清单是:StringNumberBooleanNullUndefinedObjectSymbol (ES6中添加,此处不予介绍)布尔值用来表示可能是真或假的值。 null 是故意不去赋值,它通常被分配给一个变量,用于表示绑定完毕,稍后将填充有意义的内容。var maybe = null;然后才是 undefined,这意味着变量仍然没有被附加上:var name;console.log(name)undefinednull 和 undefined 看起来非常相似,但它们是两个截然不同的实体,很多开发人员仍然不确定应该使用哪个。如果要判断 JavaScript 实例的类型,可以用 typeof 运算符。让我们用字符串尝试一下:typeof “alex”> “string"还有数字:typeof 9> “number"用于布尔值:typeof false> “boolean"undefined:typeof undefined> “undefined"还有 null:typeof null> “object"结果令人惊讶! null 看起来像一个对象,但实际上它是 JavaScript 中的一个历史性错误,自语言诞生就躺在那里了。 由于这些问题,JavaScript 一直声名狼借。但这仅仅是个开始。陌生的事情在 JavaScript 中,在两种类型之间进行转换时有一些奇怪的规则。让我给你一些背景信息。先用 Python 举一个例子。 在 Python 中执行以下指令:‘hello’ + 89会给你一个明确的错误:TypeError: can only concatenate str (not “int”) to str而在 JavaScript 中,只有天空才是你的极限:‘hello’ + 89事实上给出:“hello89"如果我们尝试向字符串添加数组,看起来会更加奇怪:‘hello’ + []将会得到1. ‘hello’还有1. ‘hello’ + [89]会给你一个惊喜:1. “hello89"看起来这种转换背后存在某种逻辑。它甚至适用于存在更多元素的 array :1. ‘hello’ + [89, 150.156, ‘mike’]得到:1. “hello89,150.156,mike"这两行 JavaScript 足以让 Java 程序员逃离。但是这种行为在 JavaScript 中 100% 是又意义的。因此这种隐式转换,也称为强制类型转换是非常值得探索的。当一个数字变成一个字符串一些编程语言有一个叫做类型转换的概念,这意味着:如果我想把一个数字或实例转换为另一种类型,那么我必须使显式转换。它也适用于 JavaScript。请看以下示例:var greet = “Hello”;var year = 89;如果我想明确的进行转换,可以在代码中表明意图:var greet = “Hello”;var year = 89;var yearString = year.toString()要么这样做:var greet = “Hello”;var year = 89;var yearString = String(year)然后我可以连接两个变量:greet + yearString;但是在 JavaScript 中有一种被称为隐式转换的微妙机制,由 JavaScript 引擎提供。该语言不会阻止我们对数字和字符串进行加法操作:‘hello’ + 89会得到:“hello89"但是这种转换背后的逻辑是什么?你可能会惊讶地发现 JavaScript 中的加法运算符 + 会自动将两个操作数中的任何一个都转换为字符串,如果其中至少有一个是字符串的话!你会发现更令人惊讶的是,这条规则在ECMAScript规范中一脉相承。 第11.6.1节 定义了加法运算符的行为,我在这里为你做了总结:如果 x 是 String 或 y 是String,则返回 ToString(x),然后返回ToString(y)这个花招只适用于数字吗?并不是。 数组和对象也会受到相同的转换:‘hello’ + [89, 150.156, ‘mike’]会得到:“hello89,150.156,mike"那么下面的代码会得到怎样的结果:‘hello’ + { name: “Jacopo” }要找到答案,你可以通过将对象转换为字符串来进行快速测试:String({ name: “Jacopo” })将会得到:"[object Object]“所以我有一种感觉:1. ‘hello’ + { name: “Jacopo” }会得到:1. “hello[object Object]“打住!这又是什么?JavaScript 中 [object Object] 的含义是什么?“[object Object]” 是最常见的 JavaScript “怪癖”之一。几乎每个 JavaScript 实例都有一个名为 toString() 的方法,有些方法是由 Object.prototype.toString 提供的。某些类型(如数组)实现了 toString() 的自定义版本,以便在调用方法时将值转换为字符串。例如 Array.prototype.toString 会覆盖 Object.toString()(也称为 method shadowing)。但是当你在普通的 JavaScript 对象上调用 toString() 时,引擎会给出“[object Object]”,因为 Object.toString() 的默认行为是由实体类型(在这种情况下为Object)返回字符串 object 。现在让我们把注意力集中在 JavaScript 比较运算符上,它们与算术运算符一样奇怪。等于还是不等于?JavaScript 中有两个主要的比较运算符。第一个我们称之为“弱比较”。这是抽象比较运算符(双等号):==。另一个是“强比较”,可以通过三等号进行识别:=== 也称为严格比较运算符。它们两者的行为方式完全不同。来看一些例子。首先,如果我们将两个字符串与两个运算符进行比较,我们得到相同的结果"hello” == “hello”> true"hello” === “hello”> true看上去一切都还好。现在尝试比较两种不同的类型,数字和字符串。首先是“强比较”:1. “1” === 12. false这说得通!字符串“1”与数字1是不同的。但是“弱比较”会发生什么?1. “1” == 12. true居然是true!它没有任何意义,除非这种行为与我们之前看到的隐式转换有关。如果适用相同的规则怎么办?没错! ECMAScript spec 再次罢工。结果抽象比较运算符在比较它们之前在类型之间进行自动转换。这是规范的摘要:比较 x == y 执行如下:如果 x 是 String 且 y 是Number,则返回比较结果 ToNumber(x)== y规范说:如果第一个操作数是一个字符串,第二个操作数是一个数字,那么将第一个操作数转换为数字。有趣。JavaScript 规范充满了这个疯狂的规则,我强烈鼓励大家对它深入挖掘。在此期间除非你有充分的理由否则在 JavaScript 代码中避免使用抽象比较运算符。你以后会感谢自己的。那么“强势比较”怎么样?规范中的说 严格相等比较在把值与三等 === 进行比较之前没有进行自动转换。在代码中使用严格相等比较可以避免愚蠢的 bug。总结JavaScript 中有七个构建块,即 String,Number,Boolean,Null,Undefined,Object 和 Symbol。这些类型被称为基元。JavaScript 开发人员可以使用算术和比较运算符来操作这些类型。但是我们要特别注意加法运算符 + 和抽象比较运算符 ==,它本质上倾向于在类型之间进行转换。JavaScript 中的隐式转换称为强制类型转换,并在 ECMAScript 规范中定义。无论什么时候你的代码都要使用严格的比较运算符 === 而不是 ==。作为最佳实践,当你打算在两种类型之间进行转换时,请务必明确操作。JavaScript 有一堆内置对象,它们反映了原始类型:String,Number,Boolean。这些内置类型可用于在不同类型之间进行显式转换。本文首发微信公众号:前端先锋欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章欢迎继续阅读本专栏其它高赞文章:12个令人惊叹的CSS实验项目世界顶级公司的前端面试都问些什么CSS Flexbox 可视化手册过节很无聊?还是用 JavaScript 写一个脑力小游戏吧!从设计者的角度看 ReactCSS粘性定位是怎样工作的一步步教你用HTML5 SVG实现动画效果程序员30岁前月薪达不到30K,该何去何从7个开放式的前端面试题React 教程:快速上手指南 ...

April 11, 2019 · 2 min · jiezi

【前端面试】变量和类型计算

1.题目1.JS使用typeof能得到哪些类型=== 和 == 的选择JS中有哪些内置函数JS变量按存储方式分为哪些类型,并描述其特点如何理解JSON2.知识点2.1 值类型和引用类型值类型(boolean,string,number,null,undefined)var a = 10;var b = a;a = 20;console.log(b); //10引用类型(对象,数组,函数)var a = {x:10}var b = a;a.x = 20;console.log(b); //20值类型直接把值存储在堆中,把a赋值给b在内存中是又给b开辟了一块新的空间,存储了同样的值。引用类型分两块存储,先在堆中存储一个实际的值,再在栈中存储一个堆中值的引用地址,指向堆中的对象。把a赋值给b是在栈中重新开辟一块空间存储的还是相同对象的引用地址,a和b存储的地址相同,指向的对象也相同。当对象值发生改变时,两者会同时改变。引用类型的值一般都比较大,采用此种存储方式可以节省内存空间。2.2 typeof运算符typeof ‘abc’ //stringtypeof 123 //numbertypeof true //booleantypeof undefined //undefinedtypeof null //objecttypeof {a:10} //objecttypeof [1,2,3] //objecttypeof console.log() //function2.3 类型转换强类型转换:通过String(),Number(),Boolean(),parseInt()函数强制转换可能发生隐式类型转换的场景字符串拼接使用==if语句逻辑循环一、首先看双等号前后有没有NaN,如果存在NaN,一律返回false。二、再看双等号前后有没有布尔,有布尔就将布尔转换为数字。(false是0,true是1)三、接着看双等号前后有没有字符串, 有三种情况:1、对方是对象,对象使用toString()或者valueOf()进行转换;2、对方是数字,字符串转数字;(前面已经举例)3、对方是字符串,直接比较;4、其他返回false四、如果是数字,对方是对象,对象取valueOf()或者toString()进行比较, 其他一律返回false五、null, undefined不会进行类型转换, 但它们俩相等上面的转换顺序一定要牢记,面试的时候,经常会出现类型的问题。‘100’==100 //转换成字符串’’==0 //转换成falseundefined == null; // true1 == true; // true2 == true; // false0 == false; // true0 == ’ ‘; // trueNaN == NaN; // false[] == false; // true[] == ![]; // true//在if中转换成false的:nullundefined’‘NaN0false10 && 0 //0 10转换成true’’ || ‘abc’ //abc ‘‘转换成false!window.abc //true 2.4 null和undefined的区别null:是被赋值过的对象,刻意把一个对象赋值为null,故意表示其为空,不应有值,所以对象为null是正常的,typeof null 返回 ‘object’ ,null可以转换为0undefined 表示“缺少值”,即此处应有一个值,但还没有定义;转为数值时为NaN(非数字值的特殊值) typeof undefined 返回 ‘undefined'3.题目解答3.1 JS使用typeof能得到哪些类型typeof ‘abc’ //stringtypeof 123 //numbertypeof true //booleantypeof undefined //undefinedtypeof null //objecttypeof {a:10} //objecttypeof [1,2,3] //objecttypeof console.log() //function3.2 === 和 == 的选择jquery源码中的写法:除了以下方式其他全部使用 ===if(obj.a == null){ //相当于 obj.a === undefined || obj.a === null}3.3 JS中有哪些内置函数单纯作为语言来说,不考虑node和浏览器webObjectArrayBooleanNumberStringFunctionDateRegExpError内置对象:Math,JSON3.4 JS变量按存储方式分为哪些类型,并描述其特点值类型何引用类型3.5 如何理解JSONJSON是JS中的一个内置对象区别JS对象 {x:10}JSON对象 {‘x’:10}JSON串 “{‘x’:10}”//将JS对象转换成json串JSON.stringify({x:10});//将json字符串转换成json对象JSON.parse("{‘x’:10}");3.6 严格模式目的消除Javascript语法的一些不合理、不严谨之处,减少一些怪异行为; 消除代码运行的一些不安全之处,保证代码运行的安全;提高编译器效率,增加运行速度;为未来新版本的Javascript做好铺垫。特性 “use strict”;可以选择放在一个函数中或自定义作用域中。禁止this指向全局对象 function f(){ return !this; } // 返回false,因为"this"指向全局对象,"!this"就是false function f(){ “use strict”; return !this; } // 返回true,因为严格模式下,this的值为undefined,所以"!this"为true。创设eval作用域正常模式下,Javascript语言有两种变量作用域(scope):全局作用域和函数作用域。严格模式创设了第三种作用域:eval作用域。正常模式下,eval语句的作用域,取决于它处于全局作用域,还是处于函数作用域。严格模式下,eval语句本身就是一个作用域,不再能够生成全局变量了,它所生成的变量只能用于eval内部。 “use strict”; var x = 2; console.info(eval(“var x = 5; x”)); // 5 console.info(x); // 2全局变量显式声明 v = 1; // 报错,v未声明 for(i = 0; i < 2; i++) { // 报错,i未声明 }禁止删除变量严格模式下无法删除变量。只有configurable设置为true的对象属性,才能被删除。 “use strict”; var x; delete x; // 语法错误 var o = Object.create(null, {‘x’: { value: 1, configurable: true }}); delete o.x; // 删除成功函数不能有重名的参数保留字为了向将来Javascript的新版本过渡,严格模式新增了一些保留字:implements, interface, let, package, private, protected, public, static, yield。使用这些词作为变量名将会报错。 function package(protected) { // 语法错误 “use strict”; var implements; // 语法错误 }3.7 eval1.没有必须使用的应用场景2.不容易调试,可读性不好3.在旧的浏览器中如果你使用了eval,性能会下降10倍。4.容易xss ...

January 3, 2019 · 2 min · jiezi