拾起曾经遗忘的基础(JS篇)

论坛 期权论坛 期权     
软谋前端   2019-7-20 10:23   1390   0
前言正如标题所说,这是我第三次翻开红宝书也就是《 JavaScript 高级程序设计第三版》,不得不说,虽然书有一些年份,很多知识点也不适合现代的前端开发,但是对于想要掌握 JavaScript 基础的前端新手,亦或是像我一样想找回曾经遗忘在记忆角落的那些碎片知识,这本书依旧非常的适合,不愧被成为 "JavaScript 圣经"。

本文是读书笔记,之所以又一次选择读这本书还有一个理由,之前都是记的纸质笔记,这次想把它作为电子版,也算是对之前知识的整理。
  1. 本文篇幅较长,目的是作为我的电子版学习笔记,我会尽可能去其糟粕,取其精华,同时我会添加一些书上未记载但很重要的知识点补充。
复制代码
  1. [/code]
  2. [b]let's go[/b]
  3. [b]JavaScript 简介[/b]
  4. 一个完整的 JavaScript 由 3 个部分组成,核心(ECMAScript 语法),DOM,BOM,后两者目前已经是可选项了,或者可以抽象为宿主,因为 JS 已经不仅限运行于浏览器
  5. [b]在 HTML 中使用 JavaScript[/b]
  6. 在浏览器中使用 JS 可以通过 [code]script
复制代码
标签来执行 JS 文件,进一步可以分为 3 种方式,内嵌 JS 代码,通过 src 指向本地 JS 文件,通过 src 指向某个静态服务器的 JS 文件(域名),推荐的是使用 src 的形式,相比于内嵌可以利用缓存提高页面加载速度和解析 DOM 的速度,并且,因为 JS 和 HTML 解耦了可维护性更强

当 script 标签是 src 形式的外部脚本,中可以设置
  1. defer
复制代码
,
  1. async
复制代码
属性,前者可以让页面解析完毕后再运行脚本,后者则是异步下载脚本并执行,同时会异步的执行 JS 代码,这 2 个属性都是为了解决浏览器必须要等到 script 标签中的 JS 代码下载并执行后才会解析之后的元素从而导致的白屏时间久的问题
  1. [/code][list][*][*][/list][code]
复制代码
  1. 复制代码
复制代码
  1. [/code][b]JavaScript 基本概念[/b][b][/b]
  2. [h1][b]标识符[/b][/h1]标识符指的是变量,函数,属性的名字,主流的名字以驼峰命名为主,或者 $,_[b]第一个字符不能是数字(第二个字符开始是合法的)[/b][b][/b]
  3. [code]
复制代码
  1. // illegal
复制代码
  1. let 123hello = '123'
复制代码
  1. [/code][code]// legitimate
复制代码
  1. let $123hello = '123'
复制代码
  1. let helloWorld = '123'
复制代码
  1. let hello123World = '123'
复制代码
  1. let _$hello = '123'
复制代码
  1. 复制代码
复制代码
  1. [/code][h1][b]数据类型[/b][/h1][b][/b]
  2. 截至今日,JavaScript 有 7 种简单数据类型,1种复杂数据类型
  3. 简单数据类型:
  4. [list][*]Undefined
  5. [*]Null
  6. [*]Boolean
  7. [*]Number
  8. [*]String
  9. [*]Symbol
  10. [*]BigInt (ES10 草案)
  11. [/list]复杂数据类型:
  12. [list][*]Object
  13. [/list][code]Function 是 Object 的子类,即继承于 Object
复制代码
[h2]Undefined 类型[/h2]Undefined 类型只有一个值,即 undefined,它和 not defined 很容易混淆,它们的异同在于
  • 使用 typeof 操作符都会返回 'undefined'
  • 使用 undefined 变量是安全的,使用 not defined 的变量会抛出错误
  1. [/code][list][*][*][*][*][*][*][*][*][/list][code]let foo
复制代码
  1. [/code][code]console.log(typeof foo) // 'undefined'
复制代码
  1. console.log(typeof bar) // 'undefined'
复制代码
  1. [/code][code]console.log(foo) // undefined
复制代码
  1. console.log(bar) // Uncaught ReferenceError: bar is not defined
复制代码
  1. 复制代码
