YDKJS-类型与文法

这一册回答这样两个问题:

类型和值真正的工作方式 强制转换的角落和缝隙、强制转换值得花时间学习&合理的地方

主要内容:回答问题

谈谈类型和值真正的工作方式

JavaScript中定义的7种内建类型?

首先要说明的是,变量没有类型,变量持有有类型的值。 其中内建类型有:nullundefinedstringnumberbooleanobjectsymbol(ES6新加); 使用typeof可以准确测出除null外的6个,还有functiontyepof demo;如果要测试null

//唯一一个typeof是object但是是fasly的值
var a = null;

(!a && typeof a === "object"); // true
复制代码

Array、String、Number和特殊值认识补充

类Array是什么?有例子吗?如何转换为Array?

类Array指长得很像Array但是没有链接到Array.prototype的数据结构,比如DOM查询操作返回的DOM元素列表(链接到NodeList.prototype)、函数内参数值暴露的arguments对象(链接到Object.prototype,在ES6被废弃)。 如何转换有两种方式:

  • Array.prototype.slice.call(***)
  • Array.from(***)

对String和Array有肤浅的相似性这样的说法怎么看?

肤浅的相似性体现在它们都有一个length属性,一个indexOf方法和一个concat方法:demo

说是肤浅说明两者更大的是区别。String是不可变的,String.prototype上挂载的方法操作字符串,不是原地修改内容,而是创建返回一个新的字符串;Array是相当可变的,尽管Array.prototype上挂载的方法有创建返回一个新数组的非变化方法,也有原地修改内容的变化方法。

对于String操作,有些Array方法是有用的,我们可以对string"借用"非变化的array方法:demo;也有著名的反转字符串的例子是做了String=>Array=>String的转换:demo。当然如果频繁借用Array方法,那为什么最开始不直接设为Array类型。

0.1+0.2===0.3为什么是false?安全整数是什么?

第一个问题,0.1和0.2在js中的二进制形式表达是不精确的,相加结果不是0.3,而是接近0.3的一个值,所以是false。为了靠谱地做数值的比较,引入一个容差/机械极小值,在ES6中这个值是Number.EPSILON,只要差值的绝对值小于容差,认为是数值相等。

第二个问题,JavaScript能表示的最大最小值是Number.MAX_VALUENumber.MIN_VALUE,但是对于整数而言有一个安全范围,在这个安全范围内,整数是无误表示,这个范围是Number.MIN_SAFE_INTEGERNumber.MAX_SAFE_INTEGER

NaN是什么?如何判定一个值是NaN?

使用不同为number的值做操作等到的就是一个NaN,它是一个哨兵值,可以理解为不合法的数字/失败的数字,试着进行数学操作但是失败了,这就是失败的number结果。

NaN值的判定不能直接和NaN比较,因为NaN是唯一一个自己不等于自己的。ES6给了一个Number.isNaN(..),如要要填补的话,类型是数字+自己不等于自己两个条件。

ES6提供了Object.is(..),用来测试两个值的绝对等价性,没有任何例外:demoObject.is(..)的定位是这样的:

Object.is(..) 可能不应当用于那些 ===== 已知 安全 的情况(见第四章“强制转换”),因为这些操作符可能高效得多,并且更惯用/常见。Object.is(..) 很大程度上是为这些特殊的等价情况准备的。

谈谈原生类型构造器/内建函数

内部[[Class]]是什么?

typeof结果是object的值被额外地打上一个内部标签属性,就是[[Class]]。这个属性不能直接访问,但可以间件通过Object.prototype.toString.call(..)访问。

比如:demo ,注意简单基本类型stringnumberboolean发生封箱行为;比如结果[Object Array]后面的Array反应的是原生类型构造器/内建函数这个信息;

特殊的是nullundefined也能工作,结果是[Object Null] [Object Undefined],虽然没有NullUndefined

Object.prototype.toString.call(..)也被用来测值类型,比typeof更完善。

谈谈封箱与开箱?自动封箱有什么优势?

