据说是面试题:由【if(a==1&&a==2&&a==3)】引发的思考探讨
有一天,突然在一个微信群有个群友发了张图片抛出了一道题,如图:
 
下面,我们先还原原题:
1 //下面代码什么时候会打印1?
2 var a=?;
3 if(a==1&&a==2&&a==3){
4 console.log(1);
5 }
说实话,我第一眼看到时居然理所当然地认为让a=true或者a=!0应该就可以了,但是代码世界的种种复杂变数让我不能轻易相信第一感觉,于是马上打开电脑,在chrome浏览器的控制台快速敲下以下代码:
【a=true时】
1 var a=true;
2 if(a==1&&a==2&&a==3){
3 console.log("猜想正确:"+a);
4 }else{
5 console.log("猜想错误");
6 }
 
【a=!0时】
1 var a=!0;
2 if(a==1&&a==2&&a==3){
3 console.log("猜想正确:"+a);
4 }else{
5 console.log("猜想错误");
6 }
 
然后打印结果让我意识到错误的同时也激发了深入探索的兴趣:既然猜想错误,那到底要怎么做才能实现呢?背后的原理又会是怎样的呢?带着疑问,然后打开了百度上CSDN的一篇博客文章开始看。
文章一开头就说是一道有趣的面试题:Excuse Me?!惊愕之余就仔细品读了共有4种主要方法。接下来我结合自己的理解尽力将4种方法的原理给解释一下,如有错误,请多多指正,谢谢各位~
【解法①:利用对象的类型装换】
 1 var a={
 2     num:1,
 3     toString:function(){
 4         return a.num++;
 5     }
 6 }
 7
 8 if(a==1&&a==2&&a==3){
 9     console.log("猜想正确!");
10 }else{
11     console.log("猜想错误!");
12 }
 
目的达到了,其中的原理是什么呢?我们先看a==1&&a==2&&a==3,这是一个短路逻辑与运算符,这就表明只有左端条件为真能会继续往右端进行判断,否则立即整个判断像短路一样为假了,所以呢,a的第一个值必须是a==1为真之后才会进行第二步的a==2判断,由此推断a的值或者说是间接返回值(类型转换后的值)应该是可以自增长的!另外,这种a==1的判断,JavaScript中当遇到不同类型的值进行比较时,会根据类型转换规则试图将它们转为同一个类型再比较。比如 Object 类型与 Number 类型进行比较时,Object 类型会转换为 Number 类型。转换为时会尝试调用 Object.valueOf 和 Object.toString 来获取对应的数字基本类型。
在上述的代码中,逻辑转换先调用了valueOf方法,如果返回的还是对象,再接着调用toString()方法。每次比较都会先执行重写后的对象方法toString(),这个方法里先返回属性num的值再自增(区分:return a.num++表示先返回再自增,return ++a.num表示先自增再把结果返回)。知道了对象a的内部之后就能明白,执行a==1判断时,对象a调用toString()方法返回了属性num的值1,此时比较两个当然是相等的。与此类似,a==2和a==3一样成立。看到这里是否有豁然开朗的感觉捏?
【解法②:利用数组的取值和类型转换】
JavaScript里的数组真的是灵魂支柱,因为绝大多数的数据都在数组里操作,因此很多时候解决问题的巧妙思路也能从它着手。下面先上代码和运行结果:
1 var a=[1,2,3,4];
2 a.join=a.shift;
3 if(a==1&&a==2&&a==3){
4 console.log("猜想正确!");
5 }else{
6 console.log("猜想错误!");
7 }
 
眨眼一看这个写法莫名其妙让人匪夷所思,当好好地理解了之后就霎时拍案叫绝,代码之简洁优雅,思路之清奇独到,堪称腻害!我们知道在JavaScript中一切皆对象,那么Array当然也是对象的子类了,同样继承了Object对象的方法valueOf()和toString(),而且重写了toString()方法,在调用数组中的每个元素的 toString() 返回值经调用 join() 方法连接(由逗号隔开)组成。所以在这里可以不重写toString()方法了,只需要对join()方法进行处理即可。那么join()方法作用扮演的是什么角色呢?没错,它用来将数组各项通过连接符拼接起来形成字符串,它不会改变原数组仅仅是取出元素连接起来。shift()方法是会将数组的第一个元素删除并返回被删除的元素,换言之就好像是直接将数组的第一个元素移出数组,因此它改变了原数组的结构和长度,但是自身不会创建新的数组。
让我们把目光聚焦到a.join=a.shift,这句话的意思是当数组调用toString()方法而间接调用join方法时,shift()方法替代了join方法,这样就相当于每次从a数组中截取第一个元素返回。所以当判断a==1时其实是从原数组截取了第一个元素的值返回后再判断,这样原数组就变成了[2,3],接着a==2判断执行类似操作即可。怎么样,这个方法巧妙吧?有没有被惊讶到捏?
【解法③:理由Object对象的defineProperty()方法定义属性并重写getter()方法】
同样道理,Let's show the code to see see!
1 var num=1;
2 Object.defineProperty(window,'a',{
3 get:function(){
4 return num++;
5 }
6 })
7
8 if(a==1&&a==2&&a==3){
9 console.log("猜想正确!");
10 }else{
11 console.log("猜想错误!");
12 }
 
