写在前面: 查漏补缺系列旨在跟随《JavaScript高级程序设计》一书对JS的内容进行复盘,向心流状态前进!
语言基础
严格模式
在ECMA5新增了严格模式的概念,是一种不同的JavaScript解析和执行模型,ECMA3中的不规范写法会进行处理,对于不安全的会话将抛出错误
启用严格模式
-
整个脚本:
javascript在整个脚本第一行添加: "use strict"
-
单个函数
javascript在函数内部第一行添加: "use strict" 整个函数就会按照严格模式进行解析和处理
严格模式的注意点
-
this
this默认执只指向undefined,而不是window
变量
变量声明
变量声明分为三个阶段:
- 创建:在内存中开辟空间
- 初始化: 将变量初始化为undefined
- 赋值: 真正的赋值
var
-
作用域: var声明的作用域范围为函数作用域
-
变量提升: var声明的变量存在变量提升(声明语句默认提升至作用域顶部)
- 准确的说var声明的变量创建 ,初始化两个阶段被提升了
let
-
作用域: let声明的变量作用域为块级作用域(一对花括号)
-
变量提升: let声明的变量不会在作用域中提升
- 准确的说let声明的变量创建阶段被提升了,但初始化没有提升,由于暂时性死区的存在我们无法直观的感受到变量提升的效果
-
同一个作用域不能重复声明
const
-
和let类似唯一不同是: const声明时必须赋值,且赋值之后不能修改
- const实际上保证的是变量的引用地址不变,因此const声明的引用数据类型是可以修改变量的属性或元素的
声明的最佳实践
const一把梭,梭不下去了,回头改成let,绝不用var
数据类型
Null类型
null值表示的是一个空对象的指针,这也是为什么typeof null
返回object
的原因,在你不知道的javascript
一书中是这样解释的: 不同的对象在底层都表示为二进制,在javascript中二进制的前三位都为0的就会判断为object
,null
的二进制表示全是0因此typeof null
返回object
Number类型
js使用IEEE 745格式表示整数和浮点数,证实使用了这种格式才会出现0.1+0.2 !== 0.3
的情况,所有使用这种格式的语言都存在这个问题
- 二进制: 以
'0b'
开头表示二进制,将二进制字符串转换为Number
:Number('0b' + str)
- 十进制
- 十六进制: 以
'0x'
开头表示十六进制,将二进制字符串转换为Number
:Number('0x' + str)
- 八进制: 开头必须是0,严格模式下,前缀0被识别为语法错误,后面的数值超过应用的范围会以十进制进行识别,es6中八进制通过0o前缀进行识别
浮点数:
-
由于浮点数使用的内存空间是整数的两倍,js在存储浮点数时总是优先转换成整数进行存储
- 1.0 => 小数点后面没有数字,当成1来处理
- 10.0 => 同上当作10来处理
-
浮点数精度最高17位,远不如整数精确
-
科学计数法使用
e
来表示,前面是数值,后面是次幂:let floatNum = 3.12e10
3.12乘以10的10次方
值的范围
- 最大值: Infinity
- 最小值: -Infinity
- 要确定一个数字是不是有限大可以使用isFinite() ,超出范围返回
false
类型转换
使用Number(),parseInt(),parseFloat()将非Number类型转换为Number
-
Number()转换规则
-
布尔: true转换为1,false转换为0
-
数值: 直接返回
-
undefined: 返回NaN
-
null: 返回0
-
字符转
- 如果是包含有效的数值字符的字符串(包含前面的正负号),忽略前面的0转换成十进制
- 如果是包含有效的十六进制格式的字符,转换成16进制对应的十进制
- 空字符: 返回0
- 如果包含上述情况之外的字符,返回NaN
-
对象: 先调用valueOf()方法,并按照上述规则进行转换,若转化结果为NaN,则调用toString()方法再将结果按照上述规则转换并返回
-
-
parseInt()转换规则
parseInt转换规则更专注于字符串是否包含数值模式,同时会忽略前面的空格,从第一个非空字符按照下面的规则开始转换
-
若不是数值,正好,负号,返回NaN
-
若是数值,则继续转换第二个字符,知道遇到非数值或者转换完毕,返回转换好的整数
-
同时parseInt()接受第二个参数来指定进制数(大于1小于36),会按照进制数进行转换,默认为十进制,超过范围返回NaN
javascript经典面试题: [1,2,3].map(parseInt) 输出 [1, NaN, NaN] 过程如下: 实际上parseInt的参数是这样的: [1, 0, Array(3)] [2, 1, Array(3)] [3, 2, Array(3)] 执行情况是这样: parseInt(1,0) 进制数为0,十进制,返回1 parseInt(2,1)进制数为1,不在范围,返回NaN parseInt(3,2)进制数为2,但3不是二进制数字,无法转换返回NaN
-
-
parseFloat()转换规则
与parseInt()转换规则相似,但是其没有第二个参数
String类型
ECMAScript中的字符串是不可以改变的,一旦创建,后续修改的时候必须先销毁原始字符串,然后将新值保存到变量中,这也是早期(IE6)浏览器处理字符串拼接慢的原因
转换字符转
-
toString()方法
- 几乎所有的值都有这个方法,null和undefined除外,其作用就是返回当前值的字符串等价物
- 在对数值掉调用这个方法的时候,可以接受一个底数作为参数: 表示以什么底数输出数值的字符串表示,2进制?十进制? 八进制?等,m默认是十进制
-
String()
转换规则如下
- 如果值有toString方法,则调用该方法并返回结果
- 如果是null,返回'null'
- 如果是undefined,返回'undefined'
模板字符串
-
标签函数
模板字符串的高级用法,将一个普通函数以前缀在模板字符串之前的方式,来执行自定义的插值行为,
标签函数接收的第一个参数是原始字符串数组(插值为空字符串),其余参数是对每个插值表达式的求值结果
javascriptconst simpleTag = (strs, ...arr) => { console.log('arr:', arr); console.log('strs:', strs); return 'hello' }; const result = simpleTag`${1}${2}+${3}`; //输出 //arr: [ 1, 2, 3 ] //strs: [ '', '', '+', '' ] console.log(result); //输出 // hello //封装使其生成原有的表达式 const simpleTag = (strs, ...arr) => arr.reduce((acc,curr,i) => acc + curr + strs[i+1],strs[0])
-
使用场景:
-
xss防护
javascriptconst againstXss = (strings, ...arr) => arr.reduce( (acc, curr, i) => acc + curr?.replace(/</g, '<').replace(/>/, '>') + strings[i + 1],//replqace more strings[0] );
-
文本高亮等
-
-
-
获取原始字符串
使用默认的String.raw标签函数可以获取到原始模板字面量的内容,而非转换后的
javascriptconsole.log(`one\ntwo`) //one //two console.log(String.raw`one\ntwo`) //one\ntwo
也可使用标签函数第一个参数,即字符串数组的
.raw
属性,获取到数组元素的原始内容javascriptconst simpleTag = (strs, ...arr) => { strs.raw.map((item) => console.log(item)); };
Symbol类型
符号,基本数据类型,唯一不可变,主要用途是确保对象属性使用唯一的标识符,使用Symbol()
函数来进行初始化,函数接收一个字符串作为参数叫做符号描述,该参数只是方便开发人员辨认,与符号定义无关
ini
const sym1 = Symbol()
const sym2 = Symbol()
sym1 === sym2 //false
//带有符号描述的符号
const sym3 = Symbol("描述")
全局符号注册表
当需要共享使用同一个符号时,可以通过Symbol.for()
方法以一个字符串作为键,在全局符号注册表中创建并重用符号
javascript
const sym1 = Symbol.for("one")
const sym2 = Symbol.for("one")
console.log(typeof sym1) //Symbol
console.log(sym2 === sym1); //true
上述sym1与sym2输出为true的原理是: Symbol.for()对每个字符串执行幂等操作,同一个字符串无论执行多少次都和执行一次结果相同.第一次执行时,Symbol.for()会先检查全局运行时注册表,发现不存在这个符号就创建并添加到注册表中,否则就返回存在的
使用Symbol.keyFor()
查询全局注册表,改方法接收符号返回对应的字符串键,若查询的不是全局符号返回undefined
,若传递的不是符号会抛出错误:
ini
const sym1 = Symbol.for('one');
const sym2 = Symbol.for('one');
console.log(Symbol.keyFor(sym1)); //one
使用符号作为属性
凡是字符串,数字可以作为属性的地方,都可以使用符号作为属性,对象字面量只能使用计算属性语法将符号作为属性
ini
const sym1 = Symbol('one');
const obj1 = {
[sym1]: 1,
};
console.log('obj1:', obj1);
//obj1: { [Symbol(one)]: 1 }
const sym2 = Symbol('two');
const obj1 = {
sym2: 1,
};
console.log('obj2:', obj2);
//obj2: { sym2: 2 }
与符号相关的其他方法
- Object.getOwnPropertySymbols(),获取对象实例自身的符号属性数组
- Object.getOwnPropertyDescriptors(),获取自身包含常规属性和符号属性描述符的对象
- Reflect.ownKeys(),获取自身对象实例两种类型的键
ini
const sym1 = Symbol('one');
const sym2 = Symbol('two');
const obj1 = {
[sym1]: 1,
[sym2]: 2,
a: 3,
};
obj1.__proto__.f = () => {
console.log(1);
};
console.log(Object.getOwnPropertySymbols(obj1));
console.log(Object.getOwnPropertyDescriptors(obj1));
console.log(Reflect.ownKeys(obj1));
//[ Symbol(one), Symbol(two) ]
//{
// a: { value: 3, writable: true, enumerable: true, configurable: true },
// [Symbol(one)]: { value: 1, writable: true, enumerable: true, configurable: true },
// [Symbol(two)]: { value: 2, writable: true, enumerable: true, configurable: true }
//}
//[ 'a', Symbol(one), Symbol(two) ]
并不会获取原型上的属性
JavaScript内置的符号属性
这些内置的符号属性用于暴露语言内部的行为,每个内置符号属性对应都有内置方法(如Symbol.hasInstance属性对应的就是instaceof方法).开发者可以直接访问,覆盖,模拟这些方法,注意这些内置符号属性是不可写,不可枚举,不可配置的
javascriptFunction.prototype[Symbol.hasInstance] = () => { return false; }; 重写不会生效
内置属性最重要的用途就是重新定义他们 ,经典面试题:如何使用for-of遍历对象,就可以在自定义对象上实现
Symbol.iterator
的值,来改变for-of迭代对象时的行为
-
Symbol.asyncIterator
在ECMAscript规范中,这个符号属性表示一个方法: 返回对象默认的AsyncIterator对象,由for-await-of循环使用.
Array,Map,Set,String
默认实现该接口asyncItreator是js的一个接口,用于支持异步迭代操作,该接口由
[Symbol.asyncIterator]
符号进行标识,由通过async function*(){}
语法定义的异步生成器函数实现,异步生成器函数内部可以使用yield
关键字来产生值,这些值会逐一返回给迭代器的调用方.- 异步生成器函数的默认返回值是生成器实例,循环时js内部会自动调用这个函数来获取生成器实例然后进行迭代,生成器函数详解请参阅Symbol.iterator内置属性部分
javascript要创建一个支持asyncIterator的对象,需要为这个对象定义[Symbol.asyncIterator]属性,并将该属性赋值为一个异步生成器函数,在该函数中通过yield产生值,并将值返回给迭代器的调用方. const asyncIteratorObj = { [Symbol.asyncIterator]: async function* () { yield 'hello'; yield 'async'; yield 'iterator'; }, }; 实现了这个接口就可以通过异步迭代器迭代该对象 const autoIteration = async (obj) => { for await (const item of obj) { console.log(item); } //下面的循环与上面的循环是等价的 //const iterator = asyncIteratorObj1[Symbol.asyncIterator](); //for await (const a of iterator) { //console.log(a); //} }; autoIteration(asyncIteratorObj); //依次输出hello,async,iterator //修改以下生成器,让他每次产生的值为一个异步 const asyncIteratorObj1 = { [Symbol.asyncIterator]: async function* () { yield new Promise((resolve) => setTimeout(() => resolve(1), 1000)); yield new Promise((resolve) => setTimeout(() => resolve(2), 2000)); yield new Promise((resolve) => setTimeout(() => resolve(3), 3000)); yield new Promise((resolve) => setTimeout(() => resolve(4), 4000)); }, }; autoIteration(asyncIteratorObj1); //1s后打印 1 //打印1后,2s后打印2 依次类推 由此可见我们通过定义[Symbol.asyncIterator]属性,实现了异步迭代一个对象
-
next()方法: 上述代码中通过异步迭代器我们实现了自动异步迭代,那如何手动进行异步迭代呢?
asyncIterator
接口提供了next()
方法以便于我们可以手动获取下一次异步迭代的值,调用next
方法返回的是一个Promise
该Promise
会在就绪时返回一个对象{value: 值,done:是否迭代已经完成}
javascriptconst asyncIteratorObj = { [Symbol.asyncIterator]: async function* () { yield new Promise((resolve) => setTimeout(() => resolve(1), 1000)); yield new Promise((resolve) => setTimeout(() => resolve(2), 2000)); yield new Promise((resolve) => setTimeout(() => resolve(3), 3000)); yield new Promise((resolve) => setTimeout(() => resolve(4), 4000)); }, }; const manualIteration = async () => { const iterator = asyncIteratorObj[Symbol.asyncIterator]();//通过异步生成器本身获取到异步生成器实例 iterator.next().then((data) => console.log(data)); const res = await iterator.next(); console.log('res:', res); }; manualIteration(); //{ value: 1, done: false } //res: { value: 2, done: false } 若done属性为true,标识对象已经迭代完毕,这里我们只迭代到2,还未迭代完成所以是false
-
Symbol.hasInstance
根据ECMAScript规范,这个符号作为一个属性表示: 一个方法,该方法决定一个构造器对象是否认可一个对象是它的实例,由
instanceof
操作符使用.这个属性定义在Function
原型上,因此所有的函数和类都可以调用,instanceof
操作符可以用来确定一个对象实例的原型链上是否有原型.javascriptclass A {} const a = new A(); //下面的使用方式是等价的 console.log(a instanceof A); //true console.log(A[Symbol.hasInstance](a)); //true //根据原型链的特性,可以在构造函数中重覆盖[Symbol.hasInstance]方法,实现自定义的instanceof行为 class B { static [Symbol.hasInstance]() { return false; } } const b = new B(); console.log(b instanceof B); //false console.log(B[Symbol.hasInstance](b));//fasle
-
Symbol.isConcatSpreadable
该内置属性为一个布尔值,用以配置一个对象被用作
Array.prototype.concat()
的参数时是否展开其元素.- 对于数组对象作为该参数时,默认按照数组元素展开然后进行连接.
- 对于类数组对象作为该参数时,默认该对象整体作为新数组元素进行连接
通过修改内置属性可以修改
concat
的行为iniconst arr1 = [1, 2, 3]; const arr2 = [5, 6, 7, 8]; console.log(arr1.concat(arr2)); //[1, 2, 3, 5,6, 7, 8] arr2[Symbol.isConcatSpreadable] = true; console.log(arr1.concat(arr2)); //[1, 2, 3, 5,6, 7, 8] arr2[Symbol.isConcatSpreadable] = false; console.log(arr1.concat(arr2)); //[ 1, 2, 3, [ 5, 6, 7, 8, [Symbol(Symbol.isConcatSpreadable)]: false ] ] const obj1 = { a: 1, b: 2, }; console.log(arr1.concat(obj1)); //[ 1, 2, 3, { a: 1, b: 2 } ] obj1[Symbol.isConcatSpreadable] = false; console.log(arr1.concat(obj1)); //[ 1, 2, 3, { a: 1, b: 2, [Symbol(Symbol.isConcatSpreadable)]: false } ] obj1[Symbol.isConcatSpreadable] = true; console.log(arr1.concat(obj1)); //[ 1, 2, 3 ]
-
Symbol.iterator
该内置属性表示: 一个方法,方法返回对象默认的迭代器,由
for-of
语句使用.值为一个生成器函数.一些数据类型用于默认的迭代器行为,可以直接使用for of
进行迭代:Array,String,Map,Set
,其他的类型则没有:Object
-
生成器函数
- 生成器函数由
function*
语法定义,包含yeild关键字
用于指定函数的暂停点,第一次调用生成器函数时不会执行函数的任何代码,而是返回一个称为Generator
的迭代器,该迭代器通过next()
方法来控制生成器函数的执行, - yeild:该关键字只能在生成器函数中使用,当生成器函数执行到
yeild
时,会暂停生成器函数的执行,并将yeild
后面表达式的值作为生成器函数的当前暂停状态,返回给next()
函数 - next(): 是
Generator
迭代器的方法用于控制生成器函数的执行,迭代器调用next()
时,生成器函数会执行,直到遇到yeild
关键字,然后暂停,并把yeild
生成的值进行返回,返回值固定为两个属性{value,done}
,vaule
的值是yeild返回的值
,done
表示迭代是否完毕ture
表示迭代完毕.next()
方法可以接受一个参数,该参数会成为生成器函数中上一个yield
表达式的返回值。
- 生成器函数由
-
实现可迭代对象
inilet obj2 = { name: 'qqq', age: 12, sex: '男', }; obj2[Symbol.iterator] = function* () { let index = 0; let arr = Object.keys(obj2); while (index < arr.length) { yield arr[index]; index++; } }; for (const key of obj2) { console.log(key); }
-
-
Symbol.match
这个符号作为属性表示: 一个正则表达式方法,该方法用正则表达式去匹配字符串,由
String.prototype.match()
方法使用.String.prototype.match()
会使用以[Symbol.match]
为键的函数来对正则表达式求值,正则表达式的原型上默认有这个函数的定义,因此所有正则表达式实例默认是这个String
方法的有效参数.typescriptRegExp.prototype[Symbol.match] //f[Symbol.match](){[native code]} 实际上string.match()求值的时候就是调用的而正则表达式原型上的[Symbol.match]方法
该方法接收一个正则表达式为参数,若传递的式非正则表达式会导致该值转换成正则表达式再求值.可以通过重新定义
Symbol.match
以取代默认的行为,使match()方法使用非正则表达式实例javascriptclass Test { static [Symbol.match](target) { return target.includes('regex'); } } console.log('this is regex'.match(Test)); //true
-
Symbol.replace ,Symbol.search,Symbol.split与Symbol.match相同,正则表达式的原型上默认有这个些函数,调用
replace,search,split
方法时,实际上调用的是正则表达式原型上对应的内置属性的方法 -
Symbol.species
表示一个函数值,该函数作为派生对象时的构造函数,主要用于控制派生对象的构造,在进行构造函数的操作时(如map ,filter)会触发该函数,确保派生对象的类型与原始对象的类型一致,,静态获取器(getter): 当访问属性时会自动调用调用该函数来返回值.用
Symbol.species
定义静态的获取器(getter)方法,当访问Symbol.species
属性时,自动调用getter方法返回对象类型,确保类型一致性javascriptclass B extends Array { static get [Symbol.species]() { return Array; } } const b = new B(); console.log(b instanceof Array);//true console.log(b instanceof B); //true const newB = b.map(() => 1); //进行一下操作 console.log(newB instanceof Array); //true console.log(newB instanceof B); //false
-
Symbol.toPrimitive
该内置属性是一个方法用于控制自定义对象转换为原始值时的行为,许多内置操作都会尝试将对象转换成原始值(数字,字符串以及未指定的类型),该方法接收一个字符串参数,可以是
string
,number
,default
即对象转换的目标类型javascriptconst obj1 = { [Symbol.toPrimitive](target) { if (target === 'string') { return 'string'; } if (target === 'number') { return 1; } return 'default'; }, }; const obj2 = {}; console.log(Number(obj1)); //1 console.log(Number(obj2)); //NaN console.log(String(obj1)); //string console.log(String(obj2)); //[object object] console.log(3 + obj1); //3default console.log(3 + obj2); //3[object object]
-
Symbol.toStringTag
该内置属性的值是一个字符串,作为对象的默认字符串描述,由
Object.prototype.toString()
使用,通过toString()
方法获取对象标识的时候,会检索该属性的值,默认为object
iniconst obj = { [Symbol.toStringTag]: '啥也不是', }; console.log(obj.toString()); //[object 啥也不是]
-
Symbol.unscopables
该属性值是一个对象,该对象的属性都会从关联对象的
with
环境中排除,设置这个符号并将其映射对应的属性的键值为true
,就能阻止该属性出现在with
环境中了,javascriptconst obj = {foo: 'bar'} with(obj){ console.log(foo) //bar } obj[Symbol.unscopables] = { foo: true } with(obj){ console.log(foo) //ReferenceError }
-
with简介
typescript`with` 是 JavaScript 中的一个语句,用于创建一个临时的作用域,以便在其中访问指定对象的属性,而不必使用该对象的名称前缀。尽管 `with` 在某些情况下可能方便,但它已被视为不推荐使用,并且在严格模式下是禁止的。 `with` 语句的基本语法如下: with (object) { // 在这个作用域内可以直接访问 object 的属性 // 不需要使用对象名称前缀 // 例如,可以直接访问 object.property } 在 `with` 语句的作用域中,你可以直接访问指定对象的属性,而不必在每次访问时重复指定对象的名称。这可以使代码更简洁,但也引入了一些潜在的问题,因此 `with` 被视为不安全和难以调试。 潜在问题包括: 1. 作用域歧义:`with` 可能会引入变量名冲突,因为在 `with` 语句块中的标识符会首先在指定对象的属性中查找,然后才在外部作用域中查找。这可能导致意外的行为。 2. 性能问题:`with` 语句可能会导致 JavaScript 引擎在执行时难以优化代码,因此可能会导致性能下降。 3. 调试困难:在使用 `with` 时,调试器可能无法正确显示变量的值,因为作用域被改变了。 由于这些问题,通常不推荐使用 `with` 语句,而应该使用明确的对象引用来访问属性,以使代码更可读、可维护和可预测。在严格模式下,`with` 语句是禁用的,无法使用。
-
语句
标签语句
标签语句用于给代码块添加标签,方便代码跳转和控制循环
语法:
ini
标签: js代码
start: const num = 0
- 使用标签语句结合break跳出双层循环
ini
let num = 0;
start: for (let a = 0; a <= 10; a++) {
for (let b = 0; b <= 10; b++) {
if (a === 0 && b === 10) {
break start; //break只能跳转到封闭语句的标签,如for,while,do..while等
}
num++;
}
}
console.log(num); //10,如果没有使用标签语句直接break,会输出120