封箱与开箱是两个相反的过程,封箱将基本类型值包装进对象,开箱从对象中取出基本类型值。封箱有手动封箱(比如new String("aaa"))和自动封箱两种,在基本类型值上访问对象的属性和方法就会发生自动封箱,比如var a = "zzz";a.length。自动封箱,即让封箱在需要的地方隐含发生,这是推荐的,一个是方便另一个是浏览器对于.length等常见的情况有性能优化。开箱也分手动开箱(valueOf)和自动开箱两种,当以一种查询基本类型值的方式使用对象包装时,自动开箱发生,即开箱隐含发生,给个demo

这么多的原生构造器...表个态?

ArrayObjectFunctionRegExp

这四种的话使用字面形式创建值几乎是更好的选择。

(使用Array构造器的一个用途:快速创建undefined * n的数组

(使用Object构造器,得一个一个去添加属性,麻烦!)

Function构造器用于动态定义函数,这篇文章里有一个demo,但是一般也挺少用的)

使用RegExp有一个用途是字面形式没有的——用于创建动态的正则表达式

DateError

这两种的话没有字面形式,只能使用原生类型构造器。

使用Date构造器给一个demo,获取当前时间时间戳比较常用

Error手动使用比较少,一般是自动被抛出,但是有些像messagetypestack的属性可能会有用

Symbol

使用Symbol(...)构造器生成的数据是简单的基本标量(注意不是object),Symbol主要为特殊的ES6结构的内建行为设计。可以自定义,也有预定义的Symbol值。(这里简单带过了,以后实际遇到了再研究)

谈谈强制转换的角落和缝隙、强制转换值得花时间学习&合理的地

抽象操作ToStringToNumberToBoolean分别是?和强制转换的关系?

强制转换最后都是转为booleanstringnumber这样的基本标量,而抽象操作ToStringToNumberToBoolean规定了这样的转换。

首先是ToString,转换为string,给个demo,注意ToString可以被明确地调用,也可以在一个需要String的上下文环境中自动地调用;另外想补充一下JSON.stringify(..)字符串化(本身和ToString的抽象操作没有关系,只要是对简单类型的字符串化行为比较像),JSON字符串化分安全值和非法值,非法值主要是因为不能移植到其他消费JSON值的语言,非法值包括undefinedfunctionsymbol和带有循环引用的objectobject可以定义一个toJSON方法,该方法在JSON.stringify(..)调用时首先被调用,主要用途是将JSON非法值转换为安全值,另外JSON.stringify(..)也提供第二个参数是替换器——过滤object属性、第三个参数填充符。

然后说说ToNumber,转换为number,给个demo,注意object/array首先是ToPrimitive抽象操作。

最后是ToBoolean,转换为boolean,这个比较简单,falsy列表中的转换为falsenullundefined""+0-0NaNfalse

明确地String<-->Number

最明确的当然是不带newString(..)Number(..);另外x.toString()也被认为是明确转为字符串,尽管背后隐含地发生了封箱;一元操作符+也被认为是明确转为数字,但是如果情况是多个+/-连着,是增加困惑的。

String-->Number似乎有点关联的是解析数字字符串的行为(比如parseIntparseFloat),解析数字字符串和类型转换的一个最大区别是,解析数字字符串是容忍错误的,比如parseInt("42px")结果是"42",而类型转换是零容忍的。关于parseInt有个网红面试题

明确地 * --> Boolean

明确地转为布尔值类型有两种方式:不带newBoolean(a)和两个否定符号!!aBoolean(a)非常明确,但是不常见,!!a更加明确。

有用的隐含强制转换

隐含地 Strings <--> Numbers

Numbers --> Strings

两个操作数的+操作符是这样工作的:如果两个操作数之一是一个string(或者object/array通过ToPrimitive抽象操作成为一个string),做string连接;其他情况做数字加法。使用在隐含地Numbers转换为Strings上面是,number + ""

Strings --> Numbers