复制代码
  1. [/code][h2]Null 类型[/h2]Null 类型也只有一个值,即 null,[b]null 表示一个空对象指针[/b],如果使用 typeof 操作符,返回的类型是 'object',但这只是语言上的 BUG,目前几乎不可能修复,如果使用 instanceof 操作符判断是否是 Object 的实例,会返回 false,证明 null 和 Object 并没有什么关系
  2. [code]
复制代码
  1. console.log(typeof null) // 'object'
复制代码
  1. console.log(null instanceof Object) // false
复制代码
  1. 复制代码
复制代码
  1. undefined 值是派生自 null 值的,所以它们抽象相等console.log(undefined == null) // true
复制代码
  1. 复制代码
复制代码
  1. [/code][h2]Number 类型[/h2]JS 的 Number 类型使用 IEEE754 格式来表示整数和浮点数值,它会导致一些小问题,例如 JS 的 0.1 其实并不是真正的 0.1,它的二进制为 0.001100110011...,无限循环(小数十进制转二进制的规则是乘 2 取整),内部是这样存储的
  2. 可以通过 Number 函数,将传入的参数转为相应的 Number 类型(注意隐式转换的坑)
  3. [code]
复制代码
  1. console.log(Number('123')) // 123
复制代码
  1. console.log(Number(null)) // 0
复制代码
  1. console.log(Number(undefined)) // NaN
复制代码
  1. console.log(Number('false')) // NaN
复制代码
  1. console.log(Number(true)) // 1
复制代码
  1. 复制代码
复制代码
  1. [/code]NaN 属于 Number 类型,且 NaN 不等于自身,可以通过 window 对象的 [code]isNaN
复制代码
来判断参数是否是 NaN,但是它有个缺陷在于会先将参数转为 Number 类型(同样是隐式转换),所以会出现
  1. isNaN('foo')
复制代码
返回 true 的情况,ES6 的
  1. Number.isNaN
复制代码
弥补了这一个缺陷,它会返回 false,证明 'foo' 字符串并不是 NaN
  1. [/code][list][*][*][*][*][*][*][*][*][*][/list][code]console.log(NaN === NaN) // false
复制代码
  1. console.log(isNaN(NaN)) // true
复制代码
  1. console.log(isNaN('foo')) // true 但这是不合理的,因为 'foo' 并不是 NaN
复制代码
  1. console.log(Number.isNaN('foo')) // false
复制代码
  1. 复制代码
复制代码
  1. parseInt 和 Number 函数的区别在于,前者是逐个字符解析参数,而后者是直接转换console.log(parseInt('123.456')) // 123
复制代码
  1. console.log(parseInt('123foo')) // 123
复制代码
  1. console.log(Number('123foo')) // NaN
复制代码
  1. 复制代码
复制代码
  1. [/code]parseInt 会逐个解析参数 '123foo',当遇到[b]非数字字符或者小数点则停止[/b](这里是字符串 f),会返回之前转换成功的数字,而 Number 则是将整个参数转为数字
  2. (值得一提的是 parseFloat 遇到非数字字符或者[b]第二个[/b]小数点,会返回之前转换成功的数字)
  3. [h2]String[/h2]
  4. ECMAScript 中的字符串是一旦创建,它们的[b]值[/b]就不可改变,如果需要改变某个变量保存的字符串,需要销毁原来的字符串,再用另一个新值字符串填充该变量
  5. [h2]Object[/h2]DOM 和 BOM 对象都是由宿主提供的宿主对象,这里的宿主即浏览器,换句话非浏览器环境可能会没有浏览器上的一些全局变量和方法,例如 node 中就没有 alert 方法
  6. [h1][b]操作符[/b][/h1]
  7. [h2]一元操作符[/h2]只能操作一个值的操作符叫做一元操作符,后置递增/递减操作符与前置递增/递减有一个重要的区别,后置是在包含它们的语句被求值之后执行的
  8. [code]
复制代码
  1. let num1 = 2
复制代码
  1. let num2 = 20
复制代码
  1. let num3 = --num1 + num2 // 21
复制代码
  1. let num4 = num1 + num2 // 21
复制代码
  1. 复制代码let num1 = 2
复制代码
  1. let num2 = 20
复制代码
  1. let num3 = num1-- + num2 // 22
复制代码
  1. let num4 = num1 + num2 // 21
复制代码
  1. 复制代码
复制代码
  1. [/code]前者先让 num1 减1,再执行和 num2 累加,后者是先和 num2 累加,再让 num 减1
  2. ,另外一元操作符会先尝试将变量转换为数字
  3. [h2]布尔操作符[/h2]
  4. 逻辑与和逻辑非这两个操作符都是短路操作,即第一个操作数能决定结果,就不会对第二个操作数求值
  5. [code]
