1、typeof 安全机制
-
undefined
和undeclared
是两码事,但是typeof
对undefined
和undeclared
变量返回都是undefined
-
undeclared
变量在进行使用的时候,浏览器会报错(如下所示)
if(DEBUG){
consoel.log("Debugging is starting");
}
VM635:1 Uncaught ReferenceError: DEBUG is not defined
at <anonymous>:1:13
判断变量是否存在,typeof
自带的安全机制,可避免报错
if(typeof DEBUG !== 'undefined'){
consoel.log("Debugging is starting");
}
或者通过全局对象进行判断,即使变量DEBUG
不存在,会返回undefined
,boolean
类型为false
if(window.DEBUG){
// pass
}
- 为某个脚本缺失的功能写
polyfill
,内部不能通过关键字var
(声明提升)进行声明,防止与浏览器中一些特殊的内建全局变量冲突,重复声明会报错,去掉var
可以防止声明被提升
if(typeof atob === 'undefined'){
atob = function(){
// pass
}
}
- 在
let
声明前引用会进入暂时性死区,typeof
安全机制也不能避免
typeof a
let a
2、数组
- 数组使用
delete
运算符可以将单元从数组中删除,删除后数组长度不发生改变 - 对数组未设置的单元,会显示通过
undefined
填充,但与直接设置a[1] =undefined
不同
let a = [];
a[0] = 0;
a[2] = 2;
console.log(a) // [0, empty, 2]
console.log([...a]) // [0, undefined, 2]
console.log(JSON.stringify(a)) // [0, null, 2]
- 数组通过数字进行索引,同时也是对象,可以包含键值对,但是不计算在长度(length)之内
- 我们一般不在数组中保存属性,但是
Array.prototype
是这么使用的,是一个空数组,并且有许多属性对应各自的方法
let a=[]
a[0] = 0
a[1] = 1
a['a'] = 'a'
a['b'] = 'b'
console.log(a, a.length) // [0, 1, a:'a', b: 'b']
注意:如果键值中有可以被强制转换
为十进制数字,就会
被当做数字索引来处理
a['12'] = 1
console.log(a.length) // 13
3、字符串
- 字符串经常被当成字符数组,都是类数组,本质并不相同,
- 字符串可以借助数组函数处理字符串
var a = "foo" ;
var b = ["f", "o", "o"] ;
var c = Array.prototype.join.call( a, "-")
var d = Array.prototype.map.call( a, function(v){
return v.toUpperCase() + '.'
}).join("")
console.log(c, d) // f-o-o F.O.O.
但是无法通过借助数组reverse
方法翻转字符串
console.log(Array.prototype.reverse.call(a))
// Cannot assign to read only property '0' of object '[object String]'
字符串只可以借助部分数组方法,为什么呢?
- 字符串本身是不可变的,变异函数是在变量本身进行修改
- 数组方法(非变异函数)可以通过改变
this
指向处理字符串
转变思路
将字符串转换为数组,逆序后再将数组转变为字符串
var a = "foo"
var c = a.split("").reverse().join("")
console.log(c) // "oof"
4、数字
- 数字可以使用一些省略写法
var a = 0.42 => var a = .42
var b = 42.0 => var b = 42.
- 所以数字在使用一些方法的时候,有歧义的情况下,会优先识别为数字的一部分,导致一些语法错误
// 无效语法
42.toFixed( 3 ) // SyntaxError
// 正确语法
0.42.toFixed(3) // 0.420
(42).toFixed(3) // 42.000
42..toFixed(3) // 42.000
42 .toFixed(3) // 42.000 注意有空格哦😯
- 小数精度问题
0.1 + 0.2 === 0.3 // false
计算机在存储的时候通过二进制编码,整数部分通过除以2取余,小数部分通过乘以2取整,对于无线循环的部分会进行截取(2进制,类似于四舍五入)
- 机器精度(ES6新增)
if(!Niumber.EPSILON){
Number.EPSILON = Math.pow(2, -52)
}
function numbersCloseEnoughToEqual(n1, n2){
return Math.abs( n1- n2 ) < Number.EPSILON
}
- 整数安全范围(ES6)
Number.MAX_VALUE 1.79E+308
Number.MAX_SAFE_INTEGER Math.pow(2, 53) - 1
Number.MIN_SAFE_INTEGER - ( Math.pow(2, 53) - 1 )
if (num1 * num2 <= Number.MAX_VALUE) {
func1();
} else {
func2();
}
-
NaN
不是数字的数字,仍然是数字类型
var num = 2 / "foo"; // NaN
typeof num === "number"; // true
NaN !== NaN // true
isNaN(num)
Js提供了isNaN
方法用于类型判断
Number.isNaN( "foo" ) // true
// 满足 isNaN方法 且 typeof 返回 number 的情况
if(!Number.isNaN){
Number.isNaN = function(n){
return ( typeof n === "number" && window.isNaN(n) )
}
}
// NaN是 `Js` 中唯一一个不等于自身的值
if(!Number.isNaN){
Number.isNaN = function(n){
return n !== n
}
}
5、值和引用
-
JavaScript
中没有指针,引用的工作机制也不尽相同。在JavaScript
中变量不可能成为指针指向另一个变量的引用。 -
JavaScript
引用指向的是值。如果一个值有10个引用,这些引用指向的都是同一个值,它们相互之间没有引用 / 指向关系。
var a = 2;
var b = a;
b++;
a ; // 2
b ; // 3
var c = [1, 2, 3] ;
var d = c ;
d.push(4);
c ; // [1, 2, 3, 4]
d ; // [1, 2, 3, 4]
- 由于引用指向的是值本身而非变量,所以一个引用无法更改另一个引用的指向
var a = [1, 2, 3]
var b = a
a ; // [1, 2, 3]
b ; // [1, 2, 3]
b = [4, 5, 6]
a ; // [1, 2, 3]
b ; // [4, 5, 6]
- 如果想通过
b
来修改a
的值,应该在相同指向的时候进行对值修改,而非重新修改引用
var a = [1, 2, 3]
var b = a
a ; // [1, 2, 3]
b ; // [1, 2, 3]
b.length = 0
b.push(4, 5, 6)
a ; // [4, 5, 6]
b ; // [4, 5, 6]
- 然而基础类型不可修改
function foo(x){
x = x + 1
console.log(x)// 3
}
var a = 2
foo(a)
console.log(a) // 2
- 不通过传参数的方式,函数内部使用的外部变量,是可以修改的
function foo(){
x = x + 1
console.log(x) // 3
}
var x = 2
foo()
x // 3
引用功能强大,但有时也会成为阻碍,赋值 / 参数传递是通过引用还是值复制完全由值的类型来决定,所以使用那种类型也间接决定了赋值 / 参数传递的方式。
6、原生函数(内建函数)
原生函数可以被当做构造函数来进行使用,但是构造出来的对象和直接赋值的对象有所不同
new String('abc')
创建的是字符串"abc"
的封装对象,而非基本类型值"abc"
var a = new String( "abc" )
console.log(a) // String {'abc'}
console.log(typeof a); // object
console.log(a instanceof String); // true
console.log(Object.prototype.toString.call( a )); // "[object String]"
let b = 'b'
console.log(b) // b
console.log(typeof b); // string
console.log(b instanceof String); // false
console.log(Object.prototype.toString.call( b )) // "[object String]"
- 于是就出现了奇怪的现象,
这里new
关键字创建的是false
的封装对象,而不是基本类型false
本身。对象本身是真值
var a = new Boolean( false )
if(!a){
console.log( "I'm here!" ) // 执行不到这里
}
- 那么如果想要获得封装对象中的基本类型值呢?(
valueOf
方法)
var a = new String( "abc" )
var b = new Number( 42 )
var c = new Boolean( true )
a.valueOf() ; // "abc"
b.valueOf() ; // 42
c.valueOf() ; // true
7、toString
对象如果有自己的toString()
方法,字符串化的时候会自动调用该方法,并使用其返回值。
相信有一个面试题可能大家都遇到过,实现一个方法,可以满足:
add(1)(2)(3)(4,5)(6)
function add(...args){
let sum = args.reduce((prev, cur)=>{return prev + cur}, 0)
function add2(...args2){
return add(sum, ...args2)
}
add2.toString = function(){
return sum
}
return add2
}
console.log(add(1,2,3)(4)(5,6)) // 21
这个函数是可以实现的,但是浏览器兼容问题,以现在(2021-10-28)为例,
-
Safari (14.0.3) 可以正常执行
-
chrome (95.0.4638.54)不能执行
- 火狐(93.0)也是不能执行的
网上有很多实现的案例,但是在多次调用之后,就会出现问题,详见:https://zhuanlan.zhihu.com/p/296852112
8、JSON.stringify
-
JSON.stringify()
在对象中遇到undefined
、function
和symbol
时会自动忽略,在数组中则会返回null
,保证元素位置不发生改变
JSON.stringify( undefined ); // undefined
JSON.stringify( function(){} ); // undefined
JSON.stringify( [1, undefined, function(){}, 4] ) // [1, null, null, 4]
JSON.stringify( { a:2, b: function(){} }) // '{"a":2}'
-
JSON.stringify()
方法对于循环引用的对象会出错 - 但是可以在对象中定义
toJson()
方法,序列化的时候会首先调用该方法返回安全值,然后对安全值进行序列化
let o = {};
let a = {
b: 42,
c: o,
d: function(){ }
}
o.e = a // 循环引用
JSON.stringify( a ) // TypeError: Converting circular structure to JSON
a.toJSON = function(){
return {b: this.b}
}
JSON.stringify( a ); // '{"b":42}'
-
JSON.stringify()
可以通过可选参数,指定部分属性被序列化
let a = {
b: 42,
c: "42",
d: [1, 2, 3]
}
JSON.stringify( a, ["b", "c"]) // {"b":42,"c":"42"}
JSON.stringify(a, function(k, v){
if(k !== 'd'){
return v
}
}) // {"b":42,"c":"42"}
-
JSON.stringify()
第三个参数可以通过指定缩进字符数(数字)或者 符号
let a = {
b: 42,
c: "42",
d: [1,2,3]
}
JSON.stringify(a, null, 3);
// {
// "b": 42,
// "c": "42",
// "d": [
// 1,
// 2,
// 3
// ]
// }
JSON.stringify(a, null, '-');
// {
// -"b": 42,
// -"c": "42",
// -"d": [
// --1,
// --2,
// --3
// -]
// }
9、假值列表
强制类型转换布尔类型为false
,其他均为真值
- undefined
- null
- false
- +0, -0, NaN
- ""
10、|| 和 &&
- || 运算符 一般用来设置默认值
function foo(a, b){
a = a || 'hello' ;
b = b || 'world' ;
}
- && 如果 左边的表达式满足条件,则执行右边的表达式,而并非返回
boolean
值
a && b => a ? b : a
a || b => a ? a : b
11、== 和 ===
- ===在进行比较相等中不允许进行强制类型转换,==允许
- 不同类型之间进行比较像等(==)
-
number
、boolean
、string
三种类型在进行比较的时候,会转换为数字进行判断
42 == '42' // true
42 == true // false
42 == false // false
1 == true // true
"1" == true // true
2.undefined
、null
undefined == null // true
undefined == undefined // true
null == null // true
3.object
和 数字( 或者字符串 )
object
会调用toPrimitive
抽象操作
"abc" == Object("abc") // true
42 == [42] // true
特殊情况(null
、undefined
不能被封装为对象)
# Object(null) // {}
null == Object(null) // false
undefined == Object(undefined) // false
- 实现一个函数满足以下情况
if(a == 2 && a == 3){
// pass
}
var i = 2
Number.prototype.valueOf = function(){
return i++
}
var a = new Number(42)
if(a == 2 && a == 3){
console.log("'I'm here!'")
}
12、标签语句
-
continue
、break
在进行跳过或者中断循环的时候,通过标签语句可以对其进行限定范围
outerloop: for(var i = 0;i < 3; i++){
console.log(i)
for(var j = 0;j < 5;j++){
if(j == 3){
continue outerloop;
}
console.log(j)
}
}
console.log('exit')
// 0 0 1 2 1 0 1 2 2 0 1 2 exit
13、[] 与 {} 求和
# [] 被强制转换为'' , {}被转换为[object Object]
[] + {} ; // '[object Object]'
# {} 被识别为代码块,不做任何处理 ,[] 被强制转换为0
{} + [] ; // 0
14、全局DOM变量
在创建带有id
属性的DOM
元素时也会创建同名的全局变量,所以尽量不要使用全局变量进行命名,避免冲突
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="div" ></div>
</body>
<script type="text/javascript">
console.log(div) // HTML元素
</script>
</html>
15、script标签
- 多个
script
标签加载的文件,是一个整体还是相互独立?
相互独立运行,共享全局变量 - 对于前面运行的
js
中声明的变量,后面可以直接引用, - 同一个文件内部存在变量声明提前,
script
之间不存在
*script
发生错误终止运行,但并不影响后续script
中的代码
# ReferenceError: foo is not defined
<script>
foo()
</script>
<script>
function foo(){
console.log('foo')
}
</script>
- 内联代码中不能出现
</script>
,一旦出现就会视为代码块结束。
<script>
var code = "<script>alert('hello world!')</script>";
</script>
变通方法
<script>
var code = "<script>alert('hello world!')</scr" + "ipt>";
</script>
16、保留字
Let this long package float,
Goto private class if short.
While protected with debugger case,
Continue volatile interface.
Instanceof super synchronized throw,
Extends final export throws.
Try import double enum?
False, boolean, abstract function,
Implements typeof transient break!
Void static, default do,
Switch int native new.
Else, delete null public var
In return for const, true, char
... Finally catch byte.
你不知道的JavaScript(中卷)
提取码:8utt