JS总结

本文仅供自己复习使用

关于解释型语言和编译型语言
编译型语言:只编译一次,后续只需要执行第一次编译的 exe 文件即可,如 C,说得直白一点大致就是 点菜,菜上齐了再吃
解释型语言:运行时才开始翻译,每次都要重新翻译,而且是逐句逐句的翻译的,如 JavaScript,说得直白一点就是 烫火锅

一、变量

1、变量类型

总共8大变量类型,七个基本,一个引用
基本类型(存在栈中):NullUndefinedNumberStringBooleanSymbol(es6确定)、BigInt(es10提出,es11确定)
引用类型(存在堆中):Object

关于 undefined 与 Undefined
Undefined 是类型,而平常我们赋值时使用的 var a = undefined 中的undefined 是变量,而非关键字。对于 ES5 之前的标准及老版浏览器中 undefined 可以被赋值。而真正值为 undefined 的可以通过 void 表达式,为了保险我们通常用void 0void(0) 来表示真正的undefined

关于 null 与 Null
Null 是类型,同时 null 也是关键字,所以直接用 null 不会被修改

关于 Undefined 和 Null
Undefined 表示声明了,未被定义,一般情况下很少有把变量赋值为 undefined 的,但是对于箭头函数如果只有一句话,且无返回值,可以使用var a = () => void aMethod();这样就可以不用写大括号了
Null 表示定义了,但值为空
null == undefinednull !== undefined
0 == '', 0 !== ''

关于 String
其实严格意义上来讲 String 不能被称为字符串类型,而应该被称为字符串中的 UTF16 编码类型
所以使用 charAt、length、charCodeAt 等方法时数出来的个数不对
String 能接受的最大长度是 2^53 - 1,但实际上能识别的却是 0 - 65535
这是因为 JS 引擎执行过程有三个阶段,分别为词法语法分析阶段 -> 预编译阶段 -> 执行阶段,在预编译阶段的长度首先就会被限制,毕竟内存没那么大,在执行期间也会被限制,所以最后长度就不可能有 2^53 - 1 那么多

关于 Number
如果想判断是 +0 还是 -0,可以使用 1 / x,如果得到的是 -Infinity 就说明是 -0,如果是 Infinity 就是 +0
浮点数不能通过 == 或 === 来比较,因为精度问题,但是相比较是否相等可以使用Math.abs(0.1 + 0.2 -0.3) <= Number.EPSILON
为什么不能直接比较 0.1 + 0.2 === 0.3 呢
Number类型通过IEE754的64位来表示数字
JS总结_第1张图片
第0位:符号位,0表示正数,1表示负数
第1-11位:表示指数
第12-63位:储存小数部分即有效数字

如图展示在Number.MAX_VALUE、Number.MAX_SAFE_INTEGER等的取值问题
JS总结_第2张图片
另外整数最大安全数是 Number.MAX_SAFE_INTEGER === Math.pow(2, 53) - 1,而不是 Math.pow(2, 52) - 1,虽然尾数是52位
这是因为尾数部分默认为1,可省略不写,所以最大数就是(52位尾数+1位为1的被省略的数)
所以在 JS 中, Math.pow(2, 53) === Math.pow(2, 53) + 1
计算时发生了什么?
计算机无法对十进制进行计算,所以要根据 IEE754 转为二进制
精度损失可能出现在进制转化和对阶运算(指将两个进行运算的浮点数的阶码对齐)过程中

关于 Symbol
一切非字符串的对象的 key 的集合,一般通过 Symbol 函数创建,如 var a = Symbol('hello'),另外每一个 Symbol 类型的值都是独一无二的,Symbol(1) != Symbol(1)
for of 也跟 Symbol.iterator 有关,因为某对象的 Symbol.iterator 方法指向该对象的遍历器方法,而只要有遍历器方法就能是用 for of

	var obj = {}
	obj[Symbol.iterator] = function() {
		var v = 0;
		return {next:v++,done:v>10}
	}

补充遍历器(Iterator)相关:
Iterator 其实是为各种数据结构提供统一的访问机制,ES6提出的 for … of 就需要实现 Iterator 接口,如果想对一个数据结构使用 for of 或迭代器,可以使用var a = Iterator(b)
数组,字符串,arguments,set,map 都有 Iterator,都能使用 for of
使用三点运算符和解构赋值也是默认去调用 iterator 接口
普通的对象不是可迭代对象,不可以用 for of,但是可以用 for in 取出对象的键
工作原理:①创建一个指针对象(遍历器对象),指向数据结构的起始位置
②第一次调用 next 方法,指针自动指向数据结构的第一个成员
③接下来不断调用 next 方法,指针会一直往后移动
④每次调用 next 方法返回一个包含 value 和 done 的对象,遍历结束后返回 {value: undefined, done: true}