复制代码
  1. let num = 0
复制代码
  1. true || num++
复制代码
  1. console.log(num) //0
复制代码
  1. 复制代码
复制代码
  1. [/code]以下常用的逻辑与判断结果
  2. 第一个操作数操作符第二个操作数结果null&&任何第一个操作数undefined&&任何第一个操作数NaN&&任何第一个操作数false&&任何第一个操作数""&&任何第一个操作数0&&任何第一个操作数对象&&任何第二个操作数true&&任何第二个操作数[b]当第一个参数是假值时,逻辑与返回第一个操作数,反之返回第二个操作数[/b][b][/b]
  3. 以下是所有假值的列表:false,null,undefined,0,NaN,""
  4. 逻辑或与逻辑与相反,以下常用的逻辑或与判断结果
  5. 第一个操作数操作符第二个操作数结果null||任何第二个操作数undefined||任何第二个操作数NaN||任何第二个操作数false||任何第二个操作数""||任何第二个操作数0||任何第二个操作数对象||任何第一个操作数true||任何第一个操作数[b]当第一个参数是假值时,逻辑或返回第二个操作数,反之返回第一个操作数[/b][b][/b]
  6. [b][/b]
  7. [h2]加性操作符[/h2]
  8. 在 ECMAScript 中,加性操作符有一些特殊的行为,这里分为操作数中有字符串和没有字符串的情况
  9. 有字符串一律视为字符串拼接,如果其中一个是字符串,另一个不是字符串,则会将它转为字符串再拼接,接着会遇到两种情况
  10. [list][*]第二个操作数是对象,则会调用 [[toPrimitive]] 将其转为原始值,如果原始值是字符串那仍会执行字符串拼接
  11. [*]操作数不是对象,则直接视为字符串拼接
  12. [/list][code]
复制代码
  1. console.log("123" + 123) // "123123"
复制代码
  1. console.log('123' + NaN) // "123NaN"
复制代码
  1. console.log("123" + {}) // "123[object Object]"
复制代码
  1. console.log("123" + undefined) // "123undefined"
复制代码
  1. 复制代码
复制代码
  1. [/code]如果两个操作数都不是字符串,又会有两种情况
  2. [list][*]操作数是对象,则会调用 [[toPrimitive]] 将其转为原始值,如果原始值是字符串那仍会执行字符串拼接
  3. [*]操作数不是对象,则会转为 Number 类型再计算
  4. [/list]值得一提的是,涉及到 NaN 的四则运算最终结果都是 NaN(另一个操作数为字符串仍视为字符串拼接)
  5. [code]
复制代码
  1. console.log(123 + true) // 124
复制代码
  1. console.log(123 + undefined) // NaN 因为 undefined 被转为 NaN
复制代码
  1. console.log(NaN + {}) // "NaN[object Object]" 含有对象会转为原始值,因为是字符串所以视为拼接
复制代码
  1. 复制代码
