当前位置: 首页 > 科技观察

偶数赋值a.x=a={n-2}和a=a.x={n-2}一样吗?

时间:2023-03-17 15:42:15 科技观察

前言最近有空看了《js 高级程序设计》中关于变量和内存的章节,对变量和内存问题做了一个总结。囊括了一些面试中的常见问题,穿插了一些看似简单但不同寻常的代码小题,小心掉坑里。如果你在面试中被问到,你知道你的精明能不能给面试官留下深刻印象吗?如果您也有兴趣,请继续阅读。本文涉及以下知识点第一部分变量1基本数据类型8种。未定义、空值、布尔值、数字、字符串、符号、bigInt、对象。1.1有7种基本类型。未定义、空值、布尔值、数字、字符串、符号、bigInt。其中两个比较特殊,稍微解释一下。symbol从Symbol()返回的每个符号值都是唯一的。符号值可以作为对象属性的标识符;这是此数据类型的唯一目的。Symbol('1')==Symbol('1')//false作为构造函数并不完整,因为它不支持语法:newSymbol()。Symbol(1)//Symbol(1)newSymbol(1)//UncaughtTypeError:SymbolisnotaconstructorQ1.如果一个对象的key是用Symbol创建的,如何获取该对象下的所有key,至少两个?A1.目的。getOwnPropertyNamesObject.getOwnPropertySymbols或Reflect.ownKeysQ2。typeofSymbol和typeofSymbol()分别输出什么?A2.typeofSymbol//"function"typeofSymbol()//"symbol"bigIntBigInt是一种数字类型的数据,可以表示任意精度格式的整数。目的是为了安全地使用更准确的时间戳、大整数ID等,这些都是chrome67中的新特性。Number类型只能安全地表示-9007199254740991(-(2^53-1))和9007199254740991(2^53-1)之间的整数,超出这个范围的整数值可能会失去精度。9007199254740991//90071992547409919007199254740992//90071992547409929007199254740993//9007199254740992!!创建一个在整数末尾附加n的BigInt,或调用BigInt()构造函数。9007199254740993n//9007199254740993nBigInt('9007199254740993')//9007199254740993nBigInt(9007199254740993)//9007199254740992n!!BigInt和Number不严格相等,但大致相等。9n==9//true9n===9//false1.2引用类型对象中包含的函数、Array、Date、RegExp。1.3null和undefined的区别undefined读取的是一个还没有赋值的变量,null定义的是一个空对象,是一个不存在的对象的占位符。将null和undefined转换为数字数据类型的结果是不同的。null转0,undefined转NaNnewNumber(null)//Number{0}newNumber(undefined)//Number{NaN}2判断数据类型2.1typeof只适用于判断基本数据类型(null除外)。Null表示空对象指针并返回对象。对于对象,除了function可以正确返回function外,其余的也都是返回object。vara;console.log(a);//undefinedconsole.log(typeofa);//undefinedtypeoftypeof1//"string"2.2instanceof判断A是否是B的实例,AinstanceofB用于判断已知对象类型或自定义类型.functionBeauty(name,age){this.name=name;this.age=age;}varbeauty=newBeauty("lnp",18);beautyinstanceofBeauty//trueQ1.ObjectinstanceofFunction//trueFunctioninstanceofObject//trueObjectinstanceofObject//trueFunctioninstanceofFunction//trueinstanceof可以判断[]是Array的实例,但是它认为[]也是Object的实例,因为它的内部机制是通过对象的原型链来判断的。在查找过程中,它会遍历左边变量的原型链,直到找到右边的变量。如果找到原型,则返回true。instanceof不能判断字面量创建的Boolean、Number、String类型,但是可以判断new操作符创建的实例。instanceof可以判断字面量创建的引用类型Array和Object。2.3constructorconstructor属性返回对创建此对象的数组函数的引用。constructor不能用来判断undefined和null,因为它们没有constructor。在实际场景小程序中,WXS不支持使用Array对象,所以我们通常用它来判断数组类型[]instanceofArray不能使用,typeof[]的输出结果是object,不满足要求。这时候可以使用constructor属性进行类型判断:[].constructor===Array//true。2.4toStringObject.prototype.toString.call()判断一个对象属于哪个内置类型。是最严谨、最通用的数据类型判断方法。判断基本类型Object.prototype.toString.call(null);//[objectNull]"Object.prototype.toString.call(undefined);//"[objectUndefined]"Object.prototype.toString.call(true);//[objectBoolean]"Object.prototype.toString.call(123);//[objectNumber]"Object.prototype.toString.call("lnp");//[objectString]"判断原生引用type///函数类型FunctionBeauty(){console.log("beautifullnp");}Object.prototype.toString.call(Beauty);//[objectFunction]"//日期类型vardate=newDate();Object.prototype.toString.call(date);//[objectDate]"//数组类型vararr=[1,2,3];Object.prototype.toString.call(arr);//[objectArray]"//正则表达式varreg=/^[\d]{5,20}$/;Object.prototype.toString.call(arr);//[objectRegExp]”判断原生JSON对象varisNativeJSON=window.JSON&&Object.原型.toString.call(JSON);console.log(isNativeJSON);//输出结果为“[objectJSON]”,说明JSON是原生的,否则不是Q1.({a:1}).toString()//返回What({a:1}).__proto__===Object.prototype({a:1}).toString()//[objectObject]对于Object对象,直接调用toString()可以返回[objectObject],对于对于其他的对象,需要通过调用来返回正确的类型信息。总结内存的第二部分是堆/栈。基本类型存放在栈内存中,引用类型的值存放在堆内存中,存放值的地址(指针)存放在栈内存中。这是因为栈内存中存储的大小必须是Fixed数据,而引用类型的大小是不固定的。堆和栈的区别2深拷贝和浅拷贝2.1数据类型Q1的拷贝。以下代码的输出是什么?1==1;[]==[];//?{}=={};//?A1.答案正确吗??1==1;//true[]==[];//false{}=={};//false原因:原值的比较是值的比较,当值相等时,theyareequal(),值Theyareequal(=)当类型和类型都相等时。对象(数组)的比较就是引用的比较。即使两个对象包含相同的属性和相同的值,它们也不相等。复制基本类型图片时,会在栈内存中重新开辟内存来存放变量age2,所以栈内存中存放的是变量age和age2各自的值,修改不影响彼此。复制引用类型时,只复制存放在栈内存中的指针,Beauty和Beauty2的指针指向堆内存中相同的值。连续赋值a.x=a={n:2}你懂吗?那么标题中的问题来了!你准备好了吗?请看下面的一小段代码......vara={n:1};varb=a;a.x=a={n:2}console.log(a.x);console.log(b.x);console.log(a);console.log(b);//上面的代码输出了什么?答案(猜测)正确吗?undefined{n:2}{n:2}{n:1,x:{n:2}}解析我们先还原一下这道题的执行过程。首先,当vara={n:1};时,在堆内存中为变量a开辟一块内存空间。图片当varb=a;时,变量b的指针在堆内存中指向与a相同的内存空间。a.x=a={n:2}因为“.”是优先级最高的运算符,先计算a.x,a.x还没有定义,所以是undefined。然后从右往左解析赋值操作,执行a={n:2},将a(一层变量)重新赋值指向新的内存空间。此时a.x指的是堆中已有内存{n:1x:undefined}的x属性,a.x可以看成是一个整体A。此时A的点已经确定,所以点a的a不会影响a.x,执行a.x=a,然后将{n:2}赋值给{n:1x:undefined}的x属性。既然函数执行到这里了,我们再看看什么是(a.x);(b.x);(A);(b);分别指向。本题考查以下两个知识点:对象类型数据在栈中的存储方式与Javascript中运算符赋值的优先级相同。我们来看一下,将a.x=a={n:2}替换成a=a.x={n:2}呢?vara={n:1};varb=a;a=a.x={n:2}console.log(a.x);console.log(b.x);console.log(a);console.log(b);答案是一样的,原理同上。2.2深拷贝和浅拷贝的区别浅拷贝只拷贝对象的指针,不拷贝对象本身。复制后,新旧对象在堆内存中指向同一个值,修改新对象改变旧对象。深拷贝不仅复制了指针,还复制了指针所指向的值,即会创建一个完全相同的对象副本。新对象和旧对象是独立的内存空间,修改新对象不会影响旧对象。2.3深拷贝和浅拷贝的优缺点,常见场景深拷贝的好处避免内存泄漏:当拷贝一个包含指针成员的对象时,被拷贝的对象指针成员有自己的内存空间。浅拷贝的好处如果对象比较大,层次很多,深拷贝会带来性能问题。遇到需要深拷贝的场景,可以考虑是否有其他方案。在实际应用场景中,更常用的是浅拷贝。2.4深浅拷贝的实现方式及其缺点深拷贝方式:JSON.parse()和JSON.stringify()针对的是纯JSON数据对象,但它能正确处理的对象只有Number、String、Boolean、数组和平面对象。letobj={a:{b:22}};letcopy=JSON.parse(JSON.stringify(obj));postMessage递归lodash浅拷贝方法:object.assignspreadoperator...sliceArray.prototype.concat()2.5总结深浅拷贝只针对Object、Array等复杂对象。浅拷贝只拷贝一层对象的属性,而深拷贝递归拷贝所有层。第三部分垃圾收集javaScript有一个自动垃圾收集机制,即执行环境将负责管理代码执行期间使用的内存。那么,我们可以忽略它吗?当然不是。开发者只是不需要关注内存是如何分配和回收的,但是仍然需要避免自己的操作导致内存无法跟踪和回收。垃圾回收首先我们了解一下js垃圾回收的原理。在局部范围内,函数执行后,局部变量不再是必需的,因此可以释放它们的内存以备将来使用。这种情况下js的垃圾回收机制很容易做出判断并回收。垃圾收集器必须不断跟踪哪些变量有用,哪些变量没用,把没用到的标记出来,等待以后合适的时机回收。js中有两种垃圾回收机制,一种是标记清除,一种是引用计数。所以对于一个全局变量,很难确定什么时候不再需要它。内存泄漏vs堆栈溢出什么是内存泄漏和堆栈溢出,它们之间有什么关系?内存泄漏是您不再访问但仍占用内存空间的变量。栈溢出就是调用的时候栈操作太多,返回的时候出栈不够。关系:内存泄漏的积累最终会导致堆栈溢出。内存泄漏会导致什么问题?运行缓慢、崩溃、高延迟4种常见的内存泄漏陷阱意外的全局变量,这些是不会被回收的变量,尤其是那些用于临时存储大量信息的变量(除非设置为null或被重新分配)定时器不会销毁节点DOM外引用闭包(闭包的局部变量会一直保存在内存中)2常见的栈溢出递归无限循环内存泄漏避免策略减少不必要的全局变量,或者对于生命周期长的对象,及时解引用(设置全局对象和globalobjectpropertiesthatarenotlongerusedtonull)注意程序逻辑,避免“死循环”之类的,避免创建过多的对象,减少过多的层级引用刘妮萍:微医前端工程师。养鱼养花养狗熬夜跳舞喝酒。出门半生,归来还有三年的前端工作经验。