关于for…in
for…in会把原型上的也遍历出来

关于装箱
装箱后的数据和原始数据是不同的,如 3 和 Number 就不一样
我们有时候会见到以下的代码

	var a = '555'
	console.log(a.toString())

总所周知基本类型是没有属性和方法的,毕竟他们已经是值了,那么这里是如何调用的toString()方法呢,其实这里js引擎做了三步:
新建一个装箱的实例
执行该方法
删掉该实例
这个步骤有点像call、bind、apply的实现

关于 valueOf 和 toString
如果是 String 则先使用 toString,若无法得到原始类型,再使用 valueOf,若 valueOf 也得不到,则报 TypeError
如果是其他如 Function、Boolea、Number 等类型,则先使用 valueOf,在使用 toString,都得不到则报错
另外,关于 3.toString() 会报错是因为 JS 引擎理解为 3.(数字字面量) 和 toString(),所以如果想真正实现就要用 3..toString() 或者使用 3 .toString(3和点中间有个空格,空格代表空白符号,使3变成一个单独的 token,.toString() 也变成单独的token)
数字字面量:11.1.0

为何引入 BigInt
因为最大的安全数字为 Number.MAX_SAFE_INTEGER,超过这个数字的计算就会出现计算出错,为了解决这个问题就引入了 BigInt

关于类型转换
NaN == falseNaN == true结果都是false
但是Boolean(NaN) == false
({}) == true({}) == false结果都是false
而且{} == true或者{} == false会报错,因为{}会被认为是作用域
[] == true
[]{} 转为 boolean (用Boolean函数)是 true
JS总结_第3张图片
JS总结_第4张图片
但如果是放在if括号里面会把最后结果通过Boolean()转换类型

{} + [] === 0 这个其实就相当于 [].toNumber
({}) + ([]) === '[object Object]' 这个相当于({}).toString() + [].toString()
{} + ([]) === 0 跟第一个一样,都是 [].toNumber
({}) + [] === '[object Object]' 跟第二个一样
({}) + 1 === '[object Object]1' 相当于 ({}).toString() + 1..toString()
{} + 1 === 1 就是 1.toPrimitive
[] + 2 === '2' 就是 [].toString() + (2).toString()
其中将()里放{}让内核认为这是一个对象,如果直接用{}就会被认为这是代码块
+操作只有让两个值为 Number 或 String 才行
①首先让两个值去 ToPrimitive 也就是说如果是原始值就直接返回,如果不是就通过 valueOf 转换成原始类型,如果是引用类型就用 toString,之后把另外一个也 toString
②如果第一步没有用到 toString,只要有任何一个值为 String 类型,两个就都要转为 String
③如果没有,那就使用 toNumber

关于类型判断
typeof a:无法区分 null数组对象Date类型RegExp类型 ,都会被认为是 'object’
a instanceof Number:只能检查是否为该值隐式原型(__proto__)链上的一个,拿不到具体的
a.__proto__.constructor:除了nullundefined都可以判断出来,但是有时候重写prototype,会遗失constructor,就默认指向Object了
Object.prototype.toString.call(a):会返回[Object xxx],这个xxx就是具体类型


2、变量、函数、类声明

var:会声明提前,可重复声明,可修改,无块级作用域,只有函数作用域和全局作用域

let:不会声明提前,不可重复声明,可修改,有块级作用域

const:不会声明提前,不可重复声明,值不可修改(若为引用类型,则是地址不可修改,但内部指向的属性可变),有块级作用域

class:不会声明提前,不可重复声明,块级作用域,不可以在声明之前调用这个值,哪怕这个在父级作用域上也不行,会在预编译的阶段就报错,如下

	var c = 1;
	function foo(){
	    console.log(c);   //报错:VM2300:3 Uncaught ReferenceError: Cannot access 'c' before initialization
		//但是把 class c 删掉就行
	    class c {}
	}
	foo();

另外:在 class 中直接声明函数,不能加 function,应该直接用 a(){} 这种简便写法,否则报错,class 中找不到 function 标识符

function:普通函数声明,可重复声明,会生成一个执行上下文,每次执行都会有一个新的 AO ,只要没有闭包,其 AO 就会销毁

闭包满足:①函数嵌套,②内部函数引用了外部函数的数据
闭包不会再执行后马上销毁,而是放在内存中,也就是说下次使用时不需要再次创建

运行一个函数之前,会做以下操作 函数形参赋值 -> 函数声明 -> 变量声明,这些操作将创建一个函数的 AO(即活动对象)
其中,变量声明的时候,如果有同名的函数被声明了就不要覆盖声明

