【中卷】你不知道的JavaScript(一)

1、typeof 安全机制

  • undefinedundeclared是两码事,但是typeofundefinedundeclared 变量返回都是 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不存在,会返回undefinedboolean类型为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]'

字符串只可以借助部分数组方法,为什么呢?

  1. 字符串本身是不可变的,变异函数是在变量本身进行修改
  2. 数组方法(非变异函数)可以通过改变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) 可以正常执行


    1.jpeg
  • chrome (95.0.4638.54)不能执行


    2.jpeg
  • 火狐(93.0)也是不能执行的
    网上有很多实现的案例,但是在多次调用之后,就会出现问题,详见:https://zhuanlan.zhihu.com/p/296852112

8、JSON.stringify

  • JSON.stringify()在对象中遇到undefinedfunctionsymbol时会自动忽略,在数组中则会返回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、== 和 ===

  • ===在进行比较相等中不允许进行强制类型转换,==允许
  • 不同类型之间进行比较像等(==)
  1. numberbooleanstring三种类型在进行比较的时候,会转换为数字进行判断
42 == '42'  // true
42 == true  // false
42 == false // false 
1 == true   // true
"1" == true // true

2.undefinednull

undefined == null       // true
undefined == undefined  // true
null == null            // true

3.object 和 数字( 或者字符串 )
object会调用toPrimitive抽象操作

"abc" == Object("abc")  //  true
42 == [42]              // true

特殊情况(nullundefined不能被封装为对象)

# Object(null)                     // {}
null == Object(null)               // false 
undefined == Object(undefined)     // false
  1. 实现一个函数满足以下情况
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、标签语句

  • continuebreak 在进行跳过或者中断循环的时候,通过标签语句可以对其进行限定范围
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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容