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,只要差值的绝对值小于容差,认为是数值相等。bash

第二个问题,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:
		// 永远不会运行到这里
}

复制代码
相关文章
相关标签/搜索