关于变量提升,本文最开始有说明 JS 是解释型语言,但 JS 依旧有变量提升,且值被默认设为 undefined,流程如下:
(1)词法和语法分析阶段
①将代码分词(也称为词法分析),简单来讲就是调用 next() 方法,把每个字都拿到,然后和 JS 的关键字和保留字等比较。在这一步中会自动过滤掉空格和注释,最后拿到一个 token 数组(或者列表)
语法分析,如果语法有错,则报错,否则生成一个 AST 树
③形成词法作用域(只跟定义时所处的位置有关)
(2)预编译阶段
①创建执行上下文,JS 会以栈的形式对执行上下文处理,称为函数调用栈(也称为执行上下文栈),执行上下文中有三个东西:作用域链、变量对象、this
②给执行上下文创建变量对象(AO)函数形参赋值(arguments + 形参) -> 函数声明 -> 变量声明(值为 undefined)
③给执行上下文创建作用域链,即当前函数的变量对象(AO) + 之前函数调用栈中的变量对象
④通过调用位置和调用方法来创建this
(3)执行阶段
①通过当前执行上下文作用域链对变量赋值或取值
②根据执行逻辑生成 CPU 能理解的机器码
③执行即可


二、原型

A 的 prototype 是 A_prototype
a(A 的实例)的 __proto__ 是 A_protoype
因为 A_protoype 其实也是通过 new Object 得到的对象,所以 A_protoype.__proto__ 是 Object_protoype
同时 Object 的 prototype 也是 Object_prototype
Object_prototype.__proto__ 是 null,相当于 Object_prototype 没有原型(万恶之源)
Function 的 prototype 是 Function 的 __proto__
Function_prototype 的 __proto__ 是 Object_prototype
JS总结_第5张图片
补充:箭头函数没有原型(prototype),也不能用 new 去构建对象,箭头函数内部没有 arguments 对象


三、回调地狱

异步函数多层直接嵌套,不利于阅读,如图
JS总结_第6张图片
解决方法:①通过 Promise 的链式调用法则来解决

关于 Promise

	var a = new Promise(function(resolve, reject){
		resolve();
		reject();
	})

①在 Promise 的参数如果没有碰到 return 就要一直执行下去,但是第一次如果碰到 resolve() 就把状态从 pending -> fulfilled,如果碰到 reject() 就把状态从 pending -> rejected,如果碰到 return 就马上返回,若 return 前有状态,则执行 thencatch ,若 then 中有某一行有异常,会在这一行终止,然后进入 catch(但如果 catch 写在 then 之前就不会,因为 thencatch 也各自返回一个 promise 对象)。若在 promise 参数里没有遇见 resolverejected 该状态则还是 pending

promise.then(fn1, fn2) 可以接受两个函数,第一个函数代表 fulfilled,第二个为 rejected,返回的 promise 对象是什么状态就是什么状态,
1)如果返回一个值,那么就是 fulfilled 状态
2)如果没有返回值,则自动返回一个 fulfiledpromise对象,不过最好还是自己返回一个自定义的 promise 对象
3)如果抛出一个错,就是 rejected 状态
4)如果返回 Promise.resolve(),那么就是 fulfilled 状态
5)如果返回 Promise.reject(),就是 rejected 状态

promise.catch(fn) 捕获 rejected 状态,其实这个内部调用的是 promise.then(undefined, fn)

Promise.all([promise1, promise2]) 返回一个 promise,如果有一个 rejectedpending,这个返回的 promise 就是 rejectedpending,如果成功,则返回结果数组(该数组与最开始定义时的顺序相同),若失败则返回第一个失败的值

Promise.race([promise1, promise2]) 哪儿个先完成就返回哪个的状态及其值,管他是成功或是失败


②通过 async 和 await 来解决

定义的 async 函数在同步没执行完前就是一个 pending 状态的 promise,同步队列都执行完了,才执行 async 函数
async 函数返回一个值,则会使用 Promise.resolve() 包装一下,await 只能写在 async 函数中
await 里面的 promise 如果有某一个状态为 pending 则直接跳出且返回的 promisepending 状态
如果不显式返回一个值,或显示返回一个 promise 最后 async 函数得到的值里就是空的


③通过 generator 函数

调用 generator 函数获得一个 Iterator 对象,通过 next 获取每个 yield 的值

	function* a{
		yield 2;
		let x = yield 'aaa';
		yield x;
	}
	console.log(a);     // Generator{}
	console.log(a.next());    // 2
	console.log(a.next());   // 'aaa'   此时 x = 'aaa'
	console.log(a.next(99));  // 99
	console.log(a.next());  // undefined

四、关于异步

异步分为宏观和微观。我们通常把我们自身所发起的任务称为宏观任务,把由JS 引擎发起的任务称为微观任务。
先微观后宏观
微观任务:promise、process.nextTick (Node 的)、async
宏观任务:setTimeout、setInterval、I/O、UI渲染、Script 标签里的


五、脚本与模板

脚本就是直接用

你可能感兴趣的:(前端学习)