JS共有八种数据类型:
原始数据类型有:
引用数据类型:
原始数据类型和引用数据类型的区别(存储的位置不同):
堆与栈 —— 在数据结构中:
堆与栈 —— 在操作系统中:
typeof操作符的返回值(字符串):
注意:
值得注意的是:未声明的变量只能进行typeof操作
描述:A instanceof B,A对象是否为B构造函数的一个实例对象
function myInstanceof(a,b){
a = a._proto_; //起点设为实例a的原型对象
b = b.prototype; //b类型的原型对象
while(true){
if(!a){
return false;
}
if(a === b){
return true;
}
a = a._proto_; //若找不到,继续往上找
}
}
① typeof 的返回值(字符串)
缺点:
② instanceof 操作符
A instanceof B, 即判断 A对象是否为B构造函数的一个实例对象
缺点:
③ constructor
缺点:
④ Object.prototype.toString.call()
上面三种方法都各有缺点:
所以最好用有一种是 Object.prototype.toString.call()
大多数对象的toString方法都是重写过的,所以使用Object原型对象上的toString方法,去判断类型。
也就是使用Object.prototype.toString.call()
console.log(Object.prototype.toString.call("jerry"));//[object String]
console.log(Object.prototype.toString.call(12));//[object Number]
console.log(Object.prototype.toString.call(true));//[object Boolean]
console.log(Object.prototype.toString.call(undefined));//[object Undefined]
console.log(Object.prototype.toString.call(null));//[object Null]
console.log(Object.prototype.toString.call({name: "jerry"}));//[object Object]
console.log(Object.prototype.toString.call(function(){}));//[object Function]
console.log(Object.prototype.toString.call([]));//[object Array]
console.log(Object.prototype.toString.call(new Date));//[object Date]
console.log(Object.prototype.toString.call(/\d/));//[object RegExp]
function Person(){};
console.log(Object.prototype.toString.call(new Person));//[object Object]
不可以使用typeof,会返回object
上一题的后三种方法都可以:instanceof,constructor,Object.prototype.toString.call()
原因:
方法一:利用ES6的Number.EPSILON
function test(a,b){
return Math.abs(a-b) < Number.EPSILON;
}
console.log(test(0.1+0.2,0.3)); // true
方法二:将浮点数化为整数
function test(a,b){
console.log((a*1000 + b*1000)/1000)); // 0.3
}
定义:
特点:
数据类型可以进行转换,转成Boolean类型、Number类型和String类型;
需要记住转换的方式和规则
转成Boolean类型常见的有两种方式:
假值列表
注意,属于假值列表内的值,转成Boolean结果都为false
也就是说,假值列表外的值,转为Boolean结果都为true
// 使用!!两次取反
console.log(!!undefined); // false
console.log(!!null); // false
console.log(!!false); // false
console.log(!!+0); // false
console.log(!!-0); // false
console.log(!!NaN);// false
console.log(!!""); // false
// 使用Boolean()方法显示转换
console.log(Boolean(undefined));
console.log(Boolean(null));
console.log(Boolean(false));
console.log(Boolean(+0));
console.log(Boolean(-0));
console.log(Boolean(NaN));
console.log(Boolean(""));
转成Number类型常见有两种方式:
基本的转换规则:
// 基本转换规则
console.log(+undefined); // NaN
console.log(+null); // 0
console.log(+true); // 1
console.log(+false);// 0
console.log(+'123');// 123
console.log(+""); // 0
console.log(+"123x"); //NaN
// Symbol -> 报错
// 使用Number进行显示转换,结果相同
console.log(Number(undefined));
console.log(Number(null));
console.log(Number(true));
console.log(Number(false));
console.log(Number("123"));
console.log(Number(""))
console.log(Number("123x"))
Object类型 即引用数据类型转成Numberd的规则!!
注意:其实数组和对象转成数字类型,都会先转成原始数据类型,这个过程叫ToPrimitive,然后再按上面的规则转成数字
console.log(+[]); // 0
console.log(+[10]) // 10
console.log(+['10']) // 10
console.log(+new Date()) // 1644911468454
console.log(+[1,2]) // NaN
console.log(+[1,"2"]) // NaN
console.log(+{}) // NaN
还有两种不常用的,专门用于字符串转换
parseInt()函数 – 专门转换字符串
console.log(parseInt('AF',16)); // 正确识别是16进制数
console.log(parseInt('AF')); // 第一个非空格字符并非数字字符或负号,直接返回NaN
parseFloat函数 – 专门转换字符串
转成String类型有两种方式
转换规则
引用数据类型
console.log(''+undefined) // 'undefined'
console.log(''+null) // 'null'
console.log(''+true) // 'true'
console.log(''+false) // 'false'
console.log(''+111) // '111'
console.log(''+[]) // ''
console.log(''+[1,2,3]) // '1,2,3'
console.log(''+[1,2,'x']) // '1,2,x'
console.log(''+{}) // [object Object]
console.log(''+{a:1}) // [object Object]
有时候我们要把引用数据类型,即对象{}或者数组[],转成原始数据类型
实际上就是调用了[[ToPrimitive]]
ToPrimitive(input,type)
如果input为Date对象,则type默认为String
其他情况下,type都认为是Number
当type是Number
当type是String
先判断x和y的成分,有如下四种情况。
确定好情况,再根据单个数据的类型转换进行具体转换
情况①:一方为String,则将另外一方转为String
console.log('test' + undefined)
console.log('test' + null)
console.log('abc' + true)
console.log('abc' + false)
console.log('test' + 2022)
console.log('test' + [])
console.log('test' + [1,2,'a'])
console.log('test' + {})
console.log('test' + {a:1})
// 结果
testundefined
testnull
abctrue
abcfalse
test2022
test
test1,2,a
test[object Object]
test[object Object]
情况②:一方为Number,而另外一方是引用数据类型,都转成String
console.log(2022 + []);
console.log(2022 + [1,2,3])
console.log(2022 + {})
console.log(2022 + {a:1})
// 结果
2022
20221,2,3
2022[object Object]
2022[object Object]
情况③: 一方为Number,另外一方是基本数据类型,则将基本数据类型转成Number
console.log(2022 + undefined)
console.log(2022 + null)
console.log(2022 + true)
console.log(2022 + false)
// 结果
NaN
2022
2023
2022
undefined转为Number是NaN,相当于2022 + NaN,则输出NaN
null是转为0
true是转为1
false是转为0
空字符串转为0
纯数字字符串
console.log({} + 1);
console.log([] + 1 )
console.log([1, 2, 3] + 0)
console.log(![] + [])
console.log([] + {})
console.log({} + []);
注意{} + [] 有可能得到别的结果。前面的{}有可能被认作代码块,相当于是 +[],那么相当于是 Number([]),把空数组转成Number,得到0
console.log({} + {}) // '[object Object][object Object]'
console.log([] + []) // ''
减乘除会把非Number转成Number,与加法不同
console.log(1 - true);
console.log('0' - 0);
console.log(false - true);
console.log({} - [])
console.log(false - [])
关于 x == y,强制类型转换有如下步骤:
console.log ( [] == 0 ); //true
console.log( [] == false ); // true
console.log ( ![] == 0 ); //true
console.log ( [] == ! [] ); //true
console.log ( [] == [] ); //false
console.log ( {} == {} ); //false
console.log({} == !{}); //false
console.log( {} == false ); // false
console.log( [2] == 2 ); // true
console.log( ['0'] == false ); // true
console.log( [undefined] == false ); //true
console.log( [null] == false ); // true
console.log( undefined == false );//false
console.log( null == false); //false
console.log( undefined == null);
补充一下,最近做到一个很坑的题目:
console.log('test' == new String('test'))
console.log('test' === new String('test'))
console.log(new String('test') == new String('test'))
console.log(new String('test') === new String('test'))
//true,false,false,false
如:
(表达式1)&& (表达式2)
(表达式1) || (表达式2)
3||2&&5||0的计算步骤
console.log(+0 == -0)
console.log(+0 === -0)
console.log(Object.is(+0,-0))
console.log(NaN == NaN)
console.log(NaN === NaN)
console.log(Object.is(NaN,NaN))
// true
// true
// false
// false
// false
// true
使用new操作符后,创建对象实例,经过以下几个步骤:
Object.create(proto,[propertiesObject])
function new_(constructor, ...args){
// step1-2 创建一个obj对象,并指定其 __ proto __ 为 constructor.prototype
const obj = Object.create(constructor.prototype);
// step3 让构造函数内部的this指向obj,并执行
const result = constructor.apply(obj,args);
return result instanceof Object ? result : obj;
}
function Person(name,age){
this.name = name;
this.age = age;
}
const person1 = new_(Person,'Silam',22)
一、Object.assign()
let outObj = {
inObj: {a: 1, b: 2}
}
let newObj = Object.assign({}, outObj)
newObj.inObj.a = 2
console.log(outObj) // {inObj: {a: 2, b: 2}}
二、扩展运算符
let outObj = {
inObj: {a: 1, b: 2}
}
let newObj = {...outObj}
newObj.inObj.a = 2
console.log(outObj) // {inObj: {a: 2, b: 2}}
结论:
一、普通赋值拷贝
二、浅拷贝
三、深拷贝
深拷贝① JSON.parse(JSON.stringify(obj))
注意,有弊端:undefind、symbol、function会消失!!!
const obj1 = {
a:1,
b:[1,2,3],
c:{
c1:'c1'
},
d:undefined,
e:Symbol('e'),
f:function(){
console.log('function')
}
}
const obj2 = JSON.parse(JSON.stringify(obj1));
console.log(obj2) // undefined,symbol,function都没了
深拷贝② lodash的cloneDeep方法
const obj1 = {
a:1,
b:[1,2,3],
c:{
c1:'c1'
}
}
const _ = require('lodash');
const obj2 = _.cloneDeep(obj1);
console.log(obj2)
深拷贝③ Jquery的extend方法
const obj1 = {
a:1,
b:[1,2,3],
c:{
c1:'c1'
}
}
const $ = require('jquery');
const obj2 = $.extend(true,{},obj1);
console.log(obj2)
深拷贝④ 递归
function myCloneDeep(obj){
if(!obj || typeof obj !== 'object') return obj;
let newObject = Array.isArray(obj) ? [] : {};
for(let key in obj){
newObject[key] = typeof obj[key] === 'object' ? myCloneDeep(obj[key]) : obj[key]
}
return newObject;
}
const obj1 = {
a:1,
b:[1,2,3],
c:{
c1:'c1'
}
}
const obj2 = myCloneDeep(obj1);
console.log(obj2)
DOM的概述
创建节点
DOM操作可以创建:
createElement —— 创建元素节点
const divE1 = document.createElement('div');
createTextNode —— 创建文本节点
const testE1 = document.createTextNode('content');
createAttribute —— 创建属性节点
const attribute = document.createAttribute('customAttribute');
createDocumentFragment —— 创建文档碎片
const fragment = document.createDocumentFragment();
// 文档碎片是一个轻量级文档
// 主要用于存储临时节点
// 在需要时,将文档碎片中的内容一次性全部添加到文档中
获取节点
和CSS选择器可以对应
const ele = document.getElementById('testdiv');
ele.innerHTML = ...;
ele.innerText = ...;
ele.textContent = ...; // 与innerText的兼容性不同
ele.style.color = '';
ele.style.font;
添加节点
删除节点
const self = document.getElementById('to-be-removed');
const parent = self.parentElement
parent.removeChild(self);
AJAX 全称为 Async JavaScript and XML
原理:通过XMLHttpRequest对象向服务器发送异步请求,从服务器获得数据,然后利用JavaScript来操作DOM而更新页面的对应部分。
Ajax可以实现异步请求,即可以在不重新加载整个页面的情况下,对网页的某些部分进行更新。(传统的网页若不使用Ajax,更新内容则会对整个页面进行加载)
一、创建XMLHttpRequest对象
const request = new XMLHttpRequest();
二、与服务器建立连接
request.open(method,url)
三、向服务器发送数据
request.send([body]);
如果请求方法是GET
四、XMLHttpRequest对象需绑定onreadystatechange事件
onreadystatechange事件主要监听服务器端的通信状态,主要监听的是XMLHttpRequest.readyState属性:
只要XMLHttpRequest.readyState一变化,就会触发readystatechange事件,XHLHttpRequest.responseText则是服务器端返回的响应结果
const request = new XMLHttpRequest()
request.onreadystatechange = function(e){
if(request.readyState === 4){ // 整个请求过程完毕
if(request.status >= 200 && request.status <= 300){
console.log(request.responseText) // 服务端返回的结果
}else if(request.status >=400){
console.log("错误信息:" + request.status)
}
}
}
request.open('POST','http://xxxx')
request.send()
//封装一个ajax请求
function ajax(options) {
//创建XMLHttpRequest对象
const xhr = new XMLHttpRequest()
//初始化参数的内容
options = options || {}
options.type = (options.type || 'GET').toUpperCase()
options.dataType = options.dataType || 'json'
const params = options.data
//发送请求
if (options.type === 'GET') {
xhr.open('GET', options.url + '?' + params, true)
xhr.send(null)
} else if (options.type === 'POST') {
xhr.open('POST', options.url, true)
xhr.send(params)
//接收请求
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
let status = xhr.status
if (status >= 200 && status < 300) {
options.success && options.success(xhr.responseText, xhr.responseXML)
} else {
options.fail && options.fail(status)
}
}
}
}
调用AJAX
ajax({
type: 'post',
dataType: 'json',
data: {},
url: 'https://xxxx',
success: function(text,xml){//请求成功后的回调函数
console.log(text)
},
fail: function(status){请求失败后的回调函数
console.log(status)
}
})
一、Ajax
Ajax的缺点:
二、Axios
三、fetch(号称是AJAX的替代品)
优点:
缺点:
关注点分离 即 Separation Of Concerns SOC,是软件架构设计的原则。
如果关注点杂糅在一起,会大大增加软件的复杂性。
SOC的具体说明:
JS脚本会阻塞文档的解析,会发生下面的事情:
所以,延迟JS脚本的加载,有利于文档的解析。
有以下方法:
插播,回忆一下defer与async的区别:
方法一:JSON.stringify()
console.log(JSON.stringify(obj) == '{}');
方法二:Object.getOwnPropertyNames()
console.log(Object.getOwnPropertyNames(obj).length == 0)
方法三:Object.keys()
console.log(Object.keys(obj).length == 0);
注意:
创建正则表达式对象:
相关常用的方法
只匹配一个字符/字符串:
/reg/ 匹配第一个'reg'字符串
/[reg]/ 匹配r、e、g三个字母中任意一个字母,匹配出第一个
/[a-z]/ 匹配a-z中的字母,匹配出第一个
/[0-9]/ 匹配0-9的数字,匹配出第一个
量词匹配
正则选项,即在正则表达式后添加正则选项
/[a-z]/i 可以匹配a-z的字母,不分大小写
/[0-9]/g 可以匹配出所有0-9的数字
边界匹配,^和$
字符匹配
常见应用
//匹配QQ号
var regex = /^[1-9][0-9]{4,10}$/g;
//手机号码
var regex = /^1[34578]\d{9}$/g;
//用户名
var regex = /^[a-zA-Z\$][a-zA-Z0-9_\$]{4,16}$/;
另外:JSON.parse(JSON.stringify(obj)) 还是实现深拷贝的一种方法。
use strict 使得 JavaScript 在更严格的条件下运行,即为严格模式
设严格模式有以下目的
严格模式的区别:
事件流
事件模型,分作以下三类:
绑定/移除方式:
// 直接在html中绑定onclick事件
<input type='button' onclick="func()">
// 在js中绑定onclick事件
var btn = document.getElementById('.btn');
btn.onclick = func;
//移除DOM0级事件
btn.onclick = null;
优点:
缺点
绑定/移除方式:
element.addEventListener(event, handler, useCapture);
element.removeEventListener(eventType, handler, useCapture)
DOM2级(标准事件模型)共有三个流程:
特点:
绑定/移除方式
attachEvent(eventType, handler)
detachEvent(eventType, handler)
IE事件模型有两个流程:
概述:
应用情景:
事件委托的优点:
事件委托的局限:
JS的本地缓存主要有以下4种:
所谓 WebStorage 有以下2种
下面正式介绍这四种本地缓存
一、Cookie
基本特性:
一、增
注意:
二、删
注意:
三、改
注意:splice方法会对原数组产生影响
四、查
五、排序
六、转换
let array = [1,2,3,4,5];
console.log(array.join('、')); // 字符串:1、2、3、4、5
七、迭代
都不改变原数组
some和every是判断为主:
some方法 —— 遍历数组的每一项,对其执行传入的函数,有一项元素返回true,则返回true
const array = [1,2,3,4,5];
console.log(array.some(item => item>4)) // true
every方法 —— 遍历数组的每一项,对其执行传入的函数,只有所有项都返回true,才返回true
const array = [1,2,3,4,5];
console.log(array.every(item => item>4)) // false
filter方法 —— 遍历数组的每一项,对其执行传入的函数,返回true的项会组成新的数组,结果返回新的数组
因为filter方法会返回匹配为true的项组成的新数组,所以它可以进行链式调用
const array = [1,2,3,4,5];
console.log(array.filter(item => item>4)) // [5]
map方法 —— 遍历数组的每一项,对其执行传入的函数,每一项都会得到新的结果,由新的结果组成新数组,结果返回新的数组,不会改变原数组。
因为map方法会返回由新结果组成的新数组,所以它可以进行链式调用
const array = [1,2,3,4,5];
console.log(array.map(item => item*4))
console.log(array)
// [ 4, 8, 12, 16, 20 ]
// [ 1, 2, 3, 4, 5 ]
forEach方法 —— 遍历数组的每一项,将每一项元素传给回调函数。
回调函数中可以接收三个参数:
forEach方法可以接收第二个参数,用于指定回调函数内部的this(前提是回调函数不能是箭头函数,因为箭头函数没有this)
注意:前四种都有返回值,forEach方法没有返回值,也不会改变原数组
const array1 = [1,2,3,4,5];
const array2 = [6,7,8,9,10];
array1.forEach((item,index) => console.log(item + ':'+ index))
array1.forEach(function(item,index){
console.log(this[index])
},array2) ;// 输出 6,7,8,9,10
还有 for…in,for…of
还有 indexOf,includes,find等查找方法
for…of循环是ES6新增的遍历方法,它的本质是调用数据结构内部的Iterator接口(遍历器对象),也就是访问Symbol.Iterator属性。
区别:
总结:
注意:
方法一、双重for循环 + splice方法
原理:从数组的第一项元素开始遍历,判断后面的元素当中是否有与被查的元素相同,有则用splice方法删除
function method(arr){
for(var i = 0; i<arr.length; i++){
for(var j = i+1; j < arr.length; j++){
if(arr[i]==arr[j]){
arr.splice(j,1);
j--;
}
}
}
return arr;
}
方法二、利用indexOf方法
原理:indexOf方法可以返回被查元素在数组中第一次出现的位置下标,如果不存在则返回-1
创建一个新数组array_,逐个遍历原数组array的元素是否在新数组中。利用IndexOf方法若返回-1,则说明该元素还未出现在新数组array_中,则将该元素push进去。
function method(arr){
var array_ = []; // build a new array
for(var i = 0; i < arr.length; i++){
if(array_.indexOf(arr[i]) == -1){
//arr[i]还未出现在array_中
array_.push(arr[i]);
}
}
return array_
}
方法三、利用includes方法
和方法二类似
function method(arr){
var array_ = [];
for(var i = 0; i < arr.length; i++){
if(!array_.includes(arr[i])){
array_.push(arr[i]);
}
}
return array_;
}
方法四、利用filter和indexOf方法
indexOf返回的是元素在数组中首次出现位置的下标,如果首次出现的下标与当前的index不同,说明是重复的。
function method(arr){
return arr.filter((item,index)=>{
return arr.indexOf(item) === index;
})
}
方法五、利用ES6的数据结构Set
Set是ES6提供的数据结构,类似于数组,但不同的是其数据成员得是唯一的,不得重复
可以先用new Set()构造函数生成一个Set数据结构,它不会把重复的数据添加进去,从而实现去重;最后再用Array.from将其转换为数组。
function method(arr){
return Array.from(new Set(arr));
}
var arr = [1,2,3,4,5,6,7,8,9,10];
for (var i = 0; i < arr.length; i++) {
const randomIndex = Math.round(Math.random() * (arr.length - 1 - i)) + i;
[arr[i], arr[randomIndex]] = [arr[randomIndex], arr[i]];
}
console.log(arr)
方法一、递归
如果某一项是数组,则进一步调用flatten函数
let arr = [1, [2, [3, 4, 5]]];
function flatten(arr) {
let result = [];
for(let i = 0; i < arr.length; i++) {
if(Array.isArray(arr[i])) {
result = result.concat(flatten(arr[i]));
} else {
result.push(arr[i]);
}
}
return result;
}
flatten(arr); // [1, 2, 3, 4,5]
方法二、扩展运算符
let arr = [1, [2, [3, 4]]];
function flatten(arr) {
while (arr.some(item => Array.isArray(item))) {
arr = [].concat(...arr);
}
return arr;
}
console.log(flatten(arr)); // [1, 2, 3, 4,5]
方法三、ES6的flat方法
let arr = [1, [2, [3, 4]]];
function flatten(arr) {
return arr.flat(Infinity);
}
console.log(flatten(arr)); // [1, 2, 3, 4,5]
一、reduce方法
reduce(function(accumulator,currentValue), 0)
accumulator累加器,并设置初始值为0
let arr=[1,2,3,4,5,6,7,8,9,10]
let sum = arr.reduce( (total,i) => total += i,0);
console.log(sum);
二、遍历数组逐个相加
类数组对象与数组相似,具有以下特点:
常见的类数组对象有:
NodeList可以通过以下代码获得,举例如:
将类数组对象转化为数组通常有以下几种方法
一、 arguments参数列表,属于类数组对象
二、 类数组对象有以下三点特征,在arguments上的描述如下:
三、 此外arguments还有 __ proto __ 与 callee 两个属性值得我们注意:
callee的两个作用
原型链:
按部分分析:
关系:Foo()是构造函数,f1是其实例对象,Foo.prototype是原型对象
部分二、
原型链:通过__ proto __ 不断连接实例对象与原型对象,终点是null
注意:如果没有通过var let const去声明变量,直接对变量进行赋值,那么这个变量就会意外暴露成全局变量,有可能会造成内存泄漏!
变量提升与函数提升的规则可简单总结为:
具体参见 变量提升、函数提升
为什么会进行变量提升与函数提升:
优点1:提升性能
优点2:容错性好
缺点:可能会覆盖变量
作用域:决定了代码区块中变量和其它资源的可见性
作用域有三类:
全局作用域:
函数作用域:
块级作用域:
作用域链:
将从概念,由来,作用,应用,注意事项几个方面来回答:
① 执行上下文的类型,分作三种类型:
全局执行上下文与函数执行上下文:
② 执行上下文栈
③ 执行上下文的生命周期,共有三个阶段:
创建阶段 做以下三个事情:
执行阶段 做以下三个事情:
回收阶段
一、词法环境:由两个部分组成:
词法环境可分作两类:
二、变量环境:变量环境也是一个词法环境;
区别只在于:
this是什么:
① 默认绑定
② 隐式绑定
③ 显示绑定
④ new绑定
⑤ 箭头函数
① 箭头函数的this
② 箭头函数没有arguments对象
③ 箭头函数没有prototype原型对象
④ 箭头函数不能进行new调用
当进行一个new操作时,发生的步骤如下
前面说到,构造函数没有自己的this,也没有prototype原型对象,所以不能作为构造函数进行new调用
⑤ 箭头函数不能作Generator函数,不能使用yield关键字
⑥ 箭头函数的写法简洁
function add(...values) {
let sum = 0;
for (var val of values) {
sum += val;
}
return sum;
}
add(2, 5, 3) // 10
在ES6提出模板语法之前,拼接字符串是个很痛苦的事情;
需要用到许多的引号和加号,得仔细检查有没有拼错,可读性大大降低。
ES6引入模板语法,是增强版的字符串,用反引号标识。优势:
// 支持用 ${} 去嵌入变量,而不需要使用引号和加号去拼接
let name = "Bob", time = "today";
`Hello ${name}, how are you ${time}?`
// 支持定义多行字符串,且所有的空格和缩进都会被保留
`In JavaScript this is
not legal.`
// 支持任意JavaScript表达式,从而可以进行运算、引用对象属性、调用函数
let x = 1;
let y = 2;
`${x} + ${y} = ${x + y}`
// "1 + 2 = 3"
`${x} + ${y * 2} = ${x + y * 2}`
// "1 + 4 = 5"
let obj = {x: 1, y: 2};
`${obj.x + obj.y}`
function f(x) {
if (x > 0) {
return m(x)
}
return n(x);
}
ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。
// 例一
const set = new Set([1, 2, 3, 4, 4]);
[...set]
// [1, 2, 3, 4]
// 例二
const items = new Set([1, 2, 3, 4, 5, 5, 5, 5]);
items.size // 5
Map和Object都是键值对的集合,有如下区别:
Map
三个遍历器和一个遍历方法
WeakMap 的设计目的
WeakMap与Map差不多,也是键值对的集合,主要有两点区别:
概述:
let s1 = Symbol('foo');
let s2 = Symbol('bar');
s1 // Symbol(foo)
s2 // Symbol(bar)
s1.toString() // "Symbol(foo)"
s2.toString() // "Symbol(bar)"
使用:
Proxy用于 修改某些操作的默认行为,等同于在语言层面做出修改,是一种元编程,即对编程语言进行编程。
// 通过new操作符和Proxy构造函数来生成proxy实例
// target:所要拦截的目标对象
// handler:是一个配置对象,用于定制拦截的行为
var proxy = new Proxy(target,handler)
理解:在目标对象之前架设一层拦截,外界对目标对象的访问都必须通过这层拦截。可以对外界的访问进行过滤和改写(定制)。
一、
遍历器Iterator是一个接口,为不同的数据结构提供统一的访问机制。
任何数据结构只要部署Iterator接口,就可以完成遍历操作(依次处理该数据结构的所有成员)
简单来说,Iterator的三个作用
// 返回一个遍历器对象,可以使用next方法逐次访问数据结构的成员
var it = makeIterator(['a', 'b']);
it.next() // { value: "a", done: false }
it.next() // { value: "b", done: false }
it.next() // { value: undefined, done: true }
function makeIterator(array) {
var nextIndex = 0;
return {
next: function() {
return nextIndex < array.length ?
{value: array[nextIndex++], done: false} :
{value: undefined, done: true};
}
};
}
二、
原生具备Iterator接口的数据结构有:
总结:
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();
hw.next()
// { value: 'hello', done: false }
hw.next()
// { value: 'world', done: false }
hw.next()
// { value: 'ending', done: true }
hw.next()
// { value: undefined, done: true }
Generator本身并不是为了异步而设计的,它除了用于异步编程,还可以进行迭代、控制输出等等。
注意:async实质上是Generator函数的语法糖,会更加简洁。
普通用法:
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
// 等同于
Point.prototype = {
constructor() {},
toString() {},
toValue() {},
};
ES6 Class继承:
class Point { /* ... */ }
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // 调用父类的constructor(x, y)
this.color = color;
}
toString() {
return this.color + ' ' + super.toString(); // 调用父类的toString()
}
}
一、ES6 Module的使用 (三个命令)
① export命令 规定模块的对外接口
// 一、导出单个变量或函数
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;
// 二、使用大括号批量导出变量或函数 (推荐使用!!!)
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
export { firstName, lastName, year };
// 三、另外,可以用as为输出的变量/函数设置别名
function v1() { ... }
function v2() { ... }
export {
v1 as streamV1,
v2 as streamV2,
v2 as streamLatestVersion
};
// 四、export命令可以位于模块的任何位置,只要在模块顶层即可。
// 如果在块级作用域内,那么就会报错!
function foo() {
export default 'bar' // SyntaxError 报错!!!
}
foo()
② import命令 加载导出模块的变量、方法
// 一、使用大括号批量地从其它模块导入变量或函数,名字要一致
import { firstName, lastName, year } from './profile.js';
function setName(element) {
element.textContent = firstName + ' ' + lastName;
}
// 二、可以使用 as 为输入的变量或函数取别名
import { lastName as surname } from './profile.js';
// 三、import命令输入的变量是只读的 read-only,不准改写对外接口!
// 但如果输入的变量是个对象,还是可以修改对象里的内容的!
import {a} from './xxx.js'
a = {}; // Syntax Error : 'a' is read-only;
a.foo = 'hello'; // 合法操作
// 四、import命令有提升效果,会提升到整个模块的头部,在编译阶段执行,首先执行
foo();
import { foo } from 'my_module';
// 五、import命令是静态的
// 报错:使用了表达式
import { 'f' + 'oo' } from 'my_module';
// 报错:使用了变量
let module = 'my_module';
import { foo } from module;
// 报错:使用了if结构
if (x === 1) {
import { foo } from 'module1';
} else {
import { foo } from 'module2';
}
③ export default命令 指定模块的默认输出
export default命令,是给用户提供方便,不需要提前阅读文档去了解有什么变量名、函数名,指定模块的默认输出。
二、Module使用的意义
三、ES6的Module和CommonJS的区别
前言:尽管可以使用Object构造函数或者是对象字面量的方式去创建单个对象,但是在用于创建大量对象时,这些方法都会产生大量的重复代码。
① 工厂模式的出现,
// 工厂模式
function createPerson(name,age,job){
var obj = new Object();
obj.name = name;
obj.age = age;
obj.job = job;
obj.sayName = function(){
console.log(this.name);
}
return obj;
|
② 构造函数模式
// 构造函数模式
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
console.log(this.name);
}
}
③ 原型模式
// 原型模式
function Person(){}
Person.prototype.name = 'Silam';
Person.prototype.age = '22';
Person.prototype.job = 'Software Engineer';
Person.prototype.sayName = function(){
console.log(this.name);
}
④ 构造函数模式+原型模式
⑤ 动态原型模式
// 动态原型模式
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
// 如果创建的对象实例中访问不到sayName
// 无法通过__ proto __访问原型对象上的sayName方法
// 则在原型对象上添加该方法
if(typeof this.sayName !== 'function'){
Person.prototype.sayName = function() {
console.log(this.name);
}
}
}
⑥ 寄生构造函数模式
① 原型链继承
// 原型链继承
// 改写子类的原型对象,使其是父类的一个实例!!!
// 子类的原型对象是父类的一个实例!!!
subType.prototype = new SuperType();
② 借用构造函数
// 借用构造函数
// 在子类的构造函数中调用父类的构造函数,通过apply/call实现
function subType(){
superType.call(this,args); // 调用父类构造函数,并传参
this.subAge = 23; // 设定子类的实例属性
}
③ 组合继承
function SubType(name, age){
// 借用构造函数去继承实例属性
// 第二次调用SuperType()
SuperType.call(this, name);
this.age = age;
}
// 通过原型链去继承原型属性/方法
// 第一次调用SuperType()
SubType.prototype = new SuperType();
// 重写SubType.prototype的constructor属性,指向自己的构造函数SubType
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
alert(this.age);
};
④ 原型式继承
// 原型式继承:使用object()方法或ES5规范的Object.create()方法
// Func是构造函数,使其prototype指向obj,obj成为原型对象
// 返回 new Func() 实例,实例的原型对象 __proto__ 就是obj
function object(obj){
function Func(){}
Func.prototype = obj;
return new Func();
}
ES5 规范:Object.create(proto,[propertiesObject])
⑤ 寄生式继承
// 寄生式继承
// 调用object函数(原型式继承的基础)
// 在对其返回的实例对象进一步增强,添加属性或方法
// 返回增强后的对象
function createAnother(original){
var obj_ = object(original);
obj_.sayHi = function(){
alert("hi");
};
return obj_;
}
⑥ 寄生式组合继承
第三种继承方式,即组合继承,最大的缺点就是会调用两次父类的构造函数:一次是在实现原型链继承(让子类的原型对象成为父类的实例);一次是借用构造函数,在子类构造函数中调用了父类构造函数。
调用两次父类的构造函数,无疑创建了两份父类的实例属性:一份实例属性放在了子类原型对象subType.prototye中;一份放在了子类实例中。
显然,我们没有必要让子类拥有两份父类的实例属性。其实同样也是没有必要为了实现原型链继承,让子类的原型对象又拥有了父类的实例属性!
所以寄生式组合继承,原理则是在通过寄生式继承,来实现对父类原型对象方法的继承,避免实现原型链继承时调用父类构造函数。
// 创建一个原型对象为父类原型对象的实例对象prototype
// 让这个父类的实例对象prototype的constructor指回subType
// 并让这个父类实例对象prototype成为子类的原型对象
// 通过寄生式继承,实现了“子类的原型对象是父类的一个实例”
// !!!但又避免了调用父类的构造函数,从而避免了在子类原型对象上添加父类的实例对象
function inheritPrototype(subType,superType){
let prototype = Object.create(superType.prototype);
protoType.constructor = subType;
subType.prototype = prototype;
}
异步编程有几种实现方式:
Promise是一种异步编程的实现方案
then方法
catch方法
all方法
race方法
finally方法
promise的优缺点
async
await
总结:
首先关于事件循环,有以下几个关键概念:
事件循环Event Loop的概述:
标记清除
引用计数 (跟踪记录每个值被引用的次数)
而引用计数有可能出现循环引用的情况:
譬如 对象A的某个属性指向对象B,且对象B的某个属性指向对象A ,就会造成循环引用
function example(){
var objA = new Object(); // 对象A的引用次数为1
var objB = new Object(); // 对象B的引用次数为1
objA.a = objB; // 对象B的引用次数+1,为2
objB.b = objA; // 对象A的引用次数+1,为2
}
如上,当example函数执行完毕后,两个对象的引用次数都不会是0,都为2,这就是循环引用,其占用的内存得不到释放。
总结:
即使JavaScript有垃圾回收机制,但还是要关注内存泄漏问题。譬如像循环引用这种情况,就需要我们通过检查:是否存在一些不再有用的值,但却仍存在着对它们的引用,如果是的话, 应该将值设为null手动释放引用
常见造成内存泄漏有以下五种情况
① 意外的全局变量
function example(){
test = 'no no no'; // 使用未声明的变量,test变成全局变量
this.example = 'no no no';
}
example() ;
// 函数在全局中被调用,故函数内部的this指向了全局window
// this.example 也相当于 window.example,example也变成了全局变量
// 不过这个可以通过严格模式去避免
② 被遗忘的setInterval、回调函数
③ 没有清理对DOM元素的引用
// 变量ref是对ref DOM元素的引用
const ref = document.getElementById('ref');
// 尽管文档卸载了该DOM元素
document.body.removeChild(ref);
// 变量ref仍然保存着对该DOM元素的引用
console.log(ref);
// 应该手动将该变量设置为null,解除引用
ref = null;
④ 闭包
function father(){
var obj = document.createElement('test');
function son(){
console.log(obj); // 闭包
}
obj = null; // 解除引用
}
⑤ 没有取消事件监听addEventListener