一、数据类型
JavaScript有哪些数据类型,它们的区别?
7种原始数据类型:undefined\boolean\number\string\bigInt\symbol\null,前6种使用typeof运算符检查,特殊的null使用其检测结果为object;
3种引用数据类型:对象、数组、函数
区别:
原始数据类型直接存储在栈(stack)中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储;
引用数据类型存储在堆(heap)中的对象,占据空间大、大小不固定。如果存储在栈中,将会影响程序运行的性能;
引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。
数据类型检测的方式有哪些
-
typeof
typeof:undefined\boolean\number\string\bigInt\symbol正确
typeof null\object\array返回object
typeof function返回function -
instanceof,用于检测某个对象的原型链是否包含某个构造函数的 prototype 属性。
instanceof 适用于检测对象,它是基于原型链运作的。
instanceof 除了适用于任何 object 的类型检查之外,也可以用来检测内置对象,比如:Array、RegExp、Object、Function
instanceof 对基本数据类型检测不起作用,主要是因为基本数据类型没有原型链 -
constructor有两个作用,一是判断数据的类型,二是对象实例通过 constrcutor 对象访问它的构造函数
fucntion Fn(){
};
var fn=new Fn();
console.log(f.constructor===Fn);// true;
注意:如果创建一个对象来改变它的原型,constructor就不能用来判断数据类型了,比如:
function Fn(){
};
Fn.prototype = new Array();
var f = new Fn();
console.log(f.constructor===Fn); // false
console.log(f.constructor===Array); // true
- hasOwnProperty判断属性是否存在于当前对象实例中(而不是原型对象中)
const info = { title: “书”, name: “大白” };
console.log(info.hasOwnProperty(“title”)); // true - Objext.propertype.toString.call()通用
使用 Object 对象的原型方法 toString 准确判断数据类型
判断数组的方式有哪些
5种:
obj instanceof Array;通过instanceof方法
Object.prototype.toString.call()通过对象原型方法;
Array.prototype.isPrototypeOf(obj)通过数组的原型方法,判断数组是否在obj的原型链上
obj.proto === Array.prototype;通过原型链判断
Array.isArrray(obj);ES6方法
判断数组中是否包含某个值
方法一:array.indexOf(item,start):元素在数组中的位置,如果没与搜索到则返回 -1。
方法二:array.find()
数组实例的find()用于找出第一个符合条件的数组元素。它的参数是一个回调函数,所有数组元素依次遍历该回调函数,直到找出第一个返回值为true的元素,然后返回该元素,否则返回undefined。
方法三:array.findIndex()
array.findIndex()和array.find()十分类似,返回第一个符合条件的数组元素的位置,如果所有元素都不符合条件,则返回-1。
方法四:for()循环
遍历数组,然后 if 判断
方法五、include()方法:
arr.includes(searchElement)方法用来判断一个数组是否包含一个指定的值,如果是返回 true,否则false。searchElement:必须。需要查找的元素值。
方法六.Array some() 方法,类似于filter()
some() 方法用于检测数组中的元素是否满足指定条件(函数提供)。
some() 方法会依次执行数组的每个元素:
如果有一个元素满足条件,则表达式返回true , 剩余的元素不会再执行检测。
如果没有满足条件的元素,则返回false。
注意: some() 不会对空数组进行检测。
注意: some() 不会改变原始数组。
null和undefined区别
undefined 代表的含义是未定义,null 代表的含义是空对象。一般变量声明了但还没有定义的时候会返回 undefined,null主要用于赋值给一些可能会返回对象的变量,作为初始化。
当对这两种类型使用 typeof 进行判断时,Null 类型化会返回 “object”,这是一个历史遗留的问题。当对这两种类型使用 typeof 进行判断时,Null 类型化会返回 “object”,这是一个历史遗留的问题。当使用双等号对两种类型的值进行比较时会返回 true,使用三个等号时会返回 false。
null==undefined //true
null===undefined //false
typeof null 的结果是什么,为什么?
在 JavaScript 第一个版本中,所有值都存储在 32 位的单元中,每个单元包含一个小的 类型标签(1-3 bits) 以及当前要存储值的真实数据。类型标签存储在每个单元的低位中,共有五种数据类型:
000: object - 当前存储的数据指向一个对象。
1: int - 当前存储的数据是一个 31 位的有符号整数。
010: double - 当前存储的数据指向一个双精度的浮点数。
100: string - 当前存储的数据指向一个字符串。
110: boolean - 当前存储的数据是布尔值。
有两种特殊数据类型:
undefined的值是 (-2)30(一个超出整数范围的数字);
null的第0位到第31位皆为0;也就是说null的标签为也为000,所以会被判定为object
在 JavaScript 最初的实现中,JavaScript 中的值是由一个表示类型的标签和实际数据值表示的。对象的类型标签是 0。由于 null 代表的是空指针(大多数平台下值为 0x00),因此,null 的类型标签是 0,typeof null 也因此返回 “object”
intanceof 操作符的实现原理,手写
instanceof 运算符用于判断构造函数的 prototype 属性是否出现在对象的原型链中的任何位置。
function myInstanceof(left, right) {
// 获取对象的原型
let proto = Object.getPrototypeOf(left)
// 获取构造函数的 prototype 对象
let prototype = right.prototype;
// 判断构造函数的 prototype 对象是否在对象的原型链上
while (true) {
if (!proto) return false;
if (proto === prototype) return true;
// 如果没有找到,就继续从其原型上找,Object.getPrototypeOf方法用来获取指定对象的原型
proto = Object.getPrototypeOf(proto);
}
}
为什么0.1+0.2 ! == 0.3,如何让其相等
浮点数转二进制时丢失了精度,计算完再转回十进制时和理论结果不同;
读音:艾pe西隆
对于这个问题,一个直接的解决方法就是设置一个误差范围,通常称为“机器精度”。对JavaScript来说,这个值通常为2-52,在ES6中,提供了Number.EPSILON属性,而它的值就是2-52,只要判断0.1+0.2-0.3是否小于Number.EPSILON,如果小于,就可以判断为0.1+0.2 ===0.3
function numberepsilon(arg1,arg2){
return Math.abs(arg1 - arg2) < Number.EPSILON;
}
console.log(numberepsilon(0.1 + 0.2, 0.3)); // true
如何获取安全的 undefined 值?
undefined 在 JavaScript 中不是一个保留字,这意味着可以使用 undefined 来作为一个变量名,但是这样的做法是非常危险的,它会影响对 undefined 值的判断。表达式 void ___ 没有返回值,因此返回结果是 undefined。void 并不改变表达式的结果,只是让表达式不返回值。因此可以用 void 0 来获得 undefined。
typeof NaN 的结果是什么?
Number
NaN 即 Not a Number , 不是一个数字,JS中number数据类型中除了浮点型和整数型还有一个特殊的值 NaN。
NaN, 它是用来表示是否属于number类型的一种状态: 是或否。而不是一个确切的值。
所以 NaN != NaN 为true, 因为它是一个范围,而不能代表一个确定的值。
isNaN 和 Number.isNaN 函数的区别?
函数 isNaN 接收参数后,会尝试将这个参数转换为数值,任何不能被转换为数值的的值都会返回 true,因此非数字值传入也会返回 true ,会影响 NaN 的判断;
函数 Number.isNaN 会首先判断传入参数是否为数字,如果是数字再继续判断是否为 NaN ,不会进行数据类型的转换,这种方法对于 NaN 的判断更为准确。
== 操作符的强制类型转换规则?
NaN不等于任何其它类型
Boolean 与其它类型进行比较,Boolean转换为Number
String 与 Number进行比较,String 转化为Number
如果一个操作值是对象,另一个不是,则调用对象的valueOf()方法,得到的结果按照前面的规则进行比较
null 与 undefined进行比较结果为true
null,undefined与其它任何类型进行比较结果都为false
引用类型与引用类型,直接判断是否指向同一对象
其他值到字符串string的转换规则?
1.Null 和 Undefined 类型 ,null 转换为 “null”,undefined 转换为 “undefined”,
2.Boolean 类型,true 转换为 “true”,false 转换为 “false”。
3.Number 类型的值直接转换,不过那些极小和极大的数字会使用指数形式。
4.Symbol 类型的值直接转换,但是只允许显式强制类型转换,使用隐式强制类型转换会产生错误。
5.对普通对象来说,除非自行定义 toString() 方法,否则会调用 toString()(Object.prototype.toString())来返回内部属性 [[Class]] 的值,如"[object Object]"。如果对象有自己的 toString() 方法,字符串化时就会调用该方法并使用其返回值。
1+'123' // '1123'
1 + {
} //"1[object Object]"
[] // ''
隐式转换:+运算符,下面的情况存在时会触发转换
有一方为String,那么另一方也会被转为String
一方为Number,另一方为引用类型,双方都转为String
一方为Number,另一方为原始值类型,则将原始值类型转换为Number
其他值到数字值number的转换规则?
1.Undefined 类型的值转换为 NaN。
2.Null 类型的值转换为 0。
3.Boolean 类型的值,true 转换为 1,false 转换为 0。
4.String 类型的值转换如同使用 Number() 函数进行转换,如果包含非数字值则转换为 NaN,空字符串为 0。
5.Symbol 类型的值不能转换为数字,会报错。
6.BigInt去除“n”
7.对象(包括数组)会首先被转换为相应的基本类型值,如果返回的是非数字的基本类型值,则再遵循以上规则将其强制转换为数字。
会按照下面的步骤去执行:
先调用对象的 Symbol.toPrimitive 这个方法,如果不存在这个方法;
再调用对象的 valueOf 获取原始值,如果获取的值不是原始值;
再调用对象的 toString 把其变为字符串;
最后再把字符串基于Number()方法转换为数字;
如果 valueOf() 和 toString() 均不返回基本类型值,会产生 TypeError 错误;
let obj ={
name:'xxx'
}
console.log(obj-10) // 数学运算:先把obj隐式转换为数字,再进行运算
//运行机制
obj[Symbol.toPrimitive] //undifined
obj.valueof() // {name:xxx}
obj.toString() // [object object]
Number ("[object object]") // NaN
NaN-10 // NaN
数组转数字:Array => Number
空数组转为0: [] --> 0
含有一个元素且为数字或数字字符串则转换为数字:[1]或['1'] --> 1
其余情况转为NaN
引用类型转数字:
除了上述的数组,日期(Date)之外的引用类型(Object)都转为NaN
其他值到布尔类型boolean的值的转换规则?
除了“0/NaN/空字符串/null/undefined”五个值是false,其余都是true
逻辑运算符(比如 || 和 &&)是在内部做了 boolean 类型转换,但实际上返回的是原始操作数的值,即使他们都不是 boolean 类型。
let x = ‘hello’ && 123 // x === 123
|| 和 && 操作符的返回值?
|| 和 && 首先会对第一个操作数执行条件判断,如果其不是布尔值就先强制转换为布尔类型,然后再执行条件判断。
对于 || 来说,如果条件判断结果为 true 就返回第一个操作数的值,如果为 false 就返回第二个操作数的值。
&& 则相反,如果条件判断结果为 true 就返回第二个操作数的值,如果为 false 就返回第一个操作数的值。
Object.is() 与比较操作符 “===”、“==”
的区别?
== 两边类型不同时,进行强制类型转换后进行比较;
=== 如果两边类型不相等,不会进行强制类型转换,直接返回false;
Object.is() 一般情况下和三个等号判断一致,但是特俗的,-0 和+0 不再相等;两个NAN 是相等的;
什么是 JavaScript 中的包装类型?
在 JavaScript 中,基本类型是没有属性和方法的,但是为了便于操作基本类型的值,在调用基本类型的属性或方法时 JavaScript 会在后台创建一个对应的基本包装类型的对象,有三种特殊的引用类型String、Number、Boolean;
const a = "abc";
a.length; // 3
a.toUpperCase(); // "ABC"
如:在访问’abc’.length时,JavaScript 将’abc’在后台转换成String(‘abc’),然后再访问其length属性;
JavaScript 中如何进行隐式类型转换?
显示转换:
转换为数值类型:Number(mix)、parseInt(string,radix)、parseFloat(string)
转换为字符串类型:toString(radix)、String(mix)
转换为布尔类型:Boolean(mix)
string、number、Boolean
对象转基本类型:
从引用类型到基本类型的转换,也就是拆箱的过程中,会遵循ECMAScript规范规定的toPrimitive原则,一般会调用引用类型的valueOf和toString方法,一般转换成不同类型的值遵循的原则不同,例如:
引用类型转换为Number类型,先调用valueOf,再调用toString
引用类型转换为String类型,先调用toString,再调用valueOf
若valueOf和toString都不存在,或者没有返回基本类型,则抛出TypeError异常。
隐士转换:
递增递减操作符(包括前置和后置)、一元正负符号操作符
加法运算操作符
乘除、减号运算符、取模运算符
逻辑操作符(!、&&、||)
关系操作符(<, >, <=, >=)
相等操作符(==)
+ 操作符什么时候用于字符串的拼接?
其中一个操作数是字符串;
或者是一个操作数是对象,经过转化后是字符串类型;
隐式转换:+运算符,下面的情况存在时会触发转换
有一方为String,那么另一方也会被转为String
一方为Number,另一方为引用类型,双方都转为String
一方为Number,另一方为原始值类型,则将原始值类型转换为Number
1+'123' // '1123'
1 + {
} //"1[object Object]"
为什么会有BigInt的提案?
JavaScript中Number.MAX_SAFE_INTEGER表示最⼤安全数字,计算结果是9007199254740991,即在这个数范围内不会出现精度丢失(⼩数除外)。但是⼀旦超过这个范围,js就会出现计算不准确的情况,这在⼤数计算的时候不得不依靠⼀些第三⽅库进⾏解决,因此官⽅提出了BigInt来解决此问题。
object.assign和扩展运算法是深拷贝还是浅拷贝,两者区别
浅拷贝
Object.assign()方法接收的第一个参数作为目标对象,后面的所有参数作为源对象。只会拷贝源对象自身的并且可枚举的属性到目标对象。会触发 ES6 setter。
扩展操作符(…)使用它时,数组或对象中的每一个值都会被拷贝到一个新的数组或对象中。它不复制继承的属性或类的属性,但是它会复制ES6的symbols 属性。
如何判断一个对象是空对象
Json.stringify(Obj) == ‘{}’
Object.keys(Obj).length < 0
数据的赋值和引用数据类型的深浅拷贝
赋值:原始类型会直接在栈中开辟一块空间,完整复制变量值;引用数据类型的赋值是复制引用地址(在栈中开辟一块空间,存储赋值的数据对应的堆中的存储地址)
浅拷贝
浅拷贝是创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝(第一层属性的精确拷贝)。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。
https://www.jianshu.com/p/35d69cf24f1f
浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。
深拷贝
将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,修改新对象不会影响原对象;
浅拷贝的实现方式5种:
Object.assign(target,source1,…) :该方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象
1. 如果目标对象和源对象有同名属性,或者多个源对象有同名属性,则后面的属性会覆盖前面的属性。
2.如果该函数只有一个参数,当参数为对象时,直接返回该对象;当参数不是对象时,会先将参数转为对象然后返回。
3. 因为null 和 undefined 不能转化为对象,所以第一个参数不能为null或 undefined,会报错。
函数库lodash的_.clone方法
展开运算符
Array.prototype.concat()该方法不会更改现有数组,返回一个新数组
Array.prototype.slice()该方法不会改变原始数组
深拷贝的实现方式4种:
JSON.parse(JSON.stringify()):缺点:这种方法虽然可以实现数组或对象深拷贝,但不能处理函数、undefined,symbol和正则,函数经过处理后变为null,正则变为空对象
函数库lodash的_.cloneDeep
import cloneDeep from 'lodash/cloneDeep';
const test1 = {
name: 'xx',
age: 11
};
const test2 = cloneDeep(test1);
const test3 = test1;
console.log('test12 :>> ', test1 === test2); // false
console.log('test13 :>> ', test1 === test3); // true
jQuery的$.extend(deepCopy, target, object1, [objectN])//第一个参数为true,就是深拷贝
遍历对象、数组直到里边都是基本数据类型,然后再去复制,就是深度拷贝
如何用递归实现一个深拷贝
1.最简单的深拷贝:
function clone(target) {
if (typeof target === 'object') {
let cloneTarget = {
};
for (const key in target) {
cloneTarget[key] = clone(target[key]);
}
return cloneTarget;
} else {
return target;
}
};
2.在1的基础上兼容数组:
module.exports = function clone(target) {
if (typeof target === 'object') {
let cloneTarget = Array.isArray(target) ? [] : {
};
for (const key in target) {
cloneTarget[key] = clone(target[key]);
}
return cloneTarget;
} else {
return target;
}
};
3.考虑循环引用:对象的属性间接或直接的引用了自身的情况,导致递归陷入死循环。
解决方法:可以额外开辟一个存储空间,来存储当前对象和拷贝对象的对应关系,当需要拷贝当前对象时,先去存储空间中找,有没有拷贝过这个对象,如果有的话直接返回,如果没有的话继续拷贝,这样就巧妙化解的循环引用的问题。这个存储空间,需要可以存储key-value形式的数据,且key可以是一个引用类型,我们可以选择Map这种数据结构。
function clone(target, map = new Map()) {
if (typeof target === 'object') {
let cloneTarget = Array.isArray(target) ? [] : {
};
if (map.get(target)) {
return map.get(target);
}
map.set(target, cloneTarget);
for (const key in target) {
cloneTarget[key] = clone(target[key], map);
}
return cloneTarget;
} else {
return target;
}
};
二、ES6
let、const、var的区别
是否有块级作用域:是 是 否
是否存在变量提升:否 否 是
是否添加全局属性:否 否 是
是否允许重复声明:否 否 是
是否存在暂时性死区:是 是 否
初始值设置:否 是 否
是否可以改变指针指向:是 否 是
块级作用域:var是不受限于块级的,而let,const有块级作用域
重复声明:var声明变量可以重复声明,而let,const不可以重复声明
全局属性:var会与window相映射(会挂一个全局属性),而let,const没有
var可以在声明的上面访问变量,存在变量提升;
let,const有暂存死区,在声明的上面访问变量会报错;
初始值:const声明之后必须赋值,否则会报错
指针指向:const定义不可变的量,不可以改变指针指向
let/const暂时性死区
var name='jack';
{
name='bob';
let name; //Uncaught ReferenceError: Cannot access 'name' before initialization
}
记住 ES6 中的一个特性,如果区块中存在 let 和 const 命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。因为JS清楚地感知到了 name 是用 let 声明在当前这个代码块内的,所以会给这个变量 name 加上了暂时性死区的限制,它就不往外探出头了.
那么,如果我们把上面的let name;去掉,程序也将正常运行, name 的值也被成功修改为了bob,就是正常地按照作用域链的规则,向外探出头去了。
const对象的属性可以修改吗
const保证的并不是变量的值不能改动,而是变量指向的那个内存地址不能改动。
基本数据类型:对于基本类型的数据(数值、字符串、布尔值),其值就保存在变量指向的那个内存地址,因此等同于常量。
引用数据类型:但对于引用类型的数据(主要是对象和数组)来说,变量指向数据的内存地址,保存的只是一个指针,const只能保证这个指针是固定不变的,至于它指向的数据结构是不是可变的,就完全不能控制了。
如果new一个箭头函数的会怎么样
箭头函数是ES6中的提出来的,它没有prototype,也没有自己的this指向,更不可以使用arguments参数,所以不能New一个箭头函数;
new操作符实现步骤:
• 以构造器的prototype属性为原型,创建新对象;
• 将this(也就是上一句中的新对象)和调用参数传给构造器,执行;
• 如果构造器没有手动返回对象,则返回第一步创建的新对象,如果有,则舍弃掉第一步创建的新对象,返回手动return的对象。
function myNew(fn, ...args) {
let obj = Object.create(fn.prototype);
let res = fn.call(obj, ...args);
return res instanceof Object ? res : obj;
}
用法如下:
function person(name, age) {
this.name = name
this.age = age
}
let p = myNew(person, '布兰', 12)
console.log(p) // { name: '布兰', age: 12 }
箭头函数与普通函数的区别
1.箭头函数更简洁:
如果没有参数,就直接写一个空括号即可
如果只有一个参数,可以省去参数的括号
如果有多个参数,用逗号分割
如果函数体的返回值只有一句,可以省略大括号
如果函数体不需要返回值,且只有一句话,可以给这个语句前面加一个void关键字。最常见的就是调用一个函数:let fn = () => void doesNotReturn();
2. 箭头函数没有自己的this:箭头函数不会创建自己的this, 所以它没有自己的this,它只会在自己作用域的上一层继承this。箭头函数中this的指向在它在定义时已经确定了,之后不会改变。
3. 箭头函数指向的this不会改变:
var id = 'GLOBAL';
var obj = {
id: 'OBJ',
a: function(){
console.log(this.id);
},
b: () => {
console.log(this.id);
}
};
obj.a(); // 'OBJ'
obj.b(); // 'GLOBAL'
new obj.a() // undefined
new obj.b() // Uncaught TypeError: obj.b is not a constructor
对象obj的方法b是使用箭头函数定义的,这个函数中的this就永远指向它定义时所处的全局执行环境中的this,即便这个函数是作为对象obj的方法调用,this依旧指向Window对象。需要注意,定义对象的大括号{}是无法形成一个单独的执行环境的,它依旧是处于全局执行环境中。
4. call()、apply()、bind()等方法不能改变箭头函数中this的指向
5,箭头函数不能当作构造函数使用:构造函数在new的步骤在上面已经说过了,实际上第二步就是将函数中的this指向该对象。 但是由于箭头函数时没有自己的this的,且this指向外层的执行环境,且不能改变指向,所以不能当做构造函数使用。
6.箭头函数没有自己的arguments:在箭头函数中访问arguments实际上获得的是它外层函数的arguments值。
7.箭头函数没有prototype
8.箭头函数不能用作generator,不能使用yeild关键字
箭头函数的this指向哪⾥?
箭头函数不同于传统JavaScript中的函数,箭头函数并没有属于⾃⼰的this,它所谓的this是捕获其所在上下⽂的 this 值,作为⾃⼰的 this 值,并且由于没有属于⾃⼰的this,所以是不会被new调⽤的,这个所谓的this也不会被改变。
扩展运算符的作用及使用场景
1.对象的扩展运算符(…) 浅拷贝,用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中。
2.数组扩展运算符:数组的扩展运算符可以将一个数组转为用逗号分隔的参数序列,且每次只能展开一层数组。
let bar = { a: 1, b: 2 };
let baz = { ...bar }; // { a: 1, b: 2 }
// 等价于 Object.assign 该方法的第一个参数是目标对象,后面的参数都是源对象。(如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性)。
let baz = Object.assign({}, bar); // { a: 1, b: 2 }
// 如果用户自定义的属性,放在扩展运算符后面,则扩展运算符内部的同名属性会被覆盖掉
let baz = {...bar, ...{a:2, b: 4}}; // {a: 2, b: 4}
// 利用这个特性,可以方便的修改对象的部分属性
console.log(...[1, [2, 3, 4], 5]) // 1 [2, 3, 4] 5
// 将数组转换为参数序列
function add(x, y) {
return x + y;
}
const numbers = [1, 2];
add(...numbers) // 3
// 复制数组
// 合并数组
// 拓展运算符和解构赋值结合,生成新数组
const [first, ...rest] = [1, 2, 3, 4, 5];// first 1 rest [2, 3, 4, 5]
// 注意:用于数组赋值时,拓展运算符只能放在参数的最后一位
const [first, ...rest, last] = [1, 2, 3, 4, 5]; // 报错
// 将字符串转为真正的数组
[...'hello'] // [ "h", "e", "l", "l", "o" ]
// 任何 Iterator 接口的对象,都可以用扩展运算符转为真正的数组,下面代码可以用于替换es5中的Array.prototype.slice.call(arguments)写法
function foo() {
const args = [...arguments];
}
// 使用Math函数获取数组中特定的值
const numbers = [9, 4, 7, 1];
Math.min(...numbers); // 1
对对象与数组的解构的理解
解构是 ES6 提供的一种新的提取数据的模式,这种模式能够从对象或数组里有针对性地拿到想要的数值。
1.在解构数组时,以元素的位置为匹配条件来提取想要的数据的
const [a, b, c] = [1, 2, 3] // a、b、c分别被赋予了数组第0、1、2个索引位的值
2.在解构对象时,是以属性的名称为匹配条件,来提取想要的数据的。注意,对象解构严格以属性名作为定位依据,所以就算调换了了属性的位置,结果也是一样的
如何提取高度嵌套的对象里的指定属性?
使用冒号+{
目标属性名}一步解构,例如:
const {
classes: {
stu: {
name } }} = school
console.log(name) // 'Bob'
对 rest 参数的理解
扩展运算符被用在函数形参上时,它还可以把一个分离的参数序列整合成一个数组。经常用于获取函数的多余参数,或者像上面这样处理函数参数个数不确定的情况
function mutiple(...args) {
console.log(args)
}
mutiple(1, 2, 3, 4) // [1, 2, 3, 4]
ES6中模板语法与字符串处理
模板字符串允许用${}
的方式嵌入变量;
1.在模板字符串中,空格、缩进、换行都会被保留,例如可以无障碍的直接写HTML代码;
2.模板字符串完全支持“运算”式的表达式,可以在${}
里完成一些计算,可以将一些简单的计算或者调用丢进${}, const finalString =
${a} + ${b} = ${a+b}
除了模板语法外, ES6中还新增了一系列的字符串方法用于提升开发效率:
1.存在性判定:
过去判断一个字符串是否在某个字符串中,只能用indexof>-1
来做,ES6 提供了三个方法:includes、startsWith、endsWith,它们都会返回一个布尔值来告诉你是否存在。
includes:判断字符串与子串的包含关系;
startWiths:判断字符串是否以某个、某串字符开头;
endsWith:判断字符串是否以某个/某串字符结尾;
2.自动重复:可以使用 repeat 方法来使同一个字符串输出多次(被连续复制多次)
const sourceCode = 'repeat for 3 times;'
const repeated = sourceCode.repeat(3)
console.log(repeated) // repeat for 3 times;repeat for 3 times;repeat for 3 times;
map和Object的区别
意外的键:Map默认情况不包含任何键,只包含显式插入的键。Object 有一个原型, 原型链上的键名有可能和自己在对象上的设置的键名产生冲突;
键的类型:Map的键可以是任意值,包括函数、对象或任意基本类型。 Object 的键必须是 String 或是Symbol。
键的顺序:Map 中的 key 是有序的。因此,当迭代的时候, Map 对象以插入的顺序返回键值;Object 的键是无序的
size:
迭代:可迭代;不可迭代
map的常用属性和遍历方法
map.set(key,value)
map.get(key)
map.delete(key)
map.clear()
map.has(key)
map.size
map.keys()返回键名的遍历器
map.values():返回键值的遍历器
map.entries():返回键值对的遍历器
forEach():使用回调函数遍历每个成员
map和weakMap的区别
map:数据存储
map本质上就是键值对的集合,但是普通的Object中的键值对中的键只能是字符串。而ES6提供的Map数据结构类似于对象,但是它的键不限制范围,可以是任意类型,是一种更加完善的Hash结构;
1.map实际上是一个数组,每一个数据都是一个数组:
const map = [
["name","张三"],
["age",18],
]
2.map数据有以下操作方法:
size: map.size 返回Map结构的成员总数。
set(key,value):设置键名key对应的键值value,然后返回整个Map结构,如果key已经有值,则键值会被更新,否则就新生成该键。(因为返回的是当前Map对象,所以可以链式调用)
get(key):该方法读取key对应的键值,如果找不到key,返回undefined。
has(key):该方法返回一个布尔值,表示某个键是否在当前Map对象中。
delete(key):该方法删除某个键,返回true,如果删除失败,返回false。
clear():map.clear()清除所有成员,没有返回值。
3.原生提供三个遍历器生成函数和一个遍历方法:
keys():返回键名的遍历器。
values():返回键值的遍历器。
entries():返回所有成员的遍历器。
forEach():遍历Map的所有成员。
weakMap:
WeakMap 对象也是一组键值对的集合,其中的键是弱引用的。其键必须是对象,而值可以是任意的;
1.不可枚举,有以下方法:set(key,value) get(key) has(key) delete(key)
2.WeakMap的设计目的在于,有时想在某个对象上面存放一些数据,但是这会形成对于这个对象的引用。一旦不再需要这两个对象,就必须手动删除这个引用,否则垃圾回收机制就不会释放对象占用的内存;而WeakMap的键名所引用的对象都是弱引用,即垃圾回收机制不将该引用考虑在内。因此,只要所引用的对象的其他引用都被清除,垃圾回收机制就会释放该对象所占用的内存。也就是说,一旦不再需要,WeakMap 里面的键名对象和所对应的键值对会自动消失,不用手动删除引用;
3.只接受对象作为键名(null
除外),不接受其他类型的值作为键名
4.键名是弱引用,键值可以是任意的,键名所指向的对象可以被垃圾回收,此时键名是无效的
const map = new Map([
["foo",1],
["bar",2],
])
for(let key of map.keys()){
console.log(key); // foo bar
}
for(let value of map.values()){
console.log(value); // 1 2
}
for(let items of map.entries()){
console.log(items); // ["foo",1] ["bar",2]
}
map.forEach( (value,key,map) => {
console.log(key,value); // foo 1 bar 2
})
三、JavaScript基础
JavaScript有哪些内置对象
js 中的内置对象主要指的是在程序执行前存在全局作用域里的由 js 定义的一些全局值属性、函数和用来实例化其他对象的构造函数对象。一般经常用到的如全局变量值 NaN、undefined,全局函数如 parseInt()、parseFloat() 用来实例化对象的构造函数如 Date、Object 等,还有提供数学计算的单体内置对象如 Math 对象。
标准内置对象的分类:
(1)值属性,这些全局属性返回一个简单值,这些值没有自己的属性和方法。
例如 Infinity、NaN、undefined、null 字面量
(2)函数属性,全局函数可以直接调用,不需要在调用时指定所属对象,执行结束后会将结果直接返回给调用者。
例如 eval()、isNaN()、parseFloat()、parseInt() 等
(3)基本对象,基本对象是定义或使用其他对象的基础。基本对象包括一般对象、函数对象和错误对象。
例如 Object、Function、Boolean、Symbol、Error 等
(4)数字和日期对象,用来表示数字、日期和执行数学计算的对象。
例如 Number、Math、Date
(5)字符串,用来表示和操作字符串的对象。
例如 String、RegEx