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

笔记js判断变量类型

平时业务代码写多了,学习又懈怠,对js的基本功能都不太熟悉了,面试答不上来,哭唧唧1.使用typeof判断的是基本数据类型。 {}object[]objectfunction(){}function'1'stringnullobjectundefined/未定义变量undefined1/NaNnumbertruebooleanSymbol()symbol2.使用instanceof操作符主要基于object类型的判断。 假设基于React.Component创建一个类 class Board extends React.Component { //... render() { console.log(this instanceof Board);// true console.log(this instanceof React.Component);// true console.log(React.Component.prototype.isPrototypeOf(this));// true console.log(this instanceof Object);// true console.log(this instanceof Game);// false } //...}class Game extends React.Component { // ...}5个log分别是true true true true false。基本可以看出instanceof与原型链有关,MDN上的描述是The instanceof operator tests whether the prototype property of a constructor appears anywhere in the prototype chain of an object. 是否这个构造函数的prototype属性出现在这个对象的原型链中。 如果改动了React.Component.prototype,就会出现 console.log(this instanceof React.Component); // false 其他???? ...

July 12, 2019 · 1 min · jiezi

让前端面试不在难(三)

今天聊一下clone这个前端面试高频问题,由此引出typeof、instanceof、Object.prototype.toString这些javascript Api。下面我们先详细的聊一下,完了解决下面试官的问题。typeoftypeof能获取一个变量或表达式的类型。原始类型BooleanNullUndefinedStringSymbolNumberSymbol和 引用类型 Object,Function下面看一些栗子 //基础类型也可以说非引用类型 let str = ‘hello word!’ console.log(typeof str) //string let num = 12 console.log(typeof num) //number let udf = undefined console.log(typeof udf) //undefined let bl = true console.log(typeof bl) //boolean let nl = null console.log(nl) //null let sl = Symbol() console.log(typeof sl) //symbol //复合类型也可以说是引用类型, let obj = { a: 1 } console.log(typeof obj) //object let arr = [1, 2, 3] console.log(typeof arr) //object //函数也属于引用类型,不过typeof却能准确的返回类型 function fn() {} console.log(typeof fn) //function从以上的执行结果可以看出,typeof不能够准确分返回所有类型instenceof我们从instenceof的实现来了解下instenceof是干什么的 // 模拟instenceof实现 // 1、instenceof接收两个参数 // 2、返回true或false function myInstenceof(X, Y) { let L = X.proto let R = Y.prototype if (L !== R) { return false } return true } // test function Fn() { } let newFn = new Fn() console.log(newFn) console.log(new Fn()) console.log(myInstenceof(newFn, new Fn())) //true console.log(myInstenceof([], new Array())) //true console.log(myInstenceof([], new Object())) //true以上demo我们就能看出,instenceof也不够靠谱,模拟实现就是判断X的原型知否在Y的原型链上。数组之所以instenceof Object为true是因为 [].prototype->new Array->new Object->null上边说了typeof和instenceof其实就是想说这两个对于深度clone的实现来说不够严谨要不就是多层判断。Object.prototype.toString.call()接下里我们说个靠谱的 Object.prototype.toString.call(’’); // [object String] Object.prototype.toString.call(1); // [object Number] Object.prototype.toString.call(true); // [object Boolean] Object.prototype.toString.call(undefined); // [object Undefined] Object.prototype.toString.call(null); // [object Null] Object.prototype.toString.call(new Function()); // [object Function] Object.prototype.toString.call(new Date()); // [object Date] Object.prototype.toString.call([]); // [object Array] Object.prototype.toString.call(new RegExp()); // [object RegExp] Object.prototype.toString.call(new Error()); // [object Error] Object.prototype.toString.call(document); // [object HTMLDocument] Object.prototype.toString.call(window); //[object Window]靠谱!接下来我们就用Object.prototype.toString.call()来解答一下面试题 function clone(obj, o) { //Object.prototype.toString.call(obj)返回类似[Object Array] 利用slice来截取我们需要的字符串 let type = Object.prototype.toString.call(obj).slice(8, -1) // 如果是Object if (type === ‘Object’) { o = {} for (let k in obj) { o[k] = clone(obj[k]); } // 如果是对象 } else if (type === ‘Array’) { o = [] for (let i = 0; i < obj.length; i++) { o.push(clone(obj[i])); } } else { // 非引用类型 o = obj } return o } let obj1 = { a: [1, 2, 3, 4, 5, 6], b: { c: 2 }, d: 1, f: function() { return 1 } } let obj2 = clone(obj1) obj2.f = function() { return 2 } obj2.a = 1 console.log(obj1) console.log(obj2)测试打印结果,obj2的改变不会影响到obj1。欢迎吐槽! ...

January 29, 2019 · 2 min · jiezi

JavaScript类型判断

JS(ES6)中的基本数据类型:1.数值型(Number):包括整数、浮点数、2.布尔型(Boolean)、3.字符串型(String)、4.数组(Array)、5.空值(Null) 、6.未定义(Undefined),基本数据类型是按值访问的,因为可以直接操作保存在变量中的实际值。引用类型:Object 、Array 、Function 、Data,引用数据类型是保存在堆内存中的对象1.typeofvar a;console.log(“1:” + typeof a);var b = null;console.log(“2:” + typeof b);var c = undefined;console.log(“3:” + typeof c);var d = new Object;console.log(“4:” + typeof d);var e = function() {};console.log(“5:” + typeof e);var f = {};console.log(“6:” + typeof f);var g = ‘’;console.log(“7:” + typeof g);var h = [];console.log(“8:” + typeof h);var i = true;console.log(“9:” + typeof i);var j = 123;console.log(“10:” + typeof j);var k = NaN;console.log(“11:” + typeof k);var l = /^[-+]?\d+$/;console.log(“12:” + typeof l);打印结果如下总结:typeof对null、undefined、NaN、数组、正则、Object的类型都为object2.constructorconstructor 用于判断一个变量的原型,constructor 属性返回对创建此对象的数组函数的引用.当一个函数 F被定义时,JS引擎会为F添加 prototype 原型,然后再在 prototype上添加一个 constructor 属性,并让其指向 F 的引用,当执行 var f = new F() 时,F 被当成了构造函数,f 是F的实例对象,此时 F 原型上的 constructor 传递到了 f 上,因此 f.constructor === Fvar F = function(){}console.log(F.prototype);var f = new F();console.log(f.constructor===F) //true不难看出,F 利用原型对象上的 constructor 引用了自身,当 F 作为构造函数来创建对象时,原型上的 constructor 就被遗传到了新创建的对象上, 从原型链角度讲,构造函数 F 就是新对象的类型。这样做的意义是,让新对象在诞生以后,就具有可追溯的数据类型,也就是说对象的constructor属性指向他的构造函数所以内置对象在内部构建时阔以这样做出判断注:null 和 undefined 是无效的对象,因此是不会有 constructor 存在的,这两种类型的数据需要通过其他方式来判断constructor属性并非一定指向构造函数,他也是可以修改、变更的(当把F.prototype = {}改写后,会默认把constructor覆盖掉)instanceofinstanceof 运算符用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性及的构造函数有这些基础类型:String、Number、Boolean、Undefined、Null、Symbol(ES6引入了一种新的原始数据类型Symbol,表示独一无二的值);复杂类型:Array,Object;其他类型:Function、RegExp、Date。var obj = new Object()obj instanceof Object // true注意左侧必须是对象(object),如果不是,直接返回false,列如:var num = 1num instanceof Number // falsenum = new Number(1)num instanceof Number // true可以看出都是num,而且都是1,只是因为第一个不是对象,是基本类型,所以直接返回false,而第二个是封装成对象,所以true。这里要严格注意这个问题,有些说法是检测目标的__proto__与构造函数的prototype相同即返回true,这是不严谨的,检测的一定要是对象才行,如:基础类型var num = 1num.proto === Number.prototype // truenum instanceof Number // falsenum = new Number(1)num.proto === Number.prototype // truenum instanceof Number // truenum.proto === (new Number(1)).proto // true上面例子可以看出,1与new Number(1)几乎是一样的,只是区别在于是否封装成对象,所以instanceof的结果是不同的,string、boolean等,这些基础类型一样的。new String(1) // String {“1”}String(1) // “1"new String(1)与String(1)是不同的,new是封装成对象,而没有new的只是基础类型转换,还是基础类型其他基础类型一样的。复杂类型,比如数组与对象,甚至函数等,与基础类型不同。复杂类型var arr = []arr instanceof Array // truearr instanceof Object // trueArray.isArray(arr) // true复杂类型从字面量是直接生成构造函数的,所以不会像基本类型一样两种情况。但是上面那个问题,当然,基础类型也会有这个问题,就是与Object对比。没办法,Object在原型链的上层,所以都会返回true,如下:(new Number(1)) instanceof Object // true由于从下往上,比如你判断是Number,那就没必要判断是不是Object了,因为已经是Number了……其他类型var reg = new RegExp(//)reg instanceof RegExp // truereg instanceof Object // truevar date = new Date()date instanceof Date // truedate instanceof Object // true除了Function,都一样,具体Function如下:function A() {}var a = new A()a instanceof Function // falsea instanceof Object // trueA instanceof Function // true这里要注意,function A() {}相当于var A; A = function() {},然后分析:a是new出来,所以是经过构造,因此已经是对象,不再是函数,所以falsea是经过构造的对象,返回ture没问题A是个函数,这没什么问题{}.toString.call(obj)用法如下console.log({}.toString.call(1));console.log({}.toString.call(“11”));console.log({}.toString.call(/123/));console.log({}.toString.call({}));console.log({}.toString.call(function() {}));console.log({}.toString.call([]));console.log({}.toString.call(true));console.log({}.toString.call(new Date()));console.log({}.toString.call(new Error()));console.log({}.toString.call(null));console.log({}.toString.call(undefined));console.log(String(null));console.log(String(undefined));返回如下注意:必须通过 call 或 apply 来调用,而不能直接调用 toString , 从原型链的角度讲,所有对象的原型链最终都指向了 Object, 按照JS变量查找规则,其他对象应该也可以直接访问到 Object 的 toString方法,而事实上,大部分的对象都实现了自身的 toString 方法,这样就可能会导致 Object 的 toString 被终止查找,因此要用 call/apply 来强制调用Object 的 toString 方法jQuery中的方法$.type(),就是用到了toStringtype: function( obj ) { if ( obj == null ) { return obj + “”; } // Support: Android<4.0, iOS<6 (functionish RegExp) return typeof obj === “object” || typeof obj === “function” ? class2type[ toString.call(obj) ] || “object” : typeof obj;},分析源代码:typeof obj === “object” || typeof obj === “function” ? class2type[ toString.call(obj) ]通过判断传入类型,如果是object或者function类型就直接返回class2type 中键值是对的结果,如果不是,那么一定就是基本类型, 通过 typeof 就可以class2type[ toString.call(obj) ] || “object"这是为了防止一些未知情况的,如果未取到,就返回object,保证了程序可用性参考文章:JS类型判断,typeof/constructor/instanceof的区别js中的constructor和prototypeJS类型判断jquery源码 揭开js之constructor属性的神秘面纱 ...

January 4, 2019 · 2 min · jiezi