可能有的人看到defineProperty()并不是很了解它的用处,我查了下MDN上的说法:Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
首先,JavaScript的运行环境通常主要分为两种:客户端(浏览器)和服务端(node),这两种环境下的全局对象管理所有的变量和函数,客户端是window,node是global,在本例以window为参考。此时通过defineProperty()给window对象定义了一个a属性,a属性的值由get()方法返回后再自增。因此,当判断a==1时,实际上是获取省略掉window对象前缀的a的值后再比较。这个defineProperty()方法应当直接在 Object 构造器对象上调用此方法,而不是在任意一个 Object 类型的实例上调用。
【小扩展:defineProperty()方法有两种给定义的属性赋值的方法:数据描述符和存取描述符(有set或get方法)】
【解法④:利用Unicode字符编码,这种方式没什么技术含量不必深究也不推荐,了解即可】
1 var aᅠ = 1;
2 var a = 2;
3 var ᅠa = 3;
4 if(aᅠ ==1 && a ==2 && ᅠa ==3){
5 console.log("Let's see see!");
6 }else{
7 console.log("Don't want to see see,ok?!");
8 }
 
【解法⑤:利用ES6的类来实现】
ES6是引入了类的比较规范的写法,我们可以在类的定义里做想做的事情,下面演示用传统函数和类分别实现:
1 //传统函数写法
2 //定义内部变量,重写valueOf并返回一个可以增长的变量值
3 function fnA(){
4 var num=1;
5 this.valueOf=function(){ //只有对象的valueOf方法被调用时才执行
6 return num++;
7 }
8 }
9
10 //ES6的规范类写法
11 class clazzA{
12 constructor(){
13 this.num=0; //类被调用创建对象就会执行构造函数,该变量会自增
14 this.valueOf();
15 }
16 valueOf(){
17 return this.num++;
18 }
19 }
20
21 //let a=new fnA; //此时valueOf并不会被调用
22 let a=new clazzA; //构造函数调用了一次valueOf方法
23 if(a==1&&a==2&&a==3){
24 console.log("实现了!");
25 }else{
26 console.log("what's wrong?");
27 }
 
 
从上面的代码和打印结果看出,传统函数和ES6类都借助了Object自带的valueOf()方法,只是二者在处理时不一样:传统函数被调用时valueOf()并没有被立即调用,只是通过匿名函数的方式声明了函数,真正调用valueOf()还是在执行判断时隐式调用的;而ES6类则选择了再构造函数里直接调用在类里重写后的valueOf()方法。因此,两者在定义变量num的初始值时需要注意一下!
通过上述的探讨大体上就使用了5种解决方法,其中最简洁优雅巧妙的当属解法②数组对象方式,解法③方式属于修改对象属性,解法①和解法5的核心还是利用对象的内置方法valueOf()或toString()进行重写值返回,解法④就权当看看了解吧~
OK,本次探讨暂且到此为止,如有错漏,欢迎指正,谢谢~
版权所有,转载请注明出处!
据说是面试题:由【if(a==1&&a==2&&a==3)】引发的思考探讨的更多相关文章
- (转)c++类的成员函数存储方式(是否属于类的对象)---一道面试题引发的思考
		昨天去面试一家公司,面试题中有一个题,自己没弄清楚,先记录如下: class D { public: void printA() { cout<<"printA"< ... 
- 一道面试题引发的思考(C#值类型和引用类型)
		某年某月,笔者去面试招行的一个外包项目,辗转来到面试地点以后,面试官给了我一份试卷,试卷只有两道题目,其中一道是这样的: 阅读以下程序 class Program { struct Point { p ... 
- js事件相关面试题
		说是面试题,其实也相当于是对js事件部分知识点的一个总结.简单内容一笔带过,了解详情我都给出了参考链接,都是之前写的一些相关文章.JavaScript本身没有事件模型,但是环境可以有. DOM:add ... 
- WEB前端常见面试题汇总:(一)
		1.JS找字符串中出现最多的字符 例如:求字符串'nininihaoa'中出现次数最多字符 方法一: var str = "nininihaoa"; var o = {}; for ... 
- Java线程面试题Top50
		不管你是新程序员还是老手,你一定在面试中遇到过有关线程的问题.Java 语言一个重要的特点就是内置了对并发的支持,让 Java 大受企业和程序员的欢迎.大多数待遇丰厚的 Java 开发职位都要求开发者 ... 
- 分享一份非常强势的Android面试题
		马上步入金九银十了,是时候看一些面试题去鹅厂了,接下来我将分享一些面试题,每天总结一点点,希望对大家有所帮助! ListView和RecyclerView区别 参考链接: https://blog.c ... 
- 不使用数据结构反转栈 递归 CVTE实习 CVTE是一家什么公司
		本文因为垃圾csdn标题字限制,标题写不好.本文想说一个算法,和我在CVTE的实习,我看到CVTE是一家什么公司.如果想要喷我的,可以留言,我不会理.如果想喷公司,可以在博客评论或发到我邮件linde ... 
- 探寻 JavaScript 逻辑运算符(与、或)的真谛
		十二月已经过半,冬季是一个美妙的季节,寒冷的空气逼得人们不得不躲在安逸舒适的环境里生活.冬季会给人一种安静祥和的氛围,让人沉浸在其中,仿佛是一个旧的阶段的结束,同时也是一个新的阶段的开始.这么说来,西 ... 
- Python黑帽编程2.7 异常处理
		Python黑帽编程2.7 异常处理 异常是个很宽泛的概念,如果程序没有按预想的执行,都可以说是异常了.遇到一些特殊情况没处理会引发异常,比如读文件的时候文件不存在,网络连接超时.程序本身的错误也可以 ... 
随机推荐
- 区块链入门到实战(28)之Solidity – 介绍
			Solidity语言是一种面向合约的高级编程语言,用于在以太坊区块链网络上实现智能合约.Solidity语言深受c++.Python和JavaScript的影响,针对以太坊(Ethereum)虚拟机( ... 
- Elementor如何隐藏页面上的标题(2种办法)
			原文首发于:https://loyseo.com/how-to-hide-page-title-in-elementor/ 本文介绍两种隐藏Elementor页面默认标题的方法,一种是单个隐藏,一种是 ... 
- 微信小程序——导航栏组件
			组件内属性详解 属性 类型 默认值 必填 说明 nav-postion String relative 否 导航栏(包含导航栏以及状态栏)的position,可取值relative.fixed.a ... 
- Welcome To CUG_YZL's cnblogs
			Welcome To CUG_YZL's cnblogs My name is YZL, studied in China University of Geosciences Wuhan now.W ... 
- 泊松分布算法的应用:开一家4S店
			王老板开了一家4S店,卖新车为主,车型也很单一,可是每个月销量都变化很大,他很头疼,该怎么备货,头疼的是: 1)备货少了,可以来了没货可能就不买,去别的店了 2)备货多了,占用库存不说,长久卖不出去就 ... 
- 微信小程序|小游戏
			[官]小游戏开发 https://developers.weixin.qq.com/minigame/dev/index.html 官网 https://mp.weixin.qq.com 做了4个微信 ... 
- JQuery的Ajax实现注册检测用户名
			Ajax(无需等待直接向服务器发起请求) (Asynchronous Javascript And Xml) :异步的 Google创新的一种js技术 实现方法一:比较原始没有封装的方法: //核对用 ... 
- 6.AVCodecContext和AVCodec
			AVCodecContext AVCodecContext 结构表示程序运行的当前 Codec 使用的上下文,着重于所有 Codec 共有的属性(并且是在程序运行时才能确定其值)和关联其他结构的字段 ... 
- 文件操作和OS模块的简单操作
			文件的作用 大家应该听说过一句话:“好记性不如烂笔头”. 不仅人的大脑会遗忘事情,计算机也会如此,比如一个程序在运行过程中用了九牛二虎之力终于计算出了结果,试想一下如果不把这些数据存放起来,相比重启电 ... 
- 架构设计 | 基于Seata中间件,微服务模式下事务管理
			源码地址:GitHub·点这里 || GitEE·点这里 一.Seata简介 1.Seata组件 Seata是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务.Seata将为用 ... 