这里使用-操作符,-操作符仅为数字操作定义,所以两个操作数都会被转为number,即发生ToNumber抽象操作。使用在隐含地Strings转换为Numbers上面是,string - 0

隐含地 Booleans --> Numbers

两个操作数的+操作符在没有string的情况是做数字加法,也适用这里,比如true+true===2 ,这里就隐含地发生了Booleans转换为Numbers。这个点有个有意思的用途,比如有3个条件,在仅一个为true情况做后续操作:

const cond1 = ....;
const cond2 = ....;
const cond3 = ....;

if((cond1&&!cond2&&!cond3)||(!cond1&&cond2&&!cond3)||(!cond1&&!cond2&&cond3)){
    //后续操作...
}
复制代码

这里的boolean逻辑就很复杂,但可使用前面的方法用数字加法做简化:

if((!!cond1+!!cond2+!!cond3)===1){//后续操作...}
复制代码

隐含地 * --> Booleans

有些表达式操作会做一个隐含地强制转换为boolean值:

  1. if(..)
  2. for(;;)第二个子句
  3. while(..)do{..}while(..)
  4. ?:三元表达式的第一个子句
  5. ||&&

其中||&&被称为逻辑或和逻辑与,但也可以认为是操作数选择器操作符,因为:

a || b; //大致相当于 a?a:b
a && b; //大致相当于 a?b:a
复制代码

另外JS压缩器会把if(a){foo();}转换为a&&foo(),因为代码更短,但是很少有人这么做(我有这么做过...)

怎么选择宽松等价与严格等价?宽松等价的抽象等价性比较算法可以说一说吗?

对待宽松等价与严格等价的态度

比较同类型的值时,它们算法是相同的,做的工作也基本是一致的,所以这里是随意的;在比较不同类型的值,宽松等价允许做强制转换,如果想要强制转换用宽松等价,否则用严格等价。 宽松不等价与严格不等价是在前面两个的基础上取反。

宽松等价的“抽象等价性比较算法”

同类型的值是简单自然地比较;

不同类型的值:

  • 比较:stringnumber
  1. 如果Type(x)是Number而Type(y)是String, 返回比较x == ToNumber(y)的结果。

  2. 如果Type(x)是String而Type(y)是Number, 返回比较ToNumber(x) == y的结果。

  • 比较:任何东西与boolean
  1. 如果Type(x)是Boolean, 返回比较 ToNumber(x) == y 的结果。

  2. 如果Type(y)是Boolean, 返回比较 x == ToNumber(y) 的结果。

这里挂个demo,提醒千万不要==true或者==false

  • 比较:nullundefined
  1. 如果x是null而y是undefined,返回true。

  2. 如果x是undefined而y是null,返回true。

nullundefined是互相等价的,而不等价于其他值(唯一宽松等价)。这里给一个伪代码:

var a = doSomething();

if (a == null) {
	// ..
}
复制代码

无论anull还是undefined,检查都是通过的,而不需要再去做a===undefined||a===null,更加简洁。

  • 比较:object与非object
  1. 如果Type(x)是一个String或者Number而Type(y)是一个Object, 返回比较 x == ToPrimitive(y) 的结果。
  2. 如果Type(x)是一个Object而Type(y)是String或者Number, 返回比较 ToPrimitive(x) == y 的结果

宽松等价的坏列表?

宽松等价的坏列表指的是比较难理解的比较情况,发生的也是隐含强制转换,demo

为了避免坏列表,给出了可以遵循的规则:

  1. 如果比较的任意一边可能出现true或者false值,那么就永远,永远不要使用==
  2. 如果比较的任意一边可能出现[]"",或0这些值,那么认真地考虑不使用==

在这些情况,使用=====

a<b时发生了什么?说说抽象关系比较算法

大致流程:

  1. 如果ab的值有一个object类型,首先是ToPrimitive抽象操作处理,使得比较双方都是基础类型值
  2. 如果这时候两个比较对象都是string类型,按照简单的字典顺序比较(数字在字母前面)
  3. 如果不满足2,两个比较对象做ToNumber抽象操作处理,再进行数字比较

demo-两个对象的比较

在这个例子中a<ba==ba>b结果都是false,奇怪的是a>=ba<=b都是true。因为大于等于、小于等于更贴切的翻译是不小于、不大于,对比较操作取反。

比较没有像等价那样的“严格的关系型比较”,所以如果不允许隐含强制转换发生,在比较之前做好类型的强制转换

主要内容之外

undefined和undeclared概念

给个代码片段就知道这两者的区别:在这里a就是undefined,b就是undeclared。

var a;

a; // undefined
b; // ReferenceError: b is not defined
复制代码

typeof 在对待 undeclared的变量时不会报错,这是一种安全防卫行为。它有什么用呢?

  1. 假设debug.js文件中有一个DEBUG的全局变量,只有在开发环境下导入全局,生产环境不会,在其他的程序代码中可能会去检查这个DEBUG变量:demo
  2. 再比如想象一个你想要其他人复制-粘贴到他们程序中或模块中的工具函数,在它里面你想要检查包含它的程序是否已经定义了一个特定的变量:demo

第二个实际case没有遇到过,另外也在链接下面挂出了依赖注入的解决方式。

获取当前时间戳有哪些方法?

除了之前提到的使用Date构造器的两种方法(new Date().getTime()Date.now())以外,一元操作符+也可以:+new Date/+new Date(),但是使用一元操作符+转换的方式不够明确,所以不是很推荐。

位操作符~发生了什么?有什么用途?

位操作符|会发生ToInt32(转换为32位整数),像0|x一般被用于转换为32位整数。位操作符~首先第一步也是ToInt32的转换,其次是按位取反,给个demo。用途有两个:

  1. 使用arr#indexOfarr#findIndexstr#indexOf时如果找不到结果返回-1-1是一个错误情况的哨兵值,使用>=-1/==-1这样的判断,是一种“抽象泄露”,而如果使用~x //0当x=-1这样的判断更加优雅
  2. 截断比特位,~~截断数字的小数部分,当然是ToInt32的结果

操作符优先级(更紧密的绑定)和结合性

例如分析下面表达式的执行结果:

a && b || c ? c || b ? a : c && b : a
复制代码

工作分两步,首先是确定更紧密的绑定,因为&&优先级比||高,而||优先级比? :高。所以更紧密的绑定使得上述表达式等价于:

((a && b) || c) ? (c || b) ? a : (c && b) : a
复制代码

第二步是在同等优先级的操作符情况下,是从左发生分组(左结合)还是从右发生分组(右结合),比如?:是右结合的,所以a?b:c?d:e是等价于a?b:(c?d:e),而不是(a?b:c)?d:e,所以上述表达式等价于:

((a && b) || c) ? ((c || b) ? a : (c && b)) : a
复制代码

掘金上有看到问true || true && fn()表达式中,fn会不会执行。 按照之前更紧密的绑定分析,等价于表达式true||(true&&fn()),但是执行顺序依然是从左往右,因为||的短接,所以在第一个选择数为true的情况,不会去执行第二个选择数,也就不会执行fn函数。

涉及TDZ(时间死区)的两种情况

  1. letconst声明的变量是在TDZ中的,所以必须得在声明之后使用,而不会发生提升;
  2. 参数默认值
var b = 3;

function foo( a = 42, b = a + b + 5 ) {
	// ..
}
复制代码

在赋值中的b引用将在参数b的TDZ中发生(不会被拉到外面的b引用),所以它会抛出一个错误。然而,赋值中的a是没有问题的,因为那时参数a的TDZ已经过去了。

switch每个case是严格等价还是宽松等价?

答案是严格等价,但是宽松等价是可以模拟的:

var a = "42";

switch (true) {
	case a == 10:
		console.log( "10 or '10'" );
		break;
	case a == 42:
		console.log( "42 or '42'" );
		break;
	default:
		// 永远不会运行到这里
}

复制代码

转载于:https://juejin.im/post/5c7c89edf265da2de165bda2

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值