复制代码
  1. [/code][h2]关系操作符[/h2]
  2. 和加性操作符一样,JS 中的关系操作符(>,=, 2 -> 4 -> 3 -> 4 -> 3 -> 4 -> ... -> 退出
  3. [h2]for in 语句[/h2]
  4. for in 语句会返回对象的属性,[b]返回的顺序可能会因浏览器而异[/b],因为没有规范,所以不要依赖它返回的顺序,而 Reflect.ownKeys ,Object.getOwnPropertyNames,Object.getOwnPropertySymbols  是由 ES6 规范 [[OwnPropertyKeys]] 算法定义的,其内容如下
  5. [list][*]首先顺序返回整数的属性(数组的属性)
  6. [*]依次按照创建顺序返回字符串属性
  7. [*]最后返回所有符号属性
  8. [/list][h2]label 语句[/h2]使用 label 语句可以为 for 语句添加标签的功能,当 for 语句内部通过 break,continue 语句退出时,可以额外指定标签名来退出到更外层的循环,这会用在多层 for 循环中
  9. [code]
复制代码
  1. let num = 0
复制代码
  1. outer: for (let i = 0; i < 10; i++) {
复制代码
  1.     for (let j = 0; j < 10; j++) {
复制代码
  1.         if (i === 5 && j === 5) {
复制代码
  1.             continue outer
复制代码
  1.         }
复制代码
  1.         num++
复制代码
  1.     }
复制代码
  1. }
复制代码
  1. [/code][code]console.log(num) // 95
复制代码
  1. 复制代码
复制代码
  1. [/code]当 i 和 j 都是 5 的时候,会跳过 5 次遍历(55,56,57,58,59),最终结果为 95,即循环执行了 95 次
  2. [h2]switch 语句[/h2]在 switch 语句中,如果每个条件不写 break 关键字退出判断的话,会发生条件穿透
  3. [code]
复制代码
  1. let i = 25
复制代码
  1. [/code][code]switch (i) {
复制代码
  1.     case 25:
复制代码
  1.         console.log('25')
复制代码
  1.     case 35:
复制代码
  1.         console.log('35')
复制代码
  1.         break;
复制代码
  1.     default:
复制代码
  1.         console.log('default')
复制代码
  1. }
复制代码
  1. // "25"
复制代码
  1. // "35"
复制代码
  1. 复制代码
复制代码
  1. [/code]i 满足第一个 case,所以打印了字符串 25,但是由于没有 break,会[b]无视[/b]第二个判断条件直接执行第二个 case 的语句,如果第二个条件也没有 break 还会继续穿透到 default 中
  2. [b]switch 语句中 case 的判断条件是严格相等,字符串 10 不等于数字 10[/b][b][/b]
  3. [b][/b]
  4. [h2]函数[/h2]
  5. 在 ES6 以前,函数的参数会被保存在一个叫 [code]arguments
复制代码
的对象中在函数执行的时候被创建,它是一个类数组,它有 length 属性代表参数个数,这里的参数个数是执行函数时传入的参数个数,而不是函数定义的参数个数
  1. [/code][list][*][*][*][*][*][*][/list][code]function func(a,b,c) {
复制代码
  1.     console.log(arguments)
复制代码
  1. }
复制代码
  1. [/code][code]func(1,2) // Arguments(2) [1, 2, callee: , Symbol(Symbol.iterator): , length:2]
复制代码
  1. 复制代码
复制代码
  1. [/code]即使定义了 3 个参数, arguments 反映的只是函数运行时候的参数个数,另外 arguments 还有一些比较特殊的特性,[b]非严格模式下[/b]它和函数运行时的参数会建立一个链接,当参数被修改时会反映到 arguments 上,反之同理
  2. [code]
复制代码
  1. function func(a,b,c) {
复制代码
  1.     console.log(arguments)
复制代码
  1.     a = 123
复制代码
  1.     console.log(arguments)
复制代码
  1. }
复制代码
  1. [/code][code]func(1,2)
复制代码
  1. // Arguments(2) [1, 2, callee: , Symbol(Symbol.iterator): , length:2]
复制代码
  1. // Arguments(2) [123, 2, callee: , Symbol(Symbol.iterator): , length:2]
复制代码
  1. 复制代码
复制代码
  1. [/code][code]
复制代码
  1. function func(a,b,c) {
复制代码
  1.     console.log(a)
复制代码
  1.     arguments[0] = 123
复制代码
  1.     console.log(a)
复制代码
  1. }
复制代码
  1. [/code][code]func(1,2)
复制代码
  1. // 1
复制代码
  1. // 123
复制代码
  1. 复制代码
复制代码
  1. [/code]而[b]严格模式[/b]不会建立这种链接,两者完全分离,虽然 ES6 仍可以使用 arguments,但是它[b]已经被废弃[/b],推荐使用剩余运算符(...)
  2. [b]函数的参数是按值传递,不是按引用传递[/b],即如果参数是一个对象,则函数内部通过形参修改这个对象,会反映到所有指向这个参数的变量
  3. [code]
复制代码
  1. let obj = {}
复制代码
  1. [/code][code]function func(o) {
复制代码
  1.     o.a = '123'
复制代码
  1. }
复制代码
  1. [/code][code]console.log(obj) // {}
复制代码
  1. func(obj)
复制代码
  1. console.log(obj) // {a:"123"}
复制代码
  1. 复制代码
复制代码
[code][/code]由于按值传递,所以这里 obj 和形参 o 都指向同一个堆内存的对象,func 内部往这个对象中添加了 a 属性,会同时反映到变量 obj
作者:yeyan1996链接:https://juejin.im/post/5d2f2b0e5188250f3534fc2e

分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:
帖子:
精华:
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP