Javascript类型转换-ToNumber

转换规则:
Undefined->NaN
Null->+0
Boolean->true转为1,false转为-0
Number->返回入参(不转换)
Symbol->抛出TypeError异常

Object转换Number规则:
1. 通过ToPrimitive(argument, hint Number)将Object转为原始值primValue
2. 返回ToNumber(primValue)
ToPrimitive参见https://blog.lyz810.com/article/2016/12/javascript-primitive-value/

示例1:


Number({
  valueOf() {
    return true;
  },
  toString() {
    return false;
  }
});//返回1

解释:
根据ToPrimitive规则,当前对象没有定义@@toPrimitive方法,则使用默认的转换规则,此处要转Number,所以先尝试调用valueOf,再尝试调用toString
该对象中定义了valueOf,且valueOf返回的不是一个Object,则ToPrimitive返回的值为true
这时再调用一次ToNumber,可以得出true转为Number结果为1

示例2:


Number({
  [Symbol.toPrimitive]() {
    return null;
  },
  toString() {
    return true;
  },
  valueOf() {
    return 2;
  }
});//返回0

解释:
这里虽然有了ValueOf和toString但定义了Symbol.toPrimitive,属于自定义了转原始值的方法,所以原始值转换结果为null
根据文章开头的规则,null转Number结果是0

字符串转数字规则:
首先ToNumber需要将字符串按照下面的语法进行解析,如果不能解释为字符串数字字面量(StringNumericLiteral),则结果为NaN
语法:
下面可选代表有0个~任意多个
字符串数字字面量StringNumericLiteral(2种):
1. 可选空白串(StrWhiteSpace
2. 可选空白串(StrWhiteSpaceStrNumericLiteral 可选空白串(StrWhiteSpace

空白串StrWhiteSpace
空白字符(StrWhiteSpaceChar)可选空白串(StrWhiteSpace

空白字符StrWhiteSpaceChar(2种):
1. 空白(TAB:U+0009、VT:U+000B、FF:U+000C、SP:U+0020、NBSP:U+00A0、ZWNBSP:U+FEFF、USP)
2. 行结束符(LF:U+000A、CR:U+000D、LS:U+2028、PS:U+2029)

StrNumericLiteral(4种,十进制,二进制,八进制,十六进制):
1. StrDecimalLiteral
2. BinaryIntegerLiteral(0b或0B开头,正则表示为/^0b[01]+$/i)
3. OctalIntegerLiteral(0o或0O开头,正则表示为/^0o[0-7]+$/i)
4. HexIntegerLiteral(0x或0X开头,正则表示为/^0x[\da-f]+$/i)

StrDecimalLiteral(3种,不带符号,正号,符号):
1. StrUnsignedDecimalLiteral
2. +StrUnsignedDecimalLiteral
3. -StrUnsignedDecimalLiteral

StrUnsignedDecimalLiteral(4种)
1. Infinity 无穷
2. DecimalDigits . [DecimalDigits] [ExponentPart](例子:1.、1.1、1.1e1、 1.1E1,注意e或E后面必须是整数)
3. .DecimalDigits [ExponentPart](例子:.1、.1e1、.1E1)
4. DecimalDigits [ExponentPart](例子:1、1e1、1E1)

字符串数字字面量和数字字面量有几处不同:
1. 前者允许在字符串开头及结尾添加一些空白或换行符
2. 前者表示十进制时,前面可以有任意多个前导0,而后者前导0代表8进制(读者可以尝试运行Number(‘012’) === 012)
3. 前者可以添加+-号声明他的符号位
4. 前者在空或只包含空白字符的时候,转换结果为+0
5. Infinity和-Infinity被看做是字符串数字字面量而不是数字字面量

示例:


Number(''); // 0 空字符串
Number(' '); // 0 空白字符
Number('\n'); // 0 行分隔符系列
Number('\ufeff');// 0,空白字符系列
Number(' 123 '); // 123,前后允许有空白
Number('012'); // 12,前导0不代表8进制,就是10进制
Number('0000012'); // 12,可以多个前导0
Number('0b11'); // 3,二进制
Number('0x0a'); // 10,十六进制
Number('100g'); // NaN,注意与parseInt区别
Number('Infinity'); // Infinity
Number('2e2'); // 200

Javascript 类型转换-ToBoolean

转换规则:
Undefined->false
Null->false
Boolean->返回参数值
Number->当参数为+0、-0或NaN时返回false,其他情况返回true
String->当参数为空字符串时(字符串长度为0),返回false,其他情况返回true
Symbol->true
Object->true

Argument Type Result
Undefined Return false.
Null Return false.
Boolean Return argument.
Number If argument is +0, -0, or NaN, return false; otherwise return true.
String If argument is the empty String (its length is zero), return false; otherwise return true.
Symbol Return true.
Object Return true.

Javascript原始值

JavaScript原始值是类型转换中的一个重要的概念,涉及到对象的类型转换时,会先转成原始值再进行其他转换。

建议先看最后面的例子,尝试在浏览器里运行后,再详细阅读原理

一、ToPrimitive ( input [ , PreferredType ] )
转原始值方法:

  1. 确保input是一个JavaScript的合法值
  2. 如果input不是一个Object类型的值返回input本身,否则继续
  3. 如果PreferredType没有传,则令hint为“default”,跳到步骤6
  4. 如果PreferredType是字符串,则令hint为“string”,跳到步骤6
  5. 如果PreferredType是数字,则令hint为“number”
  6. 令exoticToPrim为GetMethod(input, @@toPrimitive)返回值,其中@@toPrimitive是一个用于将对象转成原始值的方法,见后面的例子
  7. 如果exoticToPrim为undefined,继续步骤8,否则跳到步骤10
  8. 如果hint为“default”则将其改为“number”
  9. 返回OrdinaryToPrimitive(input, hint)的值,结束
  10. 令result为Call(exoticToPrim, input, « hint »)的返回值
  11. 如果result的类型不是Object,则返回result,结束流程,否则抛出TypeError异常

二、IsPropertyKey ( argument )
如果入参是字符串或Symbol类型,返回true,否则返回false

三、GetMethod ( V, P )

  1. 确保IsPropertyKey(P)是true
  2. 令func为GetV(V, P)的返回值
  3. 如果func为null或undefined,返回undefined,结束
  4. 如果func是一个可以被调用的方法,返回func,否则抛出一个TypeError异常

四、GetV ( V, P )
GetV用来得到属性的值,如果该值不是对象,则使用适合于该值类型的包装器对象来执行属性查找。 使用参数V和P调用操作,其中V是值,P是属性键。
第一部分中通过GetMethod(input, @@toPrimitive)调用GetV实际就是看input的@@toPrimitive是不是一个可以调用的方法,如果是,就返回这个方法,否则抛出异常

五、OrdinaryToPrimitive ( O, hint )

  1. 确保O是一个Object,hint是一个字符串,且只能是“string”或“number”
  2. 如果hint是“string”,令methodNames为[“toString”,”valueOf”](是一个有序的list),否则就是[“valueOf”,”toString”](顺序返回来)
  3. 遍历methodNames,并看每一项是不是可调用的方法,如果是则调用这个方法,然后看返回值,如果返回值不是个Object,OrdinaryToPrimitive函数直接返回这个返回值
  4. 如果上面遍历都没有符合条件,则抛出TypeError异常

六、示例
下面以Object转Number为例,观察如何调用初始值转换
1. 已知Object转Number的步骤如下:
令primValue为ToPrimitive(argument, hint Number)返回值
返回ToNumber(primValue)
2. +{}会将Object转为Number类型
来看第一段代码

const test1 = {
  valueOf: () => 2,
  toString: () => 3
};
console.log(+test1);

以上代码打印结果为2,转换过程如下:
1. 待转换的为Object(第一部分中的input),待转换类型为Number
2. 取test1的@@toPrimitive方法,这里没有,则要调用OrdinaryToPrimitive
3. 由于这里第二个参数为number,所以按顺序调用test1的valueOf和toString方法
4. 先调用valueOf,首先valueOf是一个函数,可以被调用,且返回值不是一个Object类型(这里返回值是2,是Number类型),那么OrdinaryToPrimitive返回的就是2
5. 再次调用ToNumber(2)最终结果还是2

如果没有valueOf:

const test2 = {
  toString: () => 3
};
console.log(+test2);

打印toString返回的值3

如果valueOf不是个方法,或者valueOf返回值是个Object:

const test3 = {
  valueOf: 2,
  toString: () => 3
};
console.log(+test3);

const test4 = {
  valueOf: () => ({}),
  toString: () => 3
};
console.log(+test4);

均打印toString的返回值3

valueOf返回值为非Object类型的其他值

const test5 = {
  valueOf: () => '0xa',
  toString: () => 3
};
console.log(+test5);

const test6 = {
  valueOf: () => 'a',
  toString: () => 3
};
console.log(+test6);

const test7 = {
  valueOf: () => null,
  toString: () => 3
};
console.log(+test7);

分别打印10,NaN,0
首先三个例子中均返回了valueOf的值作为原始值,根据ToNumber的规则,对原始值再进行一次ToNumber
也就是分别对’0xa’,’a’,null进行了一次转换,等同于+’0xa’,+’a’,+null

如果valueOf和toString返回值都是Object

const test8 = {
  valueOf: () => ({}),
  toString: () => ({})
};
console.log(+test8);

那么根据上面的算法,应该抛出TypeError异常,Chrome控制台运行结果为Uncaught TypeError: Cannot convert object to primitive value,符合预期

上面的例子都没有@@toPrimitive方法,我们看看@@toPrimitive方法如何使用:

const test9 = {
  [Symbol.toPrimitive]: (hint) => {
    console.log(hint);
    return 2;
  }
};
console.log(+test9);

结果输出number和2,由于这里只演示转数字,所以hint就是“number”,实际可以根据不同的类型返回不同的值,例如:

const test10 = {
  [Symbol.toPrimitive]: (hint) => {
    switch (hint) {
      case 'number':
        return 2;
      case 'string': 
        return 'Hello world';
      default: 
        throw new TypeError('转换不了')
    }
  }
};
console.log(+test10, `${test10}`);

以上返回的是2和Hello world

七、应用
一个简易的Money对象,转为数字时,返回数字金额,当转为字符串时自动添加人民币¥前缀

const moneyIn = {
  value: 10,
  [Symbol.toPrimitive](hint) {
    switch (hint) {
      case 'number':
        return this.value;
      case 'string': 
        return `¥${this.value}`;
      default: 
        throw new TypeError('出错了');
    }
  }
}
const moneyOut = {
  value: 5,
  [Symbol.toPrimitive](hint) {
    switch (hint) {
      case 'number':
        return this.value;
      case 'string': 
        return `¥${this.value}`;
      default: 
        throw new TypeError('出错了');
    }
  }
}
console.log(`收入:${moneyIn},支出:${moneyOut},余额¥${moneyIn - moneyOut}`);

注意这里只能相减,根据隐式类型转换规则,两个对象相减,会先将对象转为Number,而相加则并非如此,具体转换规则会在后续文章中分析讲解

Web Audio API系列之一Web Audio初探

本文介绍Web Audio API的简介以及使用范围等基础内容。

一、特性
Audio API主要有以下特性:

  • 为简单或复杂的混音/特效提供模块化路由架构,包括多路传送以及子混合(submixes)
  • 高动态范围,内部处理使用32位单精度浮点数
  • 对于需要极高节奏精度的音乐应用,例如鼓机和音序器,精确取样低延迟播放。同时也为动态创建音效提供了可能性
  • 为envelopes、淡入/淡出、粒状效应、滤波器扫描、LFOs等提供自动化的音频参数
  • 灵活的处理音频流中的声道,允许对其进行分离与合并
  • 可以从audio或video媒体元素中处理音频源
  • 可以处理从getUserMedia函数获取的媒体流
  • 与WebRTC集成:使用MediaStreamAudioSourceNode和webrtc处理从远端接收的音频;通过MediaStreamAudioDestinationNode和webrtc发送生成或处理过的音频流到远端
  • 音频流的合成与处理可以直接使用JavaScript
  • 支持各种3D游戏和身临其境的空间化音频:平移模型(等功率,HRTF,直通);距离衰减;声锥;阻塞/闭塞;多普勒频移;源/监听器
  • 卷积引擎适用于各种线性效果,尤其是非常高品质的室内效果,例如:小/大房间、大教堂、音乐厅、洞穴、隧道、门厅、圆形剧场、一个遥远的声音通过门口、极端过滤器、倒退效果、极端梳状滤波效果(Extreme comb filter effects)
  • 动态压缩整体控制以及甜味的混合(sweetening of the mix)
  • 高效的实时时域和频率分析/音乐可视化支持
  • 用于低通,高通和其他常见滤波器的高效双二阶滤波器。
  • 波形整形效果,用于失真和其他非线性效果
  • 振荡器

二、模块化路由
模块化路由允许不同AudioNode对象之间的任意连接。每个节点可以具有输入和(或)输出。源节点没有输入有一个输出。目的节点有一个输入,没有输出,最常见的例子是音频硬件的最终目的地AudioDestinationNode。可以在源节点和目的地节点之间放置其他节点,例如过滤器。当两个对象连接在一起时,开发人员不必担心低级流格式细节。例如,如果单声道音频流连接到立体声输入,它应该恰当地混合到左和右声道。
在最简单的情况下,单个源可以直接路由到输出。所有路由在单个AudioDestinationNode的AudioContext中:
Source→Destination
下面是一个简单的例子:

var context = new AudioContext();

function playSound() {
    var source = context.createBufferSource();
    source.buffer = dogBarkingBuffer;
    source.connect(context.destination);
    source.start(0);
}

模块化路由还允许将AudioNode的输出路由到控制不同AudioNode行为的AudioParam参数。在这种情况下,节点的输出可以用作调制信号而不是输入信号。

JavaScript通过execCommand向剪贴板写入数据

本文介绍JavaScript使用document.execCommand等函数向系统剪贴板中写入数据的方法。

一、document.execCommand
document.execCommand(‘copy’)可以复制当前页面上选中的内容到剪贴板中
兼容性:
document.execCommand本身的兼容性非常好,主流的浏览器(包括IE6等低版本浏览器)都支持。
document.execCommand(‘copy’)并不是所有浏览器都支持,可以根据document.queryCommandSupported(‘copy’)来查询是否支持copy命令。
根据MDN官网上说明,copy命令的兼容性为:
Chrome:43
Firefox:41
IE:9
Opera:29
Safari:10

二、input的select方法
input的select方法可以选中input的全部内容
注意,input的type不能是hidden,并且input不能display:none或visibility:hidden,否则无法选中

三、示例代码
document.execCommand(‘copy’)会复制当前页面被选中的文本,如果想要将页面被选中的文本复制到剪贴板中,直接调用document.execCommand(‘copy’)即可

js:
const inputHidden = document.querySelector('#inputHidden');
inputHidden.value = '复制到剪贴板的内容';
inputHidden.select();
document.execCommand('copy');
html:
<input type="text" id="inputHidden" />
css:
#inputHidden{ left: -9999px; top: -9999px; position: fixed;}

四、在线Demo
https://demo.lyz810.com/clipBoard/

CSS.supports API介绍

CSS.supports API用于通过JavaScript检测当前浏览器是否支持某属性及某属性的值。

一、接口用法
1.supports(DOMString property, DOMString value), returns boolean
2.supports(DOMString conditionText), returns boolean

第一种方法接收2个参数,第一个参数是属性的名称(如display、width、margin等);第二个参数是属性的值(如inline-block、100px、-10px等)
第二种方法接收1个参数,为条件字符串,支持or和and运算符,如”(display:inline-block)”、”(width: 100px) or (margin: -10px)”
注意:必须使用括号将每个条件包裹起来,即使只有一个条件

二、规范说明
当supports()方法通过两个参数property和value调用时,如果property是客户端支持的CSS属性名字面量,并且value可以正确的解析为对应属性支持的值,必须返回true。字面量匹配意思是不会对CSS进行处理,前后的空格不会被去除,所以如果有前后空格会导致方法返回false(例如下面的调用会返回false,因为width前面多了一个空格:CSS.supports(‘ width’, ‘100px’))。

当通过单独的一个条件字符串参数进行调用,如果条件字符串能够解析计算为一个支持的属性和值,则返回true;否则返回false。

三、其他说明
1.属性值和属性名不区分大小写
2.如果属性名包含连字符“-”,直接写原始值即可,不需要转换为驼峰方式

四、示例

CSS.supports('width', '-10px');//false,width不能小于0
CSS.supports('height', '50');//false,height需要有单位
CSS.supports('position', 'sticky');//false,也可能为true,测试浏览器不支持sticky
CSS.supports('Z-INDEX', '50');//true,不区分大小写
CSS.supports(' z-index', '50');//false,属性名前面不应该有空格
CSS.supports('z-index', '  50  ');//true,属性值前后可以有空格
CSS.supports('display:none');//false,没有用括号,应为CSS.supports('(display:none)')
CSS.supports('(    display  :  none  )');//true,这种写法前后可以有空格
CSS.supports('(display:none) and (width:-10px)');//false,第二个条件不支持所以整体为false

五、兼容性
IE:不支持
Edge:支持
Firefox:23+
Chrome:28+
Safari:9+
Opera:15+
iOS Safari:9.2+
Android:4.4+

CSS3动画事件

animationstart、animationend以及transitionend分别用于监听css3的关键帧动画的开始、结束以及css3过渡结束事件。

本文Demo:https://www.lyz810.com/demo/cssAnimationEvent/index.html

一、animationstart和animationend事件
animationstart事件在Animation动画开始时触发,对于低版本的webkit内核浏览器使用webkitAnimationStart事件
animationend事件在Animation动画停止时触发,对于低版本的webkit内核浏览器使用webkitAnimationEnd事件
用法示例:

document.querySelector('#test').addEventListener('animationstart', function(){
  console.log('动画开始');
});
document.querySelector('#test').addEventListener('animationend', function(){
  console.log('动画结束');
});

二、animationiteration事件
animationiteration事件会在Animation动画循环播放时触发,主要用于infinite类型的动画,会在每次循环播放完毕时触发。
无限循环的动画会触发animationstart、animationiteration两个事件。
对于低版本的webkit内核浏览器使用webkitAnimationIteration事件。
用法示例:

css:
@keyframes ani{
  from{margin-left: 0;}
  to{margin-left: 300px;}
}
#test{animation:ani 3s infinite;}

js:
document.querySelector('#test').addEventListener('animationiteration', function(){
  console.log('动画播放循环一次');
});

三、transitionend事件
transitionend事件在transition动画结束后触发。
用法示例:

document.querySelector('#test').addEventListener('transitionend', function(){
  console.log('过渡动画完毕');
});

JavaScript Vibration震动API

Vibration API用于控制移动端设备的震动器,它可以设置一个震动序列以完成不同时间间隔的震动频率。

一、应用场景

震动API主要应用于移动端游戏等交互类页面,例如设计类游戏可以在击中目标后调用震动器来增强用户体验。
它还可以用来做报错类的提醒,例如用户对一个表单的必填项没有进行填写,在校验失败后,可以调用震动API来提示用户进行填写。
具体使用场景需要考虑清楚,以免滥用反而影响用户体验。

二、兼容性

目前在caniuse上看到的兼容性情况是有问题的,有些浏览器虽然兼容,但基本上除了移动设备外,其他的上网设备都没有震动器,所以桌面版的浏览器虽然支持此API,但并不能使用(使用没有效果但不报错),故只应该应用于移动项目中。
目前在以下APP(Android系统)的最新版本中测试均有效:
360极速浏览器
UC浏览器
手机QQ内置浏览器(未测试QQ浏览器,但应该会支持)
微信
微博
三星手机默认浏览器(Android 5.1.1版本)

三、用法

navigator.vibrate可以控制震动器震动
它接收一个参数,表示震动序列,例如:
navigator.vibrate(1000)表示震动1000毫秒
navigator.vibrate(1000, 500, 1000)表示震动1000毫秒后,暂停500毫秒,然后再震动1000毫秒
当在一个震动的过程中,希望取消震动,则可以调用此方法,第一个参数为null或[]、[0]等:
navigator.vibrate(0)
navigator.vibrate(null)
navigator.vibrate([])
navigator.vibrate([0])
navigator.vibrate([0, 0, 0, ……])//全都是0,没什么意义

四、演示
请使用手机打开,支持微信,建议WIFI环境下打开,需要消耗600k左右的流量。
https://www.lyz810.com/demo/vibration/

JavaScript在线状态检测API

在线状态检测API用于检测当前的网络是否正常,包括是否在线以及在线状态改变事件。

一、当前在线状态属性

navigator.onLine用于检测当前的网络状态,为布尔型,用户网络连通则值为ture,否则值为false

二、状态改变事件

为window绑定online及offline事件可以监听网络连通性的改变:

window.addEventListener('online', function(){alert('网络已连接')},false);
window.addEventListener('offline', function(){alert('网络已断开')},false);

注意:IE8中需要给document.body绑定事件而不是window
此处网络连通性的变化指的是物理上的连通性变化,如果是在控制台将网络限制为offline则不会触发相应的事件。

三、兼容性

IE:8+
Chrome:14+
Firefox:3.5+(4-40存在bug,只有在offline模式下才会显示正确,否则onLine的值一直是true)
Safari:5+

页面可见状态API使用方法

Page Visibility API用于获取当前页面的可见性状态,在音频、视频播放、游戏等场景中十分有用。

本文Demo请见:https://www.lyz810.com/demo/pageVisibility/
一、应用场景

页面可见性状态一般可以用于以下场景:

  1. 音频或视频(尤其是视频)播放时,当页面不可见自动暂停播放,页面恢复后再继续播放
  2. 一些定时执行的操作(如微博获取最新的微博条数,该动作每分钟获取一次),没有必要在用户不可见的时候去获取数据
  3. 网页游戏在用户切换页面后,游戏自动暂停,用户体验更好
  4. 统计用户停留时长,这种方式比目前常用的两次页面打开时间之差得到用户停留时长更准确

二、document.visibilityState属性值

document.visibilityState有四个值(后两个是可选属性,浏览器可以不实现):

  1. hidden
  2. visible
  3. prerender
  4. unloaded

以下情况,document.visibilityState的值为hidden:

  • 浏览器最小化
  • 浏览器没有最小化,但是页面是非活动的标签页(即切换到其他标签),浏览器被其他窗口覆盖值不会受影响
  • 操作系统锁屏

在页面可见时,值为visible

在页面的父级页面不可见时,值可以为prerender(该属性为可选的)

在文档卸载时,值应为unloaded(该属性为可选的)

三、document.hidden属性

document.hidden为布尔型,当页面不可见(hidden)时为true,可见(visible)时为false

四、visibilitychange事件

document中的visibilitychange用于监听可见状态改变的事件,用法如下:

document.addEventListener('visibilitychange', function(){
  console.log(document.visibilityState);
}, false);

五、兼容性

IE:10+
FireFox:18+(10~17需要加moz前缀)
Chrome:33+(14~32需要加webkit前缀)
Safari:6.1+
Opera:12.1以及20+(15~19需要加webkit前缀,12.1不需要加前缀)