文章目录
- 前言
- 一、三剑客
- 1.HTML
- 2.CSS
- 2.1 盒模型
- 2.2 圣杯布局和双翼飞布局
- 3.JS
- 3.1 防抖(debounce)
- 3.2 节流(throttling)
- 3.3 深拷贝和浅拷贝
- 3.4 数组对象去重
- 3.5 扩展题
- 3.6 数据类型
- 3.7 作用域和作用域链
- 3.8 对象、数组、字符串的方法
- 3.9 this指向
- 3.10 闭包
- 3.11 call、apply、bind
- 3.12 函数柯里化
- 3.13 高阶函数
- 3.14 new的原理
- 3.15 垃圾回收机制和内存机制
- 3.16 事件循环(event Loop)
- 3.17 延迟加载
- 3.18 预解析
- 3.19 服务端渲染(SSR)
- 3.20 onload和domcontentloaded事件
- 3.21 cookies,sessionStorage 和 localStorage、localForage
- 3.22 location、之navigator和history
- 3.23 offset、client和scroll
- 3.23 操作dom的常用API
- 3.24 编程范式
- 3.25 iframe
- 3.26 0.1+0.2 != 0.3
- 3.27 require和import
- 3.28 ES6+
- 3.29 模块规范
- 4. TS
- 二、框架
- 总结
前言
在业余时间中,无聊的我在网上看了一些面试题和技术博文突然发现其中还有一些很不错的知识点就想收藏起来, 但是越到后面发现东西太多太杂再回头看的时候不好梳理和记忆,于是乎我决定在看的同时把它们记录起来汇总成一个知识大全,以便于之后方便与查找和理解记忆为此巩固自己的基本功。
一、三剑客
1.HTML
待更新中…
2.CSS
层叠样式表 (Cascading Style Sheets,缩写为 CSS),是一种 样式表 语言,用来描述 HTML 或 XML(包括如 SVG、MathML、XHTML 之类的 XML 分支语言)文档的呈现。CSS 描述了在屏幕、纸质、音频等其它媒体上的元素应该如何被渲染的问题。
CSS 是开放网络的核心语言之一,由 W3C 规范 实现跨浏览器的标准化。CSS 节省了大量的工作。样式可以通过定义保存在外部.css 文件中,同时控制多个网页的布局,这意味着开发者不必经历在所有网页上编辑布局的麻烦。CSS 被分为不同等级:CSS1 现已废弃,CSS2.1 是推荐标准, CSS3 分成多个小模块且正在标准化中。
2.1 盒模型
在css2中的盒模型有两种:标准盒模型也叫w3c盒模型,怪异盒模型也叫IE盒模型。而在css3当中,新增加了弹性盒子模型,弹性盒子模型是一种新增加的强大的、灵活的布局方案。
两种盒子模型都是由content + padding + border + margin构成,其大小都是由cotent + padding + border 决定的, 但是盒子内容宽、高度的计算范围是根据盒模型的不同会有所不同。
可以通过box-sizing 来改变元素的盒模型:1.box-sizing:content-box 标准盒模型(默认值) 2.box-sizing:border-box 怪异盒模型。
标准盒模型
根据W3C的规范,盒子实际内容content的宽度和高度等于我们设置的width和height,而盒子总宽度=width+padding+border+margin,总高度=height+padding+border+margin。
怪异盒模型
怪异盒模型的width和height包括了border和padding。
盒子总宽度=width+margin,盒子总高度=height+margin。
怪异盒模型的使用场景:当元素的width和height固定,此时需要添加padding或者border,可将标准盒模型转换为怪异盒模型。
弹性盒模型
弹性盒模型由弹性容器(flex container)和弹性子元素(flex-item)组成,是CSS3的一种新的布局模式。设置了display:flex的父元素被称之为弹性容器,弹性容器内包含一个或多个弹性子元素,容器内默认存在两根轴:水平的主轴和垂直的交叉轴,项目默认沿主轴排列。
容器的属性:有flex-direction, flex-wrap,flex-flow, justify-content,align-items,align-content等等
2.2 圣杯布局和双翼飞布局
- 三栏布局,中间一栏最先加载和渲染(内容最重要,这就是为什么还需要了解这种布局的原因)。
- 两侧内容固定,中间内容随着宽度自适应。
- 一般用于PC 网页。
html部分
<body class="holy">
<header></header>
<div class="holy_body">
<main class="holy_content">holy_content</main>
<nav class="holy_nav">holy_nav</nav>
<aside class="holy_aside">holy_aside</aside>
</div>
<footer></footer>
</body>
css部分
<style>
.holy {
display: flex;
flex-direction: column;
min-height: 100vh;
}
header, footer {
flex: 1;
border: 1px solid red;
}
.holy_body {
display: flex;
flex: 1;
border: 1px solid black;
}
.holy_content {
flex: 1;
}
.holy_nav, .holy_aside {
flex: 0 0 12em;
}
.holy_nav {
order: -1;
}
@media (max-width: 768px) {
.holy_body {
flex-direction: column;
flex: 1;
}
.holy_aside, .holy_nav, .holy_content {
flex: auto;
}
}
</style>
3.JS
JavaScript(简称“JS”) 是一种具有函数优先的轻量级,解释型或即时编译型的编程语言。虽然它是作为开发Web页面的脚本语言而出名,但是它也被用到了很多非浏览器环境中,JavaScript 基于原型编程、多范式的动态脚本语言,并且支持面向对象、命令式、声明式、函数式编程范式。
JavaScript在1995年由Netscape公司的Brendan Eich,在网景导航者浏览器上首次设计实现而成。因为Netscape与Sun合作,Netscape管理层希望它外观看起来像Java,因此取名为JavaScript。但实际上它的语法风格与Self及Scheme较为接近。
JavaScript的标准是ECMAScript 。截至 2012 年,所有浏览器都完整的支持ECMAScript5.1,旧版本的浏览器至少支持ECMAScript 3 标准。2015年6月17日,ECMA国际组织发布了ECMAScript的第六版,该版本正式名称为 ECMAScript 2015,但通常被称为ECMAScript 6 或者ES2015。
3.1 防抖(debounce)
官话:当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定时间到来之前,又触发了事件,就重新开始延时。也就是说当一个用户一直触发这个函数,且每次触发函数的间隔小于既定时间,那么防抖的情况下只会执行一次。
白话:在input框中,当用户连续不停的输入一些值之后只在规定时间内触发最后的一次的回调
防抖: <input id="input" type="text">
const inp = document.getElementById('input')
inp.addEventListener('input', function (e) {
result(e.target.value)
})
let result = debounce(function (value) {
console.log('value', value);
}, 1000)
function debounce(fn, time) {
let timer
return function (value) {
// 这里的value是如何通过fn拿到的呢?利用了闭包能够读取其它函数内部变量的特点
clearTimeout(timer)
timer = setTimeout(() => {
fn(value)
}, time);
}
}
3.2 节流(throttling)
官话:固定周期内,只执行一次动作,若有新事件触发,不执行。周期结束后,又有事件触发,开始新的周期。
白话:在按钮中,当用户连续不停的点击触发事件之后只会在规定时间内触发最行的回调
节流: <button id="button">手速再快也是1s一次</button>
const btn = document.getElementById('button')
btn.addEventListener('click', throttling(function(e) {
console.log('value', e.target.innerText);
}, 1000))
function throttling(fn, time) {
let timer
return function() {
if (!timer) {
timer = setTimeout(() => {
// 改变this指向把e对象传出去
fn.apply(this, arguments)
timer = null
}, time);
}
}
}
3.3 深拷贝和浅拷贝
3.3.1 浅拷贝
拷贝基本数据类型时,不受任何影响,当拷贝引用类型时,源对象也会被修改且浅拷贝只拷贝已存在对象的对象属性的引用,其余非对象属性是占用新的内存空间,并非与原对象共享。
- Array.prototype.slice()方法
slice() 方法返回一个新的数组对象,这一对象是一个由 begin 和 end 决定的原数组的 浅拷贝(包括 begin,不包括end)。原始数组不会被改变。
const arr = ['小明', '小红', { name: '小紫', age: 20 }]
let newArr = arr.slice()
newArr[0] = '小绿'
newArr[2].age = 99
console.log('arr', arr); // ['小明', '小红', {name: '小紫', age: 99}]
console.log('newArr', newArr); // ['小绿', '小红', {name: '小紫', age: 99}]
- Array.concat()方法
concat方法不会改变this或任何作为参数提供的数组,而是返回一个 浅拷贝,它包含与原始数组相结合的相同元素的副本
const arr = ['小明', '小红', { name: '小紫', age: 20 }]
let newArr = [].concat(arr) // 或者let newArr = [...[], ...arr]
newArr[0] = '小绿'
newArr[2].age = 99
console.log('arr', arr); // ['小明', '小红', {name: '小紫', age: 99}]
console.log('newArr', newArr); // ['小绿', '小红', {name: '小紫', age: 99}]
- Object.assign()方法和ES6的扩展运算符
Object.assign() 拷贝的是属性值。假如源对象的属性值是一个指向对象的引用,它也只拷贝那个引用值。
用扩展运算符对数组或者对象进行拷贝时,只能扩展和深拷贝第一层的值,对于第二层极其以后的值,扩展运算符将不能对其进行打散扩展,也不能对其进行深拷贝,即拷贝后和拷贝前第二层中的对象或者数组仍然引用的是同一个地址,其中一方改变,另一方也跟着改变。
const obj = { name: '小明', age: { num: 10 } }
let newObj = {}
Object.assign(newObj, obj) // 或者let newObj = {...obj}
newObj.name = '小亮'
newObj.age.num = 100
console.log('obj', obj); // {name: '小明', age:{num: 100}}
console.log('newObj', newObj); // {name: '小亮', age: {num: 100}}
3.3.2 深拷贝
深拷贝就是把一个对象,从内存中完整的拷贝出来,从堆内存中开辟了新区域,用来存新对象,并且修改新对象不会影响原对象
- 用JSON.stringify和JSON.parse
可以深拷贝的数组和对象,但是不能拷贝函数,可以进行对象或者数组的嵌套拷贝
const arr = ['小明', '小红', { name: '小紫', age: 20 }]
let newArr = deepClone(arr)
newArr[0] = '小绿'
newArr[2].age = 99
console.log('arr', arr); // ['小明', '小红', {name: '小紫', age: 20}]
console.log('newArr', newArr); // ['小绿', '小红', {name: '小紫', age: 99}]
function deepClone(data) {
let _obj = JSON.parse(JSON.stringify(data))
return _obj
}
- 用递归
函数 caller 运行时,调用其他函数 called ,js会在调用栈中新开一个调用帧存储作用域和上下文信息,而caller的调用帧信息仍需要保存。而内存中调用栈存储信息有限,递归情况下,如果递归层次过深会导致调用栈耗光而引起stack overflow —— 爆栈。
const obj = {
name: '香风智乃',
age: 18,
sex: '女',
address: {
id: '001',
title: '咖啡馆',
},
color: ['蓝色', '白色'],
say() {
console.log('唱歌');
}
}
let newObj = deepClone(obj)
newObj.name = '时崎狂三',
newObj.address.id = '002'
console.log('obj', obj);
console.log('newObj', newObj);
function deepClone(obj) {
// 判断深拷贝的是数组还是对象,是对象则对象拷贝反之数组拷贝
let newObj = Array.isArray(obj) ? [] : {}
// 传入的obj不能为空并且要是数组或者对象,null也是对象
if (obj && typeof obj === "object") {
for (key in obj) {
// hasOwnProperty()方法用于检测一个对象是否含有特定的自身属性,返回一个布尔值
if (obj.hasOwnProperty(key)) {
//obj里面属性值不为空并且还是对象,进行深度拷贝
if (obj[key] && typeof obj[key] === "object") {
//递归进行深度的拷贝
newObj[key] = deepClone(obj[key])
} else {
// 反之直接拷贝
newObj[key] = obj[key]
}
}
}
}
return newObj
}
- 用jQuery的extend方法
const arr = ['小明', '小红', { name: '小紫', age: 20 }]
let newArr = $.extend(true,[],arr);
newArr[0] = '小绿'
newArr[2].age = 99
console.log('arr', arr); // ['小明', '小红', {name: '小紫', age: 20}]
console.log('newArr', newArr); // ['小绿', '小红', {name: '小紫', age: 99}]
- 用lodash函数库
const objects = [{ 'a': 1 }, { 'b': 2 }];
let deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]); // => false
3.4 数组对象去重
3.4.1 用indexOf()
indexOf() 方法可返回某个指定的字符串值在字符串中首次出现的位置,如果没有找到匹配的字符串则返回 -1。
const arr = [1, 2, 35, 35, '香风智乃', '香风智乃', '雏鹤爱']
let arr_2 = _set(arr)
console.log(arr_2); // [1, 2, 35, '香风智乃', '雏鹤爱']
function _set(arr) {
let newArr = []
for (let i in arr) {
if (newArr.indexOf(arr[i]) === -1) {
newArr.push(arr[i])
}
}
return newArr
}
3.4.2 用new Set()
ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。Set 本身是一个构造函数,用来生成 Set 数据结构。
let arr_2 = [...new Set(arr)]
console.log(arr_2); // [1, 2, 35, '香风智乃', '雏鹤爱']
3.4.3 用reduce()
reduce 为数组中的每一个元素依次执行回调函数,不包括数组中被删除或从未被赋值的元素,接受四个参数:初始值(或者上一次回调函数的返回值),当前元素值,当前索引,调用 reduce 的数组。
let arr_3 = [
{ id: 1, name: 'obj' },
{ id: 3, name: 'string' },
{ id: 2, name: 'arr' },
{ id: 1, name: 'num' },
{ id: 1, name: 'tttt' }
]
let obj = {}
arr_3 = arr_3.reduce((pre, cur) => {
obj[cur.id] ? '' : obj[cur.id] = true && pre.push(cur)
return pre
}, [])
console.log('arr_3', arr_3);
3.4.4 用filter和findIndex
利用filter和findIndex双重循环(1对n)的方式找到相同属性值的下标(selfIndex)最后再与该项的下标(index)作比较。当出现index值不等,就表示出现了重复数据,那么该重复项将不会被返回。
let arr = [
{ "name": "zhangsan", "age": 18 },
{ "name": "lisi", "age": 16 },
{ "name": "zhangsan", "age": 20 },
{ "name": "wanger", "age": 18 },
{ "name": "wanger", "age": 18 }
]
function uniqueArray(arr) {
let res = arr.filter((item, index, self) => {
/*
第一轮:
item.name: 'zhangsan' item2.name: 'zhangsan' selfIndex: 0 index: 0
因为在arr中item.name的属性值与self中item2.name的属性值相等为true
并返回self数组中该项的下标selfIndex,也与index相等则返回这项数据
结束findIndex循环,进入下一轮的filter循环
第二轮:
item.name: 'lisi' item2.name: 'zhangsan' selfIndex: undefined index: 1
因为findIndex会遍历完self数组直至找到符合项下标,当遍历完时找不到则返回-1
item.name: 'lisi' item2.name: 'lisi' selfIndex: 1 index: 1
同第一轮
第三轮:
item.name: 'zhangsan' item2.name: 'zhangsan' selfIndex: 0 index: 2
此时findIndex在self里找到了和item.name相同值并返回该项的下标selfIndex
由于filter遍历arr时的index一直在更新,所以和selfIndex不相等则跳过此项
结束findIndex循环,进入下一轮的filter循环
....
*/
return self.findIndex(item2 => item2.name === item.name) === index
})
return res
}
3.4.5 用forEach
先利用forEach进行遍历,再通过对象中属性名唯一性的特点进行去重
let arr = [
{ "name": "zhangsan", "age": 18 },
{ "name": "lisi", "age": 16 },
{ "name": "zhangsan", "age": 20 },
{ "name": "wanger", "age": 18 },
{ "name": "wanger", "age": 18 }
]
function uniqueArray(arr) {
let newArr = []
let obj = {}
arr.forEach(item => {
let { name } = item
/*
第一轮:
name:"zhangsan" obj: {}
因为obj对象中没有name为zhangsan的属性值所以为false,取反if执行。
newArr:[{name: 'zhangsan', age: 18}] obj: {zhangsan: 'zhangsan'}
此时obj保存了这次name的值,以便之后遇到重复的name进行判断。
第二轮:
name:"lisi" obj: {zhangsan: 'zhangsan'}
newArr:[{name: 'zhangsan', age: 18}, { "name": "lisi", "age": 16 }] obj: {zhangsan: 'zhangsan', lisi: 'lisi'}
同第一轮
第三轮:
name:"zhangsan" obj: {zhangsan: 'zhangsan', lisi: 'lisi'}
因为obj中已经存在name为zhangsan的属性名所以为true,取反if跳过。
newArr:[{name: 'zhangsan', age: 18}, { "name": "lisi", "age": 16 }] obj: {zhangsan: 'zhangsan', lisi: 'lisi'}
....
*/
if (!obj[name]) { // 由于是数组对象的name去重,则用name当obj的属性名
obj[name] = name
newArr.push(item)
}
})
return newArr
}
3.4.6 用Map
利用forEach进行遍历,再通过map数据结构中的一些api进行去重
let arr = [
{ "name": "zhangsan", "age": 18 },
{ "name": "lisi", "age": 16 },
{ "name": "zhangsan", "age": 20 },
{ "name": "wanger", "age": 18 },
{ "name": "wanger", "age": 18 }
]
function uniqueArray(arr) {
let map = new Map()
let newArr = []
arr.forEach(item => {
let { name } = item
// 因为Map基本上是一个key不受类型限制的对象加上本身的api,所以步骤和对象去重相同。
if (!map.has(name)) {
map.set(name, name) // 这里用set方法添加的对象不能用es6的对象简写。
newArr.push(item)
}
})
return newArr
}
3.5 扩展题
3.5.1 红绿灯
按照后端返回的arr,先打印红5s后打印黄2s后打印绿,依次循环不同的颜色
解决思路:利用异步编程方案,返回一个promise再用async、await依次接收最后递归调用重复此过程
const arr = [{ color: 'red', time: 5000 }, { color: 'yellow', time: 2000 }, { color: 'green', time: 3000 }]
const taskRunner = (color, time) => (
new Promise((resolve, reject) => {
setTimeout(() => {
console.log(`${time}秒后=======>${color}`)
resolve()
}, time);
})
)
const task = async () => {
for (let i in arr) {
await taskRunner(arr[i].color, arr[i].time)
}
task()
}
task()
3.5.2 get方法
第一个参数是目标对象:let a = {b:{c:[1,2,3]}},第二个参数是path: b.c.2,返回结果3
解决思路:先把path传换为数组,在用while循环每一次res的属性值直至拿到结果
const obj = {b:{c:[1,2,3]}}
const path = 'b.c.3'
let result = get(obj, path)
console.log('res', result) // 3
function get(target, path) {
let prop
let paths = path.split('.')
let res = target
// prop变量每次会保存paths弹出的值,直至undefined结束循环返回结果
while (prop = paths.shift()) {
/*
res变量每次被赋值为prop属性名中的属性值
第一轮:prop = "b", res = c: [1, 2, 3]
第二轮:prop = "c", res = [1, 2, 3]
第三轮:prop = "2", res = 3
第四轮:prop = undefined
....
*/
// :第一轮: res=c:[1,2,3]
res = res[prop]
}
return res
}
3.5.3 有10000个数据,需要顺序的批量提交到服务器
const list = new Map()
for (let i = 0; i < 10000; i++) {
list.set(i, i)
}
console.log('有10000个数据,需要顺序的批量提交到服务器', list.size)
class ConcurrentReq {
constructor(startNum, endNum, method, url) {
this.startNum = startNum
this.endNum = endNum
this.method = method
this.url = url
this.proList = []
this.init()
}
createReq(contNum) {
return new Promise((resolve) => {
axios({
method: this.method,
url: this.url,
data: {
i: contNum
}
}).then(() => {
resolve(contNum)
})
})
}
creatPro(contNum, maxNum) {
const curPro = this.createReq(contNum)
this.startNum++
return curPro.then((res) => {
this.endNum++
if (this.startNum < maxNum + 1) {
this.creatPro(contNum, maxNum)
} else {
if (this.endNum === maxNum + 1) {
console.log(`结束`);
}
return res
}
})
}
init() {
for (let i = this.startNum; i <= this.endNum; i++) {
this.proList.push(this.creatPro(i, this.endNum))
}
Promise.all(this.proList).then(res => console.log(res))
}
}
let test = new ConcurrentReq(
list.get(0),
list.size - 1,
'post',
'http://127.0.0.1:8081/'
)
3.6 数据类型
js的数据类型分为两种:基本数据类型(原始类型)、引用数据类型(对象类型),基本类型数据存放在栈中 引用数据类型存放在堆中
3.6.1 基本数据类型
Null:空值,undefined:未定义,Boolean:布尔值,Number:数字,String:字符串,Symbol:表示独一无二的值 、BigInt :表示任意大的整数。
- null 和 undefined 的区别:在 if 语句中 null 和 undefined 都会转为false两者用相等运算符比较也是相等。undefined 代表的含义是未定义定义了形参没传实参等等;null 代表的含义是空对象。也作为对象原型链的终点主要赋值给可能会返回对象的遍历作为初始化。
- ES10新增:BigInt 表示任意大的整数:BigInt数据类型的目的是比Number数据类型支持的范围更大的整数值。在对大整数执行数学运算时,以任意精度表示整数的能力尤为重要。使用BigInt,整数溢出将不再是问题。
3.6.2 引用数据类型
Object、Array、 function、Date、RegExp。 JavaScript不支持创建任何自定义类型的数据,也就是说JavaScript中所有值的类型都是上面8中之一。
3.6.3 数据类型存储和堆栈内存
基本数据类型:直接存储在栈内存中,占据空间小,大小固定是属于被频繁使用的数据。
引用数据类型:将指针存储在栈中,值存储在堆中。当对象值赋值给另一个变量时,赋值的是对象的指针,指向同一块内存地址。
3.7 作用域和作用域链
3.7.1 作用域
作用域是定义变量的区域,它有一套访问变量的规则,这套规则来管理浏览器引擎如何在当前作用域以及嵌套的作用域中根据变量(标识符)进行变量查找。函数内部局部作用域(函数作用域),函数外面全局作用域。
全局作用域就是Js中最外层的作用域,在哪里都可以访问。函数作用域是js通过函数创建的一个独立作用域,只能在函数内部访问,函数可以嵌套,所以作用域也可以嵌套。Es6中新增了块级作用域(由大括号包裹,比如:if(){},for(){}等)
3.7.2 作用域链
各个作用域的嵌套关系组成一条作用域链。作用域链主要是进行标识符(变量和函数)的查询,标识符解析就是沿着作用域链一级一级的搜索标识符的过程,而作用域链就是保证对变量和函数的有序访问。
如果自身作用域中声明该变量,则无需使用作用域链。如果自身作用域中未声明该变量,则需要使用作用域链进行查找。
3.8 对象、数组、字符串的方法
3.8.1 object Api
Object.is() 是一种判断两个值是否相同的方法。
语法:Object.is(value1, value2);
参数:value1:要比较的第一个值。value2:要比较的第二个值。
返回值:一个布尔表达式,指示两个参数是否具有相同的值。
Object.assign() 方法用于将所有可枚举的自身属性从一个或多个源对象复制到目标对象。
语法:Object.assign(target, ...sources)
参数:target:目标对象——应用源属性的对象,修改后返回。sources:源对象——包含你要应用的属性的对象。
返回值:修改后的目标对象。
Object.entries() ES8的Object.entries是把对象转成键值对数组, [key, value] 对的数组。
语法:Object.entries(obj)
参数:obj:要返回其自己的可枚举字符串键属性 [key, value] 对的对象。返回值:给定对象自己的可枚举字符串键属性 [key, value] 对的数组。
Object.fromEntries则相反,是把键值对数组转为对象
Object.values() 方法返回给定对象自己的可枚举属性值的数组,其顺序与 for...in 循环提供的顺序相同。
语法:Object.values(obj)
参数:obj:要返回其可枚举自身属性值的对象。返回值:包含给定对象自己的可枚举属性值的数组。
Object.prototype.hasOwnProperty()
hasOwnProperty() 方法返回一个布尔值,指示对象是否具有指定的属性作为它自己的属性。
如果指定的属性是对象的直接属性,则该方法返回 true — 即使值为 null 或未定义。如果该属性是继承的或根本没有声明,则返回 false。
语法:hasOwnProperty(prop)
参数:prop:要测试的属性的字符串名称或符号。
返回值:如果对象将指定的属性作为自己的属性,则返回true;否则为false。
Object.keys()
Object.keys() 方法用于返回给定对象自己的可枚举属性名称的数组,以与普通循环相同的顺序迭代。
语法:Object.keys(obj)
参数:obj:要返回可枚举自身属性的对象。
返回值:表示给定对象的所有可枚举属性的字符串数组。
Object.prototype.toString()
toString() 方法返回一个表示对象的字符串。当对象将被表示为文本值或以期望字符串的方式引用对象时,将自动调用此方法 id。默认情况下,toString() 方法由从 Object 继承的每个对象继承。
语法:toString()
返回值:表示对象的字符串。
Object.freeze()
Object.freeze() 方法冻结一个对象,这意味着它不能再被更改。冻结对象可防止向其添加新属性,防止删除现有属性,防止更改现有属性的可枚举性、可配置性或可写性,并防止更改现有属性的值。它还可以防止其原型被更改。
语法:Object.freeze(obj)
参数:obj:要冻结的对象。返回值:传递给函数的对象。
Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。 (请打开浏览器控制台以查看运行结果。)
语法:const me = Object.create(person);
参数:
proto:新创建对象的原型对象。
propertiesObject
可选。需要传入一个对象,该对象的属性类型参照Object.defineProperties()的第二个参数。如果该参数被指定且不为 undefined,该传入对象的自有可枚举属性(即其自身定义的属性,而不是其原型链上的枚举属性)将为新创建的对象添加指定的属性值和对应的属性描述符。
返回值
一个新对象,带着指定的原型对象和属性。
3.8.2 Array Api
1、sort( ):sort 排序 如果下面参数的正反 控制 升序和降序 ,返回的是从新排序的原数组
2、splice( ):向数组的指定index处插入 返回的是被删除掉的元素的集合,会改变原有数组;截取类 没有参数,返回空数组,原数组不变;一个参数,从该参数表示的索引位开始截取,直至数组结束,返回截取的 数组,原数组改变;两个参数,第一个参数表示开始截取的索引位,第二个参数表示截取的长度,返回截取的 数组,原数组改变;三个或者更多参数,第三个及以后的参数表示要从截取位插入的值。会改变原数据
3、pop( ):从尾部删除一个元素 返回被删除掉的元素,改变原有数组。
4、push( ):向数组的末尾追加 返回值是添加数据后数组的新长度,改变原有数组。
5、unshift( ):向数组的开头添加 返回值是添加数据后数组的新长度,改变原有数组。
6、shift( ):从头部删除一个元素 返回被删除掉的元素,改变原有数组。
7、reverse( ): 原数组倒序 它的返回值是倒序之后的原数组
8、concat( ):数组合并。
9、slice( ):数组元素的截取,返回一个新数组,新数组是截取的元素,可以为负值。从数组中截取,如果不传参,会返回原数组。如果只传入一个参数,会从头部开始删除,直到数组结束,原数组不会改变;传入两个参数,第一个是开始截取的索引,第二个是结束截取的索引,不包含结束截取的这一项,原数组不会改变。最多可以接受两个参数。
10、join( ):讲数组进行分割成为字符串 这能分割一层在套一层就分隔不了了
11、toString( ):数组转字符串;
12、toLocaleString( ):将数组转换为本地数组。
13、forEach( ):数组进行遍历;
14、map( ):没有return时,对数组的遍历。有return时,返回一个新数组,该新数组的元素是经过过滤(逻辑处理)过的函数。
15、filter( ):对数组中的每一运行给定的函数,会返回满足该函数的项组成的数组。
16、every( ):当数组中每一个元素在callback上被返回true时就返回true。(注:every其实类似filter,只不过它的功能是判断是不是数组中的所有元素都符合条件,并且返回的是布尔值)。
17、some( ):当数组中有一个元素在callback上被返回true时就返回true。(注:every其实类似filter,只不过它的功能是判断是不是数组中的所有元素都符合条件,并且返回的是布尔值)。
18、reduce( ):回调函数中有4个参数。prev(之前计算过的值),next(之前计算过的下一个的值),index,arr。把数组列表计算成一个
19.isArray() 判断是否是数组
20. indexOf 找索如果找到了就会返回当前的一个下标,若果没找到就会反回-1
21. lastIndexOf 它是从最后一个值向前查找的 找索如果找到了就会返回当前的一个下标,若果没找到就会反回-1
22. Array.of() 填充单个值
23. Array.from() 来源是类数组
24.fill填充方法 可以传入3各参数 可以填充数组里的值也就是替换 如果一个值全部都替换掉 , 第一个参数就是值 第二个参数 从起始第几个 第三个参数就是最后一个
find 查找这一组数 符合条件的第一个数 给他返回出来
findIndex() 查找这一组数 符合条件的第一数的下标 给他返回出来 没有返回 -1
keys 属性名 values属性值 entries属性和属性值
forEach 循环遍历 有3个参数 无法使用 break continue , 参数一就是每个元素 参数二就是每个下标 参数三就是每个一项包扩下标和元素
### 改变数组本身的api
1. `pop()` 尾部弹出一个元素
2. `push()` 尾部插入一个元素
3. `shift()` 头部弹出一个元素
4. `unshift()` 头部插入一个元素
5. `sort([func])` 对数组进行排序,func有2各参数,其返回值小于0,那么参数1被排列到参数2之前,反之参数2排在参数1之前
6. `reverse()` 原位反转数组中的元素
7. `splice(pos,deleteCount,...item)` 返回修改后的数组,从pos开始删除deleteCount个元素,并在当前位置插入items
8. `copyWithin(pos[, start[, end]])` 复制从start到end(不包括end)的元素,到pos开始的索引,返回改变后的数组,浅拷贝
9. `arr.fill(value[, start[, end]])` 从start到end默认到数组最后一个位置,不包括end,填充val,返回填充后的数组
其他数组api不改变原数组
map 映射关系的数组 map 主要就是有返回值可以return 数组 判断的会返回boolean
1、map()方法返回一个新数组,新数组中的元素为原始数组中的每个元素调用函数处理后得到的值。
2、map()方法按照原始数组元素顺序依次处理元素。
注意:
map()不会对空数组进行检测。
map()不会改变原始数组。
map() 函数的作用是对数组中的每一个元素进行处理,返回新的元素。
filter 满足条件的都能返回 是一个数组
some返回boolean 循环数组 只要有一个成员通过了就会返回 true 反而 false
every返回boolean 循环数组 只有全部成员通过了就会返回 true 反而 false
reduce() 累加器 把上一次计算的值,给下一次计算进行相加
set 对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用
delete [1] delete 可以删除数组中的一向
**Array.isArray()** 用于确定传递的值是否是一个 [`Array`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array)。
flat 扁平化 将嵌套的数组 “拉平”,变成一维数组。该方法返回一个新数组,对原数据没有影响。// 参数写的就是代表要扁平到第几层
//1、every()
var arr = [1,56,80,5];
var main = arr.every(n => n > 0);
console.log(main) //输出:true
//2、some()
var arr = [1,-56,80,-5];
var main = arr.some(n => n > 0);
console.log(main) //输出:true
//3、reducer()
var arr = [10,20,30,40]
let result = arr.reduce(function(prev,next,index,arr){
return prev + next;
})
console.log(result); //输出:100
// 4、filter 返回满足要求的数组项组成的新数组
var arr3 = [3,6,7,12,20,64,35]
var result3 = arr3.filter((item,index,arr)=>{
return item > 3
})
console.log(result3) //[6,7,12,20,64,35]
// 5、map 返回每次函数调用的结果组成的数组
var arr4 = [1,2]
var result4 = arr4.map((item,index,arr)=>{
return `<span>${item}</span>`
})
console.log(result4)
/*[ '<span>1</span>',
'<span>2</span>', ]*/
ES6数组的常用方法:
1、Array.from( ):将对象或字符串转成数组,注意得有length。
2、Array.of( ): 将一组值转换为数组。
3、copyWithin(target,start(可选),end(可选)):数组内数据的复制替换
target:从该位置开始替换数据;
start:从该位置开始读取数据,默认为0;
end:到该位置停止数据的读取,默认为数组的长度
4、find( ):用于找出第一个符合条件的数组成员。
5、findIndex( ):返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1。
6、fill(value,start,end):使用给定值,填充一个数组。
value:填充的值;
start:开始填充的位置;
end:填充结束的位置。
7、keys( ):对键名的遍历。
8、values( ):对键值的遍历。
9、entries( ):对键值对的遍历。
10、includes( ):数组原型的方法,查找一个数值是否在数组中,只能判断一些简单类型的数据,对于复杂类型的数据无法判断。该方法接受两个参数,分别是查询的数据和初始的查询索引值。
11、flat( ):用于数组扁平,数组去除未定义。可以去除空项。
12、flatMap( ):对原数组的每个成员执行一个函数。
13、Map( ):是一组键值对的结构,具有极快的查找速度。
14、Set( ):Set和Map类似,也是一组key的集合,但不存储value。由于key不能重复,所以,在Set中,没有重复的key。
//1、Array.from() -- Array.of()
var arrayLink = {
"0":"a",
"1":"b",
"2":"c",
length:3
}
var arr = Array.from(arrayLink)
console.log(arr) // 输出: [a,b,c]
console.log(Array.from("abcdefg")) //输出:["a", "b", "c", "d", "e", "f", "g"]
console.log(Array.of(1,2,3,4,5)) //输出: [1, 2, 3, 4, 5]
//2、copyWithin()
var arr = [1,2,3,4,5];
var main = arr.copyWithin(0,3);
console.log(main); //输出:[4,5,3,4,5]
//3、find()
var arr = [1,-5,2,9,-6];
var main = arr.find(n => n < 0);
console.log(main); //输出:-5
//4、fill()
var arr = ["a","b","c","d"];
console.log(arr.fill(7,1,2));//输出:["a",7,"c","d"]
//5、keys() values() entries()
var arr = ["a","b","c","d"];
for(let index of arr.keys()){
console.log(index);
}
for(let elem of arr.values()){
console.log(elem);
}
for(let [index,elem] of arr.entries()){
console.log(index,elem);
}
//6、includes()
let arr = [12,34,223,45,67]
console.log(arr.includes(45)) //输出:true
[1, 2, NaN].includes(NaN) // true
[1, 2, NaN].indexOf(NaN) // -1
//7、Map
var m = new Map([['Michael', 95], ['Bob', 75], ['Tracy', 85]]);
m.get('Michael'); // 95
//初始化Map需要一个二维数组,或者直接初始化一个空Map。Map具有以下方法:
var m = new Map(); // 空Map
m.set('Adam', 67); // 添加新的key-value
m.set('Bob', 59);
m.has('Adam'); // 是否存在key 'Adam': true
m.get('Adam'); // 67
m.delete('Adam'); // 删除key 'Adam'
m.get('Adam'); // undefined
//由于一个key只能对应一个value,所以,多次对一个key放入value,后面的值会把前面的值冲掉:
var m = new Map();
m.set('Adam', 67);
m.set('Adam', 88);
m.get('Adam'); // 88
//8、Set
//要创建一个Set,需要提供一个Array作为输入,或者直接创建一个空Set:
var s1 = new Set(); // 空Set
var s2 = new Set([1, 2, 3]); // 含1, 2, 3
//重复元素在Set中自动被过滤:
var s = new Set([1, 2, 3, 3, '3']);
s; // Set {1, 2, 3, "3"} 注意:数字3和字符串'3'是不同的元素
//通过add(key)方法可以添加元素到Set中,可以重复添加,但不会有效果:
s.add(4);
s; // Set {1, 2, 3, 4}
s.add(4);
s; // 仍然是 Set {1, 2, 3, 4}
//通过delete(key)方法可以删除元素:
var s = new Set([1, 2, 3]);
s; // Set {1, 2, 3}
s.delete(3);
s; // Set {1, 2}
3.8.3 String Api
1、chartAt( ):返回在指定位置的字符;
2、concat( ):返回新的字符串**,将一个或多个字符串与原字符串连接合并
3、indexOf( ):检索字符串,返回第一次出现的索引,没有出现则为-1
4、lastIndexOf(searchValue[ fromIndex]) 返回从字符串尾部开始第一次出现的索引,没有则-1,fromIndex的值相对于从尾部开始的索引
5、split( ):返回一个以指定分隔符出现位置分隔而成的一个数组,数组元素不包含分隔符
6、substr( ):从起始索引号提取字符串中指定数目的字符;
7、substring( ):提取字符串中两个指定的索引号之间的字符;
8、toLowerCase( ):字符串转小写;
9、toUpperCase( ):字符串转大写;
10、valueOf( ):返回某个字符串对象的原始值;
11、trim( ):删除字符串两边的空格;
12、trimeState 取出开始的空格
13、trimeEnd 去除末尾空格
14、includes(searchString[, position])返回boolean,判断一个字符串是否包含在另一个字符串中,从postition索引开始搜寻,默认0
15、slice( ):提取字符串片段,并在新的字符串中返回被提取的部分;
16、search(regexp)返回首次匹配到的索引,没有则-1,执行正则表达式和 String 对象之间的一个搜索匹配
17、toString()返回一个表示调用对象的字符串,该方法返回指定对象的字符串形式
18、trim()返回去掉两端空白后的新字符串 还有trimend trimstart
19、replace() 把指定的字符串替换成为别的字符
3.9 this指向
在全局的环境下this是指向window 的普通函数调用直接调用中的this 会指向 window, 严格模式下this会指向 undefined,自执行函数 this 指向 window,定时器中的 this 指向 window
在对象里调用的this,指向调用函数的那个对象,在构造函数以及类中的this,构造函数配合 new 使用, 而 new 关键字会将构造函数中的 this 指向实例化对象,所以构造函数中的 this 指向 当前实例化的对象。
在方法中的this谁调用就指向谁。箭头函数没有自己的 this,箭头函数的this在定义的时候,会继承自外层第一个普通函数的this
3.10 闭包
-
闭包的概念就是:只有权利访问另一个函数作用域中的变量,一般就是函数包裹着函数。
-
闭包可以重用一个变量,且保证这个变量不会被污染的一种机制。这些变量的值始终保持在内存中,不会被垃圾回收机制处理。
-
闭包的缺点:由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
-
为什么要用闭包:使用场景 : 防抖、节流、函数套函数避免全局污染
闭包原理
函数执行分成两个阶段(预编译阶段和执行阶段)。
1.在预编译阶段,如果发现内部函数使用了外部函数的变量,则会在内存中创建一个“闭包”对象并保存对应变量值,
如果已存在“闭包”,则只需要增加对应属性值即可。
2.执行完后,函数执行上下文会被销毁,函数对“闭包”对象的引用也会被销毁,但其内部函数还持用该“闭包”的引用,
所以内部函数可以继续使用“外部函数”中的变量
利用了函数作用域链的特性,一个函数内部定义的函数会将包含外部函数的活动对象添加到它的作用域链中,函数执行完毕,其执行作用域链销毁,
但因内部函数的作用域链仍然在引用这个活动对象,所以其活动对象不会被销毁,直到内部函数被烧毁后才被销毁。
3.11 call、apply、bind
都是改变this指向和函数调用,实际上call与apply的功能是一样的比如可以调用父构造函数来实现继承等等,但两者传参不一样。
call方法第二个参数是参数列表arg1, arg2, …。
apply方法第二个参数是一个数组[arg1, arg2, …]。
其上两个方法使用后可直接调用。 bind 传参后不会立即执行,而是返回一个改变了this指向的函数,这个函数可以继续传参,且执行,需要类似于bind()()两个括号才能调⽤。
Function.prototype._call = function (context) {
if (typeof context === undefined || typeof context === null) {
context = window
}
const symbol = Symbol()
context[symbol] = this
const args = [...arguments].slice(1)
const result = context[symbol](...args)
delete context[symbol]
return result
}
Function.prototype._apply = function (context) {
if (typeof context === undefined || typeof context === null) {
context = window
}
const symbol = Symbol()
context[symbol] = this
let result
if (arguments[1]) {
result = context[symbol](...arguments[1])
} else {
result = context[symbol]()
}
delete context[symbol]
return result
}
Function.prototype._bind = function (context) {
if (typeof context === undefined || typeof context === null) {
context = window
}
const _this = this
const args = [...arguments].slice(1)
return function Fn() {
if (this instanceof Fn) {
return new _this(...args, ...arguments)
}
return _this.apply(context, args.concat(...arguments))
}
}
3.12 函数柯里化
3.12.1 概念
把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
3.12.2 特点
- 接收单一参数,将更多的参数通过回调函数来搞定
- 返回一个新函数,用于处理所有的想要传入的参数
- 需要利用call/apply与arguments对象收集参数
- 返回的这个函数正是用来处理收集起来的参数。
3.12.3 用途
函数柯里化是对闭包的一种应用形式,延迟计算、参数复用、动态生成函数(都是闭包的用途)。
3.12.4 例子
柯里化函数:把一个多参数的函数转化为单参数函数的方法。并且返回接受余下的参数而且返回结果的新函数的技术。
// 简单的相加函数
var add = function (x,y) {
return x + y
}
// 调用:
add(1,2)
// 柯里化以后
var add = function (x) { //柯里化函数(闭包)
return function (y) {
return x + y
}
}
add(1)(2)
3.13 高阶函数
接收函数作为参数或者返回函数的函数
function higherOrderFunction(param,callback){
return callback(param);
}
3.14 new的原理
首先在堆内存中开辟一个空间
- 创建一个空的简单 JavaScript 对象(即 {})
- 为步骤 1 新创建的对象添加属性 proto,将该属性链接至构造函数的原型对象
- 将步骤 1 新创建的对象作为 this 的上下文
- 如果该函数没有返回对象,则返回 this
function _new(fn,...args){ // ...args为ES6展开符,也可以使用arguments
//先用Object创建一个空的对象,
const obj = Object.create(fn.prototype) //fn.prototype代表 用当前对象的原型去创建
//现在obj就代表Dog了,但是参数和this指向没有修改
const rel = fn.apply(obj,args)
//正常规定,如何fn返回的是null或undefined(也就是不返回内容),我们返回的是obj,否则返回rel
return rel instanceof Object ? rel : obj
}
3.15 垃圾回收机制和内存机制
3.15.1 垃圾回收
浏览器的js具有自动垃圾回收机制,垃圾回收机制也就是自动内存管理机制,垃圾收集器会定期的找出那些不在继续使用的变量,然后释放内存。但是这个过程不是实时的,因为GC开销比较大并且时停止响应其他操作,所以垃圾回收器会按照固定的时间间隔周期性的执行。
标记清除:大部分浏览器使用这种垃圾回收,当变量进入执行环境(声明变量)的时候,垃圾回收器将该变量进行了标记,当该变量离开环境的时候,将其再度标记,随之进行删除。
引用计数:这种方式常常会引起内存的泄露,主要存在于低版本的浏览器。它的机制就是跟踪某一个值得引用次数,当声明一个变量并且将一个引用类型赋值给变量得时候引用次数加1,当这个变量指向其他一个时引用次数减1,当为0时出发回收机制进行回收。
3.15.2 内存泄漏
如果 那些不再使用的变量,它们所占用的内存 不去清除的话就会造成内存泄漏。
内存泄露其实就是我们的程序中已经动态分配的堆内存,由于某些原因没有得到释放,造成系统内存的浪费导致程序运行速度减慢甚至系统崩溃等严重后果。
比如说:
- 全局变量
- 闭包:在闭包中引入闭包外部的变量时,当闭包结束时此对象无法被垃圾回收(GC)。
- DOM:当原有的DOM被移除时,子结点引用没有被移除则无法回收
- Times计时器泄露
3.16 事件循环(event Loop)
由于js是单线程语言,所以任务是一个一个顺序执行的。如果一个任务耗时过长,那么后面的一个任务就必须等待。那么问题来了,假如浏览网站时图片资源很大加载的很慢,难道网页要一直卡到图片完全显示出来?所以js的任务分为了两类同步任务和异步任务。而异步任务又分为宏任务和微任务,因此js执行的过程为:同步任务->微任务->宏任务。
- 先执行所有同步任务,碰到异步任务放到任务队列中
- 同步任务执行完毕,开始执行当前所有的异步任务
- 先执行任务队列里面所有的微任务
- 然后执行一个宏任务
- 然后再执行所有的微任务
- 再执行一个宏任务,再执行所有的微任务·······依次类推到执行结束。
3-6的这个循环称为事件循环Event Loop。
事件循环是JavaScript实现异步的一种方法,也是JavaScript的执行机制。
3.16.1 同步任务
在主线程上排队执行的任务,只有前一个任务执行完毕,才能继续执行下一个任务。new promise()、console.log()属于同步任务。
3.16.2 异步任务
不进入主线程、而进入"任务队列"的任务;只有等主线程任务全部执行完毕,"任务队列"的任务才会进入主线程执行。
js异步有一个机制,遇到宏任务先执行宏任务,将宏任务放入eventqueue,然后在执行微任务,将微任务放入eventqueue里。但这两个queue不是同一个。当调用时先从微任务里拿这个回调函数,然后再从宏任务里的queue中拿回调函数。
- 宏任务:包括整体代码script(可以理解为外层同步代码),setTimeout,setInterval、setImmediate、还有如 I/O 操作、UI 渲染等。
- 微任务:原生Promise.then、process.nextTick、Object.observe(已废弃)、对 Dom 变化监听的 MutationObserver。
3.17 延迟加载
JavaScript 是单线程(js不走完下面不会走是因为同步)会阻塞DOM的解析,因此也就会阻塞DOM的加载。所以有时候我们希望延迟JS的加载来提高页面的加载速度。
- 把JS放在页面的最底部
- script标签的defer属性:脚本会立即下载但延迟到整个页面加载完毕再执行。该属性对于内联脚本无作用 (即没有 「src」 属性的脚本)。
- 是在外部JS加载完成后,浏览器空闲时,Load事件触发前执行,标记为async的脚本并不保证按照指定他们的先后顺序执行, 该属性对于内联脚本无作用 (即没有 「src」 属性的脚本)。
- 动态创建script标签,监听dom加载完毕再引入js文件。
3.18 预解析
JS代码在执行前,浏览器会对js代码进行扫描,默认的把所有带var和function声明的变量进行提前的声明或者定义,遵循先解析后使用的原则。分为两类变量预解析(变量提升)和函数预解析(函数提升)。
3.18.1 变量预解析
JS会把申明的变量,提升到当前作用域的最前面,只申明不赋值。let、const和var都有提升,但是let定义的变量没有赋值之前是不可以使用,var可以使用是undefined。任何一个作用域执行代码之前都要预解析。
var声明的变量会被挂在到running execution context(执行上下文)的VariableEnvironment(变量环境)上,会在当前作用域的变量对象实例化的过程中被声明并赋值为undefined。 在赋值语句执行时才会为变量赋值。
let,const 声明的变量会被挂在到running execution context(执行上下文)的LexicalEnvironment(词法环境)上,会在当前作用域的变量对象实例化的过程中被创建,但是直到变量的语法声明代码被执行之前,他们都是不可被访问的。同样在赋值语句执行时才会为变量赋值。
3.18.2 函数预解析
JS会把带有名字的函数,提升到当前作用域最前面,只定义不调用。并不是所有的函数都要预解析,比如匿名函数(表达式函数)不会预解析。
3.18.3 案例
function a(){
alert(1);
}
var a;
alert(a)
/*
1.定义(全局) 注意:函数a和变量a重名的时候,函数占优(也可以理解为变量a比函数a提的更前面,导致被函数a覆盖了),所以下面的定义只定义函数a
function a(){...}
2.执行
alert(a) //弹出函数体
*/
alert(a)
var a = 10;
alert(a);
function a(){
alert(20);
}
alert(a);
var a = 30;
function a(){
alert(40)
}
alert(a)
/*
1.定义var a被function a覆盖
function a(){alert(40)}
2.执行
alert(a) //函数体 function a(){alert(40)}
a = 10; //把function a(){}修改了
alert(a) //10
function a(){alert(20);} // 不执行,因为被覆盖了function a(){alert(40)}
alert(a) //10
a = 30
alert(a) //30
*/
a();
var a = funtion() {alert(1)};
a();
function a(){alert(2)};
a();
var a = function(){alert(3)};
a();
/*
1.定义(var a;但是后面有个同名的function a(),所以var a被function a()顶替了,后面还有个var a,
提升var a;但因为函数占优,所以只定义function a(){...})
function a(){alert(2)};
2.执行
a() //2
a = funtion() {alert(1)};
a() // 1
function a(){alert(2)}; // 不执行 因为已经提升了
a() // 1
a = function(){alert(3)};
a() // 3
*/
3.18.4 预解析的优点
- 提高性能
在JS代码执行之前,会进行语法检查和预编译,并且这一操作只进行一次。这么做就是为了提高性能,如果没有这一步,那么每次执行代码前都必须重新解析一遍该变量(函数),而这是没有必要的,因为变量(函数)的代码并不会改变,解析一遍就够了。
在解析的过程中,还会为函数生成预编译代码。在预编译时,会统计声明了哪些变量、创建了哪些函数,并对函数的代码进行压缩,去除注释、不必要的空白等。这样做的好处就是每次执行函数时都可以直接为该函数分配栈空间(不需要再解析一遍去获取代码中声明了哪些变量,创建了哪些函数),并且因为代码压缩的原因,代码执行也更快了。
- 容错性更好
a = 1
var a
console.log(a) //1
如果没有变量提升,这段代码就会报错导致的问题
var tmp = new Date();
function fn(){
console.log(tmp);
if(false){
var tmp = 'hello nanjiu';
}
}
fn(); // undefined
在这个函数中,原本是要打印出外层的tmp变量,但是因为变量提升的问题,内层定义的tmp被提到函数内部的最顶部,
相当于覆盖了外层的tmp,所以打印结果为undefined。
var tmp = 'hello nan jiu';
for (var i = 0; i < tmp.length; i++) {
console.log(tmp[i]);
}
console.log(i); // 13
由于遍历时定义的i会变量提升成为一个全局变量,在函数结束之后不会被销毁,所以打印出来13。
总结
解析和预编译过程中的声明提升可以提高性能,让函数可以在执行时预先为变量分配栈空间
声明提升还可以提高JS代码的容错性,使一些不规范的代码也可以正常执行
函数是一等公民,当函数声明与变量声明冲突时,变量提升时函数优先级更高,会忽略同名的变量声明
3.19 服务端渲染(SSR)
服务端渲染的模式下,当用户第一次请求页面时,由服务器把需要的组件或页面渲染成 HTML 字符串,然后把它返回给客户端。客户端拿到手的,是可以直接渲染然后呈现给用户的 HTML 内容,不需要为了生成 DOM 内容自己再去跑一遍 JS 代码。使用服务端渲染的网站,可以说是“所见即所得”,页面上呈现的内容,我们在 html 源文件里也能找到。有了服务端渲染,当请求用户页面时,返回的body里已经有了首屏的html结构,之后结合css显示出来。
vue全家桶或者react全家桶,都是推荐通过服务端渲染来实现路由的。
3.19.1 优点
- 首屏渲染快(关键性问题):相比于加载单页应用,我只需要加载当前页面的内容,而不需要像 React 或者 Vue 一样加载全部的 js 文件。
- SEO(搜索引擎)优化:不同爬虫工作原理类似,只会爬取源码,不会执行网站的任何脚本。
- 可以生成缓存片段、节能。
3.19.2 缺点
- 为了实现服务端渲染,应用代码中需要兼容服务端和客户端两种运行情况。
- 由于服务器增加了渲染HTML的需求,使得原本只需要输出静态资源文件的nodejs服务,新增了数据获取的IO和渲染HTML的CPU占用。
- 用户体验较差,不容易维护、通常前端改了部分html或者css,后端也需要改。
3.20 onload和domcontentloaded事件
- onload:当一个资源及其依赖资源已完成加载时,将触发onload事件。
- domcontentloaded:当初始的HTML文档被完全加载和解析完成之后, DOMContentLoaded事件被触发,而无需等待样式表、图像和子框架的完成加载。
区别: ①onload事件是DOM事件,onDOMContentLoaded是HTML5事件。 ②onload事件会被样式表、图像和子框架阻塞,而onDOMContentLoaded不会。 ③当加载的脚本内容并不包含立即执行DOM操作时,使用onDOMContentLoaded事件是个更好的选择,会比onload事件执行时间更早。
3.21 cookies,sessionStorage 和 localStorage、localForage
- cookies
一个大小不超过4K的小型文本数据,一般由服务器生成,可以设置失效时间;若没有设置时间,关闭浏览器cookie失效,若设置了 时间,cookie就会存放在硬盘里,过期才失效,每次http请求,header都携带cookie。
- sessionStorage
5M或者更大,永久有效,窗口或者浏览器关闭也会一直保存,除非手动永久清除或者js代码清除,因此用作持久数据,不参与和服务器的通信。
- localStorage
关闭页面或浏览器后被清除。存 放数据大小为一般为 5MB,而且它仅在客户端(即浏览器)中保存,不参与和服务器的通信。
- localForage
localForage 是一个 JavaScript 库,通过简单类似 localStorage API 的异步存储来改进你的 Web 应用程序的离线体验。它能存储多种类型的数据适合存储大量数据,而不仅仅是字符串。
localForage 有一个优雅降级策略,若浏览器不支持 IndexedDB 或 WebSQL,则使用 localStorage。在所有主流浏览器中都可用:Chrome,Firefox,IE 和 Safari(包括 Safari Mobile)。
localforage.setItem('key', 'value').then(function () {
return localforage.getItem('key');
}).then(function (value) {
// we got our value
}).catch(function (err) {
console.log(err);
});
// createInstance 创建并返回一个 localForage 的新实例。每个实例对象都有独立的数据库,而不会影响到其他实例
var store = localforage.createInstance({
name: "nameHere"
});
var otherStore = localforage.createInstance({
name: "otherName"
});
// 设置某个数据仓库 key 的值不会影响到另一个数据仓库
store.setItem("key1", "value1");
otherStore.setItem("key2", "value2");
IndexedDB是浏览器提供的本地数据库,它可以被网页脚本创建和操作。允许储存大量数据,提供查找接口,还能建立索引。就数据库类型而言,不属于关系型数据库(不支持 SQL 查询语句),更接近 NoSQL 数据库。特点:
1. 键值对存储: 内部采用对象仓库(Object store)存放数据。所有类型数据都可以直接存入。对象仓库中,数据以”键值对“形式保存,每个数据记录都有对应的主键。
2. 异步:防止大量数据的读写,拖慢网页的展现。
3. 支持事务:意味着一系列操作步骤中,只要有异步失败,整个事务就都取消,数据库回滚到事务发生之前的记录,不存在只改写部分数据的情况。
4. 同源限制:每个数据库对应创建他的域名,网页只能访问自身域名下的数据库,而不能访问跨域数据库。
5. 支持二进制存储
6. 存储空间大: 取决于硬件:
- Chrome 允许浏览器使用多达 80% 的总磁盘空间。一个源最多可以使用总磁盘空间的 60%。
- IE 10 及以上最多可存储 250MB。
- Firefox 允许浏览器使用多达 50% 的可用磁盘空间。
- Safari 允许大约 1GB。
3.22 location、之navigator和history
- location
对象存储了当前文档位置(URL)相关的信息,简单地说就是网页地址字符串。使用 window 对象的 location 属性可以访问。
href会重新定位到一个URL,hash会跳到当前页面中的anchor名字的标记(如果有),而且页面不会被重新加载
- navigator
window 对象给我们提供了一个 history 对象,与浏览器历史记录进行交互。该对象包含用户(在浏览器窗口中) 访问过的 URL。
- history
window 对象给我们提供了一个 history 对象,与浏览器历史记录进行交互。该对象包含用户(在浏览器窗口中) 访问过的 URL。
3.23 offset、client和scroll
- 偏移量offset:就是偏移量, 我们使用 offset系列相关属性可以动态的得到该元素的位置(偏移)、大小等。
element.offsetParent | 返回作为该元素带有定位的父级元素,如果父级都没有定位则返回body |
element.offsetTop | 返回元素相对带有定位父元素上方的偏移 |
element.offsetLeft | 返回元素相对带有定位父元素左边框的偏移 |
element.offsetWidth | 返回自身包括padding、边框、内容区的宽度,返回数值不带单位 |
element.offsetHeight | 返回自身包括padding、边框、内容区的高度,返回数值不带单位 |
- 元素可视区client:使用client系列的相关属性来获取元素可视区的相关信忘.。通过 client系列的相关属性可以动态的得到该元素的边框大小、元素大小等。
clientTop | 返回元素上边框的大小 |
clientLeft | 返回元素左边框的大小 |
clientWidth | 返回自身包括padding 、内容区的宽度,不含边框,返回数值不带单位 |
clientHeight | 返回白身包括padding 、内容区的高度,不含边框,返回数值不带单位 |
- 元素滚动scroll:我们使用 scroll 的相关属性可以动态的得到该元素的大小、滚动距离等。
scrollTop | 返回被卷去的上侧距离,返回数值不带单位 |
scrollLeft | 返回被卷去的左侧距离,返回数值不带单位 |
scrollWidth | 返回自身实际的宽度。不含边框,返回数值不带单位 |
scrollHeight | 返回自身实际的高度。不含边框,返回数值不带单位 |
3.23 操作dom的常用API
拿到指定节点
var id = document.getElementById("id"); //返回带有指定id的元素
var name = document.getElementByTagName("li"); //返回带有指定标签的元素
var class = document.getElementByClassName("class"); //返回带有包含执行类名的所有元素节点列表。`
创建DOM节点
var node = document.createElement("div");
var attr = document.createAttribute("class");
var text = document.createTextNode("菜呀菜");`
插入DOM节点
node.appendChild(text) //插入新的子节点
node.insertBefore(pre,child) //在node元素内child前加入新元素`
删除DOM节点
node.removeChild(text) //从父元素删除子元素节点
修改DOM节点
node.setAttribute("class","name") //修改设置属性节点
node.replaceChild(pre,child) //父节点内新子节点替换旧子节点`
常用DOM属性
node.innerHtml //获取/替换元素内容
node.parentNode //元素节点的父节点
node.parentElement //元素节点的父元素节点(一般与Node节点相同)
node.firstChild //属性的第一个节点
node.lastChild //属性的最后一个节点
node.nextSibling //节点元素后的兄弟元素(包括回车,空格,换行)
node.nextElementSibling //节点元素后的兄弟元素节点
node.previousSibling //获取元素的上一个兄弟节点(元素,文本,注释)
node.previousElementSibling //获取元素的上一个兄弟节点(只包含元素节点)
node.childNodes //元素节点的子节点(空格,换行默认为文本节点)
node.children //返回当前元素的所有元素节点
node.nodeValue //获取节点值
node.nodeName //获取节点名字
node.attributes //元素节点的属性节点
node.getAttribute("name") //元素节点的某个属性节点
node.style.width = "200px" //设置css样式`
3.24 编程范式
3.24.1 命令(指令)式编程
专注于”如何去做”,这样不管”做什么”,都会按照你的命令去做。
例如你想通过点击改变页面中某一个元素,首先要获取按钮,再给按钮添加点击事件,获取要改变的元素,执行点击函数,改变元素达到自己的目的,这是一步一步的步骤操作,就如同给计算机发布命令,一步一步执行,这就是命令式编程。
let a=1;
let b=2;
let c=a+b
3.24.2 声明式编程
专注于”做什么”而不是”如何去做”。在更高层面写代码,更关心的是目标,而不是底层算法实现的过程。
以Vue为例,在页面中通过 {{ }} 显示一个data里面的变量,你只需要改变这个变量,页面就会跟着刷新,这就是你只需要结果,vue 内部去处理过程,这就是声明式编程。
SELECT * FROM collection WHERE num > 1;
3.24.3 函数式编程
把运算过程抽象成一个函数,任何地方都可去重用这些函数,屏蔽实现的细节,只要管目标的逻辑。
抽象出来的函数是细腻的函数,可以将其组合成功能更加强大的函数。
// 非函数式
const num1 = 2
const num2 = 3
const sum = num1 + num2
console.log(sum)
// 函数式
function add(n1, n2) {
return n1 + n2
}
const sum = add(2, 3)
console.log(sum)
3.25 iframe
iframe 元素会创建包含另外一个文档的内联框架(即行内框架)。
一般用来包含别的页面,例如我们可以在我们自己的网站页面加载别人网站的内容。
<iframe src="demo_iframe_sandbox.htm"></iframe>
当localStorage超过了5M也可以用iframe解决。假如现在a.com域名下localstorage存不下了,我们可以使用iframe创建b.com域框架(子页面)用于存储a.com剩下的数据。然后使用postMessage读写数据。
window.postMessage()方法可以安全地实现跨源通信。通常,对于两个不同页面的脚本,只有当执行它们的页面位于具有相同的协议(通常为https),端口号(443为https的默认值),以及主机 (两个页面的模数Document.domain设置为相同的值) 时,这两个脚本才能相互通信。window.postMessage()方法提供了一种受控机制来规避此限制,只要正确的使用,这种方法就很安全。
3.25.1 优点
- iframe能够原封不动的把嵌入的网页展现出来。
- 如果有多个网页引用iframe,那么你只需要修改iframe的内容,就可以实现调用的每一个页面内容的更改,方便快捷。
- 网页如果为了统一风格,头部和版本都是一样的,就可以写成一个页面,用iframe来嵌套,可以增加代码的可重用。
- 如果遇到加载缓慢的第三方内容如图标和广告,这些问题可以由iframe来解决。
3.25.2 缺点
- 会产生很多页面不易管理。
- iframe框架结构有时会让人感到迷惑,如果框架个数多的话,可能会出现上下、左右滚动条,会分散访问者的注意力,用户体验度差。
- 代码复杂,无法被一些搜索引擎索引到,这一点很关键,现在的搜索引擎爬虫还不能很好的处理iframe中的内容,所以使用iframe会不利于搜索引擎优化。
- 很多的移动设备(PDA 手机)无法完全显示框架,设备兼容性差。
- iframe框架页面会增加服务器的http请求,对于大型网站是不可取的。
3.25.3 常用属性
- frameborder:是否显示边框。
- height:框架作为一个普通元素的高度。
- width:框架作为一个普通元素的宽度,建议使用css设置。
- name:框架的名称,window.frames[name]时专用的属性。
- scrolling:框架的是否滚动。yes,no,auto。
- src:内框架的地址,可以使页面地址,也可以是图片的地址。
- srcdoc :用来替代原来HTML body里面的内容。
- sandbox:对iframe进行一些列限制。
3.26 0.1+0.2 != 0.3
3.26.1 存储原理
在计算机中数字无论是定点数还是浮点数都是以多位二进制的方式进行存储的。
在JS中数字采用的IEEE 754的双精度标准进行存储(存储一个数值所使用的二进制位数比较多,精度更准确)。
3.26.1 原因
对于像0.1这样的数值用二进制表示你就会发现无法整除,最后算下来会是 0.000110011…由于存储空间有限,最后计算机会舍弃后面的数值,所以我们最后就只能得到一个近似值。
JS中采用的IEEE 754的双精度标准也是一样的道理在存储空间有限的情况下,当出现这种无法整除的小数的时候就会取一个近似值,在js中如果这个近似值足够近似,那么js就会认为他就是那个值。
console.log(0.1000000000000001)
// 0.1000000000000001 (中间14个0,会打印除本身)
console.log(0.10000000000000001)
// 0.1 (中间15个0,js会认为两个值足够近似,所以输出0.1)
在0.1 + 0.2这个式子中,0.1和0.2都是近似表示的,在他们相加的时候,两个近似值进行了计算,导致最后得到的值是0.30000000000000004,此时对于JS来说,其不够近似于0.3,于是就出现了0.1 + 0.2 != 0.3 这个现象。 当然,也并非所有的近似值相加都得不到正确的结果。
3.26.1 解决办法
// 方式一:将浮点数转化成整数计算。因为整数都是可以精确表示的。
console.log((0.1*10+0.2*10)/10===0.3)
// 方式二: js的Number对象有一个保留小数位数的方法:toFixed()。
console.log(parseFloat((0.1+0.2).toFixed(5)))
3.27 require和import
当前端应用越来越复杂时,我们想要将代码分割成不同的模块,便于复用、按需加载等。
require 和 import 分别是不同模块化规范下引入模块的语句。
3.27.1 require规范
module变量代表当前模块,它的exports属性是对外的接口。通过exports可以将模块从模块中导出,其他文件加载该模块实际上就是读取module.exports变量,他们可以是变量、函数、对象等。在node中如果用exports进行导出的话系统会系统帮您转成module.exports的,只是导出需要定义导出名。
const fs = require('fs')
exports.fs = fs
module.exports = fs
3.27.2 import规范
是ES6中的语法标准也是用来加载模块文件的,import函数可以读取并执行一个JavaScript文件,然后返回该模块的export命令指定输出的代码。export与export default均可用于导出常量、函数、文件、模块,export可以有多个,export default只能有一个。
import fs from 'fs'
import {readFile} from 'fs' //从 fs 导入 readFile 模块
import {default as fs} from 'fs' //从 fs 中导入使用 export default 导出的模块
import * as fileSystem from 'fs' //从 fs 导入所有模块,引用对象名为 fileSystem
import {readFile as read} from 'fs' //从 fs 导入 readFile 模块,引用对象名为 read
export default fs
export const fs
export function readFile
export {readFile, read}
export * from 'fs'
3.27.3 区别
- require/exports 是运行时动态加载,import/export 是静态编译
CommonJS 加载的是一个对象(即 module.exports 属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。- 阮一峰
- require/exports 输出的是一个值的拷贝,import/export 模块输出的是值的引用
require/exports 输出的是值的拷贝。也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。
import/export 模块输出的是值的引用。JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。若文件引用的模块值改变,require 引入的模块值不会改变,而 import 引入的模块值会改变。
- require/exports 是 CommonJS 规范引入方式,import/export 是 ES6 的一个语法标准,如果要兼容浏览器的话必须转化成 ES5 的语法。
require 相当于module.exports的传送门,module.exports 后面的内容是什么,require 的结果就是什么,比如对象、数字、字符串、函数等,然后再把 require 的结果赋值给某个变量。并可以运用在代码的任何地方,甚至不需要赋值给某个变量之后再使用。
import 是编译时运行的(require是运行时的),它必须放在文件开头,而且使用格式也是确定的,不容置疑。它不会将整个模块运行后赋值给某个变量,而是只选择import的接口进行编译,这样在性能上比require好很多。
3.28 ES6+
ES 的全称是 ECMAScript , 它是由 ECMA 国际标准化组织,制定的一项脚本语言的标准化规范。
3.28.1 箭头函数
- ES6中新增的定义函数的方式
() => {} //():代表是函数; =>:必须要的符号,指向哪一个代码块;{}:函数体
const fn = () => {}//代表把一个函数赋值给fn
- 函数体中只有一句代码,且代码的执行结果就是返回值,可以省略大括号
function sum(num1, num2) {
return num1 + num2;
}
//es6写法
const sum = (num1, num2) => num1 + num2;
- 如果形参只有一个,可以省略小括号
function fn (v) {
return v;
}
//es6写法
const fn = v => v;
- 箭头函数不绑定this关键字,箭头函数中的this,指向的是函数定义位置的上下文this,箭头函数不能使用arguments
const obj = { name: '张三'}
function fn () {
console.log(this);//this 指向 是obj对象
return () => {
console.log(this);//this 指向 的是箭头函数定义的位置,那么这个箭头函数定义在fn里面,而这个fn指向是的obj对象,所以这个this也指向是obj对象
}
}
const resFn = fn.call(obj);
resFn();
注意事项:
- 箭头函数是匿名函数不能作为构造函数,不能使用new。
- 箭头函数不绑定arguments,取而代之用rest参数…解决。
- this指向不同,箭头函数的this在定义的时候继承自外层第一个普通函数的this。
- 箭头函数通过call()或apply()调用一个函数,只传入了一个参数,对this并没有影响。
- 箭头函数没有prototype(原型),所以箭头函数本身没有this。
- 箭头函数不能当做Generator函数,不能使用yield关键字。
- 箭头函数不能通过call()、apply()、bind()方法直接修改它的this指向。
3.28.2 变量
let
ES6中新增了用于声明变量的关键字。
- let声明的变量只在所处于的块级有效。
if (true) {
let a = 10;
}
console.log(a) // a is not defined
- 不存在变量提升。
console.log(a); // a is not defined
let a = 20;
- 暂时性死区。
// 利用let声明的变量会绑定在这个块级作用域,不会受外界的影响
var tmp = 123;
if (true) {
tmp = 'abc';
//console.log(tmp);
//上面两条语句都会报错,初始化前无法访问
let tmp;
}
const
声明常量,常量就是值(内存地址)不能变化的量。
- 具有块级作用域
if (true) {
const a = 10;
}
console.log(a) // a is not defined
- 声明常量时必须赋值
const PI; // Missing initializer in const declaration
- 常量赋值后,值不能修改
const PI = 3.14;
PI = 100; // Assignment to constant variable.
const ary = [100, 200];
ary[0] = 'a';
ary[1] = 'b';
console.log(ary); // ['a', 'b'];
ary = ['a', 'b']; // Assignment to constant variable.
let、const和var的区别
- 使用var声明的变量,其作用域为该语句所在的函数内,且存在变量提升现象。
- 使用let声明的变量,其作用域为该语句所在的代码块内,不存在变量提升。
- 使用const声明的是常量,在后面出现的代码中不能再修改该常量的值。
能用const的情况下尽量使用const,大多数情况使用let,避免使用var。 const > let > var const声明的好处,一让阅读代码的人知道该变量不可修改,二是防止在修改代码的过程中无意中修改了该变量导致报错,减少bug的产生
3.28.3 map和forEach
- 相同点
都是循环遍历数组中的每一项 forEach和map方法里每次执行匿名函数都支持3个参数,参数分别是item(当前每一项)、index(索引值)、arr(原数组),需要用哪个的时候就写哪个 匿名函数中的this都是指向window 只能遍历数组
注意:forEach对于空数组是不会调用回调函数的。
- 不同点
map方法返回一个新的数组,数组中的元素为原始数组调用函数处理后的值。(原数组进行处理之后对应的一个新的数组。) map()方法不会改变原始数组 map()方法不会对空数组进行检测 forEach()方法用于调用数组的每个元素,将元素传给回调函数.(没有return,返回值是undefined)
3.28.4 for…in 和 for…of
- 推荐在循环对象属性的时候,使用 for…in,在遍历数组的时候的时候使用for…of。
- for in遍历的是数组的索引,而for of遍历的是数组元素值。
- for…of 不能循环普通的对象,需要通过和 Object.keys()搭配使用。
- for…in 遍历顺序以数字为先 无法遍历 symbol 属性 可以遍历到公有中可枚举的。
- 从遍历对象的角度来说,for···in会遍历出来的为对象的key,但for···of会直接报错。
3.28.5 Set和Map
Set数据结构
set 类似以数组,主要区别是有自己的方法,并且内部集合不能重复。
- 是数据是唯一的。
const set1 = new Set()
增加元素 使用 add
set2.add(4)
是否含有某个元素 使用 has
console.log(set2.has(2))
查看长度 使用 size
console.log(set2.size)
删除元素 使用 delete
set2.delete(2)
size: 返回Set实例的成员总数。
add(value):添加某个值,返回 Set 结构本身。
delete(value):删除某个值。
clear():清除所有成员,没有返回值。
- 是不重复性。
传入的数组中有重复项,会自动去重
const set2 = new Set([1, 2, '123', 3, 3, '123'])
Set`的不重复性中,要注意`引用数据类型和NaN
两个对象都是不用的指针,所以没法去重
const set1 = new Set([1, {name: '孙志豪'}, 2, {name: '孙志豪'}])
如果是两个对象是同一指针,则能去重
const obj = {name: '我们一样'}
const set2 = new Set([1, obj, 2, obj])
Map数据结构
Map
对比
object最大的好处就是,key不受
类型限制
定义map
const map1 = new Map()
新增键值对 使用 set(key, value)
map1.set(true, 1)
判断map是否含有某个key 使用 has(key)
console.log(map1.has('哈哈'))
获取map中某个key对应的value
console.log(map1.get(true))
删除map中某个键值对 使用 delete(key)
map1.delete('哈哈')
3.28.7 WeakSet和WeakMap
它们对于值的引用都是不计入垃圾回收机制的,所以名字里面才会有一个"Weak",表示这是弱引用。
WeakSet数据结构
Set和WeakSet的区别是set内部建议存放数组,WeakSet内部建议存放引用类型(数组和对象)。
虽然WeakSet内部建议存放对象,但是WeakSet初始化的时候也不能进行初始化赋值,必须使用add赋值。
WeakSet 是 Set的“兄弟”类型,其 API 也是 Set 的子集。WeakSet中的“weak”(弱),描述的是 JavaScript 垃圾回收程序对待“弱集合”中值的方式。
// 弱集合中的值只能是 Object或者继承自 Object 的类型,尝试使用非对象设置值会抛出TypeError。如果想在初始化时填充弱集合,则构造函数可以接收一个可迭代对象,其中需要包含有效的值。可迭代对象中的每个值都会按照迭代顺序插入到新实例中:
const val1 = {id: 1},
val2 = {id: 2},
val3 = {id: 3};
// 使用数组初始化弱集合
const ws1 = new WeakSet([val1, val2, val3]);
alert(ws1.has(val1)); // true
alert(ws1.has(val2)); // true
alert(ws1.has(val3)); // true
// 初始化是全有或全无的操作
// 只要有一个值无效就会抛出错误,导致整个初始化失败
const ws2 = new WeakSet([val1, "BADVAL", val3]);
// TypeError: Invalid value used in WeakSet
typeof ws2;
// ReferenceError: ws2 is not defined
// 原始值可以先包装成对象再用作值
const stringVal = new String("val1");
const ws3 = new WeakSet([stringVal]);
alert(ws3.has(stringVal)); // true
// 初始化之后可以使用add()再添加新值,可以使用 has()查询,还可以使用 delete()删除:
const ws = new WeakSet();
const val1 = {id: 1},
val2 = {id: 2};
alert(ws.has(val1)); // false
ws.add(val1) .add(val2);
alert(ws.has(val1)); // true
alert(ws.has(val2)); // true
ws.delete(val1); // 只删除这一个值
alert(ws.has(val1)); // false
alert(ws.has(val2)); // true
WeakMap数据结构
它是一个 Map 字典,其中的键很弱,也就是说,如果对该键的所有引用都丢失,并且不再有对该值的引用,则可以对该值进行垃圾回收。
基本上,如果你要往对象上添加数据,又不想干扰垃圾回收机制,就可以使用 WeakMap。
基本上,如果你要往对象上添加数据,又不想干扰垃圾回收机制,就可以使用 WeakMap。
const wm = new WeakMap();
const element = document.getElementById('example');
wm.set(element, 'some information');
wm.get(element) // "some information"
/*
DOM 节点对象的引用计数是1,而不是2。
这时,一旦消除对该节点的引用,它占用的内存就会被垃圾回收机制释放。
Weakmap 保存的这个键值对,也会自动消失。
*/
3.28.8 promise
promise是一个对异步操作进行封装并返回其结果的构造函数。
- Promise 是异步编程的一种解决方案,主要用于异步计算,支持链式调用,可以解决回调地狱 的问题,自己身上有all、reject、resolve、race 等方法,原型上有then、catch等方法。
- 可以将异步操作队列化,按照期望的顺序执行,返回符合预期的结果,可以在对象之间传递和操作 promise,帮助我们处理队列。
- promise 有三个状态:pending[待定]初始状态,fulfilled[实现]操作成功,rejected[被否决]操作失败。
- Promise 对象状态改变:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了。
- 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部,但是写了then 和 catch ,会被then的第二个参数 或 catch所捕获。
promise 的then会返回一个新的 promise 对象,能保证 then 方 可以进行链式调用
const PENDING = 'pending' // 等待
const RESOLVED = 'resolved' // 成功
const REJECTED = 'rejected' // 失败
function MyPromise(fn) {
const _this = this
_this.state = PENDING
_this.value = null
_this.resolvedCallbacks = []
_this.rejectedCallbacks = []
function resolve(value) {
if (_this.state === PENDING) {
_this.state = RESOLVED
_this.value = value
_this.resolvedCallbacks.map(cb => cb(_this.value))
}
}
function reject(value) {
if (_this.state === PENDING) {
_this.state = REJECTED
_this.value = value
_this.rejectedCallbacks.map(cb => cb(_this.value))
}
}
try {
fn(resolve, reject)
} catch (e) {
reject(e)
}
}
MyPromise.prototype.then = function (onFulfilled, onRejected) {
const _this = this
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v
onRejected = typeof onRejected === 'function' ? onRejected : r => { throw r }
if (_this.state === PENDING) {
_this.resolvedCallbacks.push(onFulfilled)
_this.rejectedCallbacks.push(onRejected)
}
if (_this.state === RESOLVED) onFulfilled(_this.value)
if (_this.state === REJECTED) onRejected(_this.value)
}
new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve(1)
}, 200)
}).then(value => {
console.log(value);
})
Promise.all哪怕一个请求失败了也能得到其余正确的请求结果的解决方案
Promise.all默认只要有一个错误就直接返回错误。promise.all中任何一个promise 出现错误的时候都会执行reject,导致其它正常返回的数据也无法使用
Promise.all(
[
Promise.reject({ code: 500, msg: "服务异常" }),
Promise.resolve({ code: 200, list: [] }),
Promise.resolve({ code: 200, list: [] })
].map(p => p.catch(e => e))
)
.then(res => {
console.log("res=>", res);
})
.catch(error => {
console.log("error=>", error);
});
res=> [ { code: 500, msg: '服务异常' },
{ code: 200, list: [] },
{ code: 200, list: [] } ]
核心内容是map方法,map的每一项都是promise,catch方法返回值会被promise.reslove()包裹,这样传进promise.all的数据都是resolved状态的。
// 使用Promise.all 其中id为69的商品,返回失败,会导致整个Promise接受到reject状态.
// 所以进行改造, p catch 得到的err 为返回失败抛出的信息, 进行置空
.map(p => p.catch(err => '')))
3.28.9 async、await
async/await相比较Promise 对象then 函数的嵌套,与 Generator 执行的繁琐(需要借助co才能自动执行,否则得手动调用next() ), Async/Await 可以让你轻松写出同步风格的代码同时又拥有异步机制,更加简洁,逻辑更加清晰。
- await 是个运算符,用于组成表达式,await 表达式的运算结果取决于它等的东西,如果是promise则会等待promaise 返回结果,接普通函数直接进行链式调用。
- await 能够获取promise执行的结果 await必须和async一起使用才行,async配合await使用是一个阻塞的异步方法。
- await语句后的Promise对象变成reject状态时,那么整个async函数会中断,后面的程序不会继续执行。
async 函数就是 Generator 函数的语法糖。
// Generator 函数,依次读取两个文件。
var fs = require('fs');
var fs = require('fs');
var readFile = function (fileName){
return new Promise(function (resolve, reject){
fs.readFile(fileName, function(error, data){
if (error) reject(error);
resolve(data);
});
});
};
var gen = function* (){
var f1 = yield readFile('/etc/fstab');
var f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
// 写成 async 函数
var asyncReadFile = async function (){
var f1 = await readFile('/etc/fstab');
var f2 = await readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
async 函数就是将 Generator 函数的星号(*)替换成 async,将 yield 替换成 await。
使用场景
多个await命令的异步操作,如果不存在依赖关系(后面的await不依赖前一个await返回的结果),用Promise.all()让它们同时触发。
function test1 () {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1)
}, 1000)
})
}
function test2 () {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(2)
}, 2000)
})
}
async function exc1 () {
console.log('exc1 start:',Date.now())
let res1 = await test1();
let res2 = await test2(); // 不依赖 res1 的值
console.log('exc1 end:', Date.now())
}
async function exc2 () {
console.log('exc2 start:',Date.now())
let [res1, res2] = await Promise.all([test1(), test2()])
console.log('exc2 end:', Date.now())
}
exc1();
exc2();
// exc1 的两个并列await的写法,比较耗时,只有test1执行完了才会执行test2
3.28.10 generator(生成器)
是ES6提供的一种异步编程解决方案,语法不同于普通函数;简单的把Generator 理解为一个状态机,封装了多个内部状态。执行Generator 函数会返回一个迭代器对象,可以通过调用迭代器next依次遍历Generator函数内部的每一个状态。
function* Generator() {
// 内部使用yield表达式——不会阻止代码向下运行
yield '我是第一个状态'
yield '我是第二个状态'
yield '我是第三个状态'
}
let res = Generator() //返回值 返回的是一个迭代器对象
console.log(res.next()); //next()执行一个状态
console.log(res.next()); //next()执行下一个状态
// console.log(res.next()); //next()执行下一个状态
- Generator 是分段执行的, yield (又得)可暂停,next方法可启动。每次返回的是yield后的表达式结果,这使得Generator函数非常适合将异步任务同步化
- Generator 并不是为异步而设计出来的,它还有其他功能(对象迭代、控制输出、部署Interator`接口…)
- Generator函数返回Iterator对象,因此我们还可以通过for…of进行遍历,原生对象没有遍历接口,通过Generator函数为它加上这个接口,就能使用for…of进行遍历了
promise、Generator、async/await的区别:
- promise和async/await是专门用于处理异步操作的。
- Generator并不是为异步而设计出来的,它还有其他功能(对象迭代、控制输出、部署Interator接口。
- Generator、async需要与promise对象搭配处理异步情况。
- async实质是Generator的语法糖,相当于会自动执行Generator函数。
- async使用上更为简洁,将异步代码以同步的形式进行编写,是处理异步编程的最终方案。
3.28.11 proxy
Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。
可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。
语法
const p = new Proxy(target, handler)
// target:要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
// handler:一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。
示例
当对象中不存在属性名时,默认返回值为 37。下面的代码以此展示了 get handler 的使用场景。
const handler = {
get: function(obj, prop) {
return prop in obj ? obj[prop] : 37;
}
};
const p = new Proxy({}, handler);
p.a = 1;
p.b = undefined;
console.log(p.a, p.b); // 1, undefined
console.log('c' in p, p.c); // false, 37
3.28.12 symbol
- symbol 是es6 加入的,是一个基本数据类型,它代表的是一个独一无二的值。
- symbol 他是一个原始类型的值就,不可以使用 new关键字,symbol不是对象 没有迭代器的接口 不能去添加属性值,他是类似于字符串的一种类型。
- symbol不能用来四则运算,否则会报错,只能用显示的方式转为字符串。
- symbol 参数里的 a 表示一种修饰符 对当前创建的 symbol 的一种修饰,作为区分 ,否则会混淆。
3.28.13 解构赋值
简单来说就是简化信息提取,在编码过程中,我们经常定义许多对象和数组,然后有组织地从中提取相关的信息片段。ES6 中添加了可以简化这种任务的新特性:解构。解构是一种打破数据结构,将其拆分为更小部分的过程。
对象解构
对象结构赋值的语法就是在表达式的左侧使用了对象的字面量方式,将对象中的值赋值到左侧相同的变量上。
let obj = {
name: '姓名',
age: '年龄'
}
let { name, age } = obj;
console.log(name, age); // 姓名 年龄
默认值
当使用解构赋值语句时,如果所指定的本地变量在对象中没有找到同名属性,那么该变量 会被自动赋值为 undefined 。
let obj = {
name: '姓名',
age: '年龄'
};
let { sex, name } = obj;
console.log(sex, name); // undefined "姓名"
赋值给不同的本地变量名
前面所说的都是将取出来的值赋值到本地变量上,有时候我们并不想使用这些变量,而想使用其他的变量名称这种语法也是支持的。它会先取出对象中的值并赋值到新定义的变量上,并且还支持添加默认值,当取不到的时候就使用默认值(如sex给设置默认值为‘男’,取不到值得时候不再显示undefined)。
let obj = {
name: '姓名',
age: '年龄'
};
let { name: localName, age: localAge, sex: localSex = '男'} = obj;
console.log(localName, localAge, localSex); // 姓名 年龄 男
嵌套的对象解构
嵌套对象得解构和单层对象得结构赋值比较类似,需要先取出外层得值,在解构取出内层嵌套得值。
let obj = {
name: {
nameOne: '姓名One',
nameTwo: '姓名Two'
},
age: '年龄',
};
let { name: { nameOne } } = obj;
console.log(nameOne); // 姓名One
数组解构
数组解构有点与对象得解构类似,不过数组得解构不是作用在具体得属性名上,而是作用在值得内部位置上,根据位置进行赋值。
let arr = ['姓名', '年龄'];
let [name, age] = arr;
console.log(name, age); // 姓名 年龄
取出指定位置的值
如果我们只想取第二个或者第三个值,那么只需要把需要取值得前几位进行占位就可以了。
let arr = ['姓名', '年龄'];
let [, age] = arr;
console.log(age); // 年龄
默认值
数组解构中同样支持没有取到值时设置默认值。
let arr = ['姓名', '年龄'];
let [, age, sex = '性别'] = arr;
console.log(age, sex); // 年龄 性别
嵌套得解构
数组嵌套得解构和单个解构类似,只需要在解构得位置插入[]就可以。
let arr = ['姓名', ['年龄']];
let [, [age], sex = '性别'] = arr;
console.log(age, sex); // 年龄 性别
数组解构的剩余项
前面得值进行解构后,可以使用 … 语法来将剩余的项目赋值给一个指定的变量,这个变量为没有解构得剩余值组成得数组。
注意:剩余解构变量只能放在最后面。
let arr = ['姓名', '年龄', '性别'];
let [name, ...data] = arr;
console.log(name, data); // 姓名 ["年龄", "性别"]
利用数组解构交换两个变量得值
在es5得时候交换两个变量需要声明第三个变量,作用中间层来实现,使用数组解构赋值来实现就不需要声明其他得变量了。
let a = 1,
b = 2;
[b, a] = [a, b];
console.log(a, b); // 2 1
混合解构
在我们平时开发中,接口返回得数据经常是对象和数组进行嵌套返回得,解构赋值同样也可以进行混合嵌套解构。
let obj = {
name: {
nameOne: '姓名One',
nameTwo: '姓名Two'
},
age: ['年龄1', '年龄2']
};
let {
name: { nameOne },
age: [age1]
} = obj;
console.log(nameOne, age1); // 姓名One 年龄1
数组结构与对象字符串结构的区别
- 对象的解构与数组类似,但有所不同。数组的元素是按次序排列的,变量的取值由它的位置决定。
- 而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。字符串也是可以解构赋值的。字符串被转换成了一个类似数组的对象。
3.28.15 iterator(遍历器)
JavaScript 原有的表示“集合”的数据结构,主要是数组(Array)和对象(Object),ES6 又添加了Map和Set。这样就有了四种数据集合,用户还可以组合使用它们,定义自己的数据结构,比如数组的成员是Map,Map的成员是对象。这样就需要一种统一的接口机制,来处理所有不同的数据结构。遍历器(Iterator)就是这样一种机制。
Iterator 是 Symbol 基本数据类型的一个属性,是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator,就可以完成遍历操作(即依次处理该数据结构的所有成员)。
function createIterator(items) {
var i = 0;
return {
next: function() {
var done = (i >= items.length);
var value = !done ? items[i++] : undefined;
return {
done: done,
value: value
};
}
};
}
var iterator = createIterator([1, 2, 3]);
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 2, done: false }"
console.log(iterator.next()); // "{ value: 3, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
// 之后的所有调用
console.log(iterator.next()); // "{ value: undefined, done: true }"
作用
- 为各种数据结构,提供一个统一的、简便的访问接口。
- 使得数据结构的成员能够按某种次序排列。
- ES6 创造了一种新的遍历命令for…of循环,Iterator 接口主要供for…of使用。
过程
- 创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
- 第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。
- 第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。
- 不断调用指针对象的next方法,直到它指向数据结构的结束位置。
每一次调用next方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含value和done两个属性的对象。其中,value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束。
3.29 模块规范
JavaScript 模块化规范,包括原生规范 ES6 模块、Node.js 采用的 CommonJS,以及开源社区早期为浏览器提供的规范 AMD,具有 CommonJS 特性和 AMD 特性的 CMD,让 CommonJS 和 AMD 模块跨端运行的 UMD。
CommonJS 规范
- 每一个文件都是一个模块,每一个模块都有一个独立的作用域,文件内的变量,函数都是私有的,其他文件不可使用(除非赋值到 global上)。
- 每个模块内部,module变量代表当前模块。
- 每个文件对外的接口是 module.exports 属性。
- require用于引用其他模块,实际获得的是其他模块的module.exports这个属性。
它通过 require 来引入模块,通过 module.exports 定义模块的输出接口。这种模块加载方案是服务器端的解决方案,它是以同步的方式来引入模块的,因为在服务端文件都存储在本地磁盘,所以读取非常快,所以以同步的方式加载没有问题。但如果是在浏览器端,由于模块的加载是使用网络请求,因此使用异步加载的方式更加合适。
//test.js
const name = 'pjy';
const age = 19;
function sayHello(name) {
console.log("hello " + name);
}
module.exports = {
name,
age,
sayHello
}
//main.js
const a = require('./test');
console.log(a.age);//19
console.log(a.sayHello);//[Function: sayHello]
特性
CommonJS ,它采用的是值拷贝和动态声明。值拷贝和值引用相反,一旦输出一个值,模块内部的变化就影响不到这个值了,可以简单地理解为变量浅拷贝。
动态声明,就是消除了静态声明的限制,可以“自由”地在表达式语句中引用模块。
require文件查找规则
比如导入格式为:require(X)
-
X是一个Node核心模块,比如path、http直接返回核心模块,并且停止查找。
-
X是以./ 或…/ 或/(根目录)开头。
-
将X当做一个文件在对应的目录下查找:
-
如果有后缀名,按照后缀名的格式查找对应的文件。
-
如果没有后缀名,会按照如下顺序:
- 直接查找文件X。
- 查找X.js文件。
- 查找X.json文件。
- 查找X.node文件。
-
-
没有找到对应的文件,将X作为一个目录查找目录下面的index文件:
- 查找X/index.js文件。
- 查找X/index.json文件。
- 查找X/index.node文件。
-
如果没有找到,那么报错:not found
-
缺点
- 只有等到对应的模块加载完毕,当前模块中的内容才能被运行。
- 浏览器加载js文件需要先从服务器将文件下载下来,之后再加载运行。
- 那么采用同步的就意味着后续的js代码都无法正常运行,即使是一些简单的DOM操作。
AMD
AMD主要是应用于浏览器的一种模块化规范,它采用的是异步加载模块。
异步加载,就是指同时并发加载所依赖的模块,当所有依赖模块都加载完成之后,再执行当前模块的回调函数。这种加载方式和浏览器环境的性能需求刚好吻合。
AMD 实现的比较常用的库是 require.js 和 curl.js。
这种方案采用异步加载的方式来加载模块,模块的加载不影响后面语句的执行,所有依赖这个模块的语句都定义在一个回调函数里,等到加载完成后再执行回调函数。require.js 实现了 AMD 规范。
//foo.js
define(function() {
const name = "pjy";
const age = 19;
function sum(num1,num2) {
return num1 + num2
}
return {
name,
age,
sum
}
})
//main.js
require.config({
baseUrl: './src',
paths: {
foo: "./foo",
bar: "./bar"
}
})
require(["foo","bar"],function(foo) {
console.log("main:",foo);
})
语法
define(id?, dependencies?, factory);
- 第 1 个参数 id 为模块的名称,该参数是可选的。如果没有提供该参数,模块的名字应该默认为模块加载器请求的指定脚本的名字;如果提供了该参数,模块名必须是“顶级”的和绝对的(不允许相对名字)。
- 第 2 个参数 dependencies 是个数组,它定义了所依赖的模块。依赖模块必须根据模块的工厂函数优先级执行,并且执行的结果应该按照依赖数组中的位置顺序以参数的形式传入(定义中模块的)工厂函数中。
- 第 3 个参数 factory 为模块初始化要执行的函数或对象。如果是函数,那么该函数是单例模式,只会被执行一次;如果是对象,此对象应该为模块的输出值。
CMD
这种方案和 AMD 方案都是为了解决异步模块加载的问题,sea.js 实现了 CMD 规范。它和require.js的区别在于模块定义时对依赖的处理不同和对依赖模块的执行时机的处理不同。
语法
CMD 定义模块也是通过一个全局函数 define 来实现的,但只有一个参数,该参数既可以是函数也可以是对象。
define(factory);
如果这个参数是对象,那么模块导出的就是对象;如果这个参数为函数,那么这个函数会被传入 3 个参数 require 、 exports 和 module。
//foo.js
define(function(require, exports, module) {
const name = "pjy";
const age = 19;
function sum(num1,num2) {
return num1 + num2
}
// exports.name = name;
// exports.age = age
module.exports = {
name,
age
}
})
//main.js
define(function(require, exports, module) {
const foo = require("./foo");
console.log("main:", foo);
});
-
第 1 个参数 require 是一个函数,通过调用它可以引用其他模块,也可以调用 require.async 函数来异步调用模块。
-
第 2 个参数 exports 是一个对象,当定义模块的时候,需要通过向参数 exports 添加属性来导出模块 API。
-
第 3 个参数 module 是一个对象,它包含 3 个属性:
- uri:模块完整的 URI 路径。
- dependencies:模块的依赖。
- exports:模块需要被导出的 API,作用同第二个参数 exports。
ES6 模块
- 使用了import和export关键字。
- 采用编译期的静态分析,并且也加入了动态引用的方式。
- export负责将模块内的内容导出,import负责从其他模块导入内容。
采用严格模式:use strict:
- 虽然大部分主流浏览器支持 ES6 模块,但是和引入普通 JS 的方式略有不同,需要在对应 script 标签中将属性 type 值设置为“module”才能被正确地解析为 ES6 模块。
<script src="./main.js" type="module"></script>
- 在 Node.js 下使用 ES6 模块则需要将文件名后缀改为“.mjs”,用来和 Node.js 默认使用的 CommonJS 规范模块作区分。
ES6 模块对于引用声明有严格的要求,首先必须在文件的首部,不允许使用变量或表达式,不允许被嵌入到其他语句中。所以下面 3 种引用模块方式都会报错。
// 必须首部声明
let a = 1
import { app } from './app';
// 不允许使用变量或表达式
import { 'a' + 'p' + 'p' } from './app';
// 不允许被嵌入语句逻辑
if (moduleName === 'app') {
import { init } from './app';
} else {
import { init } from './bpp';
}
exports(导出)
-
在语句声明的前面直接加上export关键字
export const name = "pjy"; export const age = 19;
-
将所有需要导出的标识符,放到export后面的{}中
//声明和导出分开 const name = "pjy"; const age = 19; function foo() { console.log("foo function"); } export { name, age, foo }
注意:这里的{}里面不是ES6的对象字面量的增强写法,{}也不是表示一个对象的;
所以: export {name: name},是错误的写法 -
导出时给标识符起一个别名
const name = "pjy"; const age = 19; function foo() { console.log("foo function"); } export { name as fName, age as fAge, foo as fFoo } //此时导入时要用别名导入 import {fName,fAge} from "./foo.js"
import(导入)
-
import {标识符列表} from ‘模块’。
import {name,age} from "./foo.js"
注意:这里的{}也不是一个对象,里面只是存放导入的标识符列表内容
-
导入时给标识符起别名。
import {name as fName,age as fAge} from "./foo.js"
-
通过* 将模块功能放到一个模块功能对象(a module object)上。
import * as foo from "./foo.js" //在使用时 console.log(foo.name); console.log(foo.age);
export和import结合使用
这里是在当前js文件中导入math.js 和 format.js,然后统一将导入的模块以当前文件的形式导出
import {add, sub} from './math.js'
import {timeFormat, priceFormat} from './format.js'
export {
add,
sub,
timeFormat,
priceFormat
}
default
在一个模块中,只能有一个默认导出(default export)。
- 默认导出export时可以不需要指定名字。
- 在导入时不需要使用{},并且可以自己来指定名字。
//foo.js
const foo = "foo value";
export default foo;
//main.js
import pjy from "./foo.js"
console.log(pjy);//foo value
UMD(AMD和CommonJS的糅合)
UMD先判断是否支持Node.js的模块(exports)是否存在,存在则使用Node.js模块模式。
在判断是否支持AMD(define是否存在),存在则使用AMD方式加载模块。
(function (window, factory) {
if (typeof exports === 'object') {
module.exports = factory();
} else if (typeof define === 'function' && define.amd) {
define(factory);
} else {
window.eventUtil = factory();
}
})(this, function () {
//module ...
});
4. TS
待更新中…
二、框架
1. Vue
vue是一套构建于用户界面的自底向上方 增量开发 渐进式基于MVVM的框架核心思想是数据驱动,组件化。数据驱动:视图内容根据数据的改变而改变组件化:增加代码复用性,可维护性,可测试性,提高开发效率,方便重复使用,体现了高内聚,低耦合。
1.1 优点
- 轻量级:只关注view视图层,是一个构建数据的视图结合,大小只有几十kb。
- 双向数据绑定:保留了的angular的特点,在data()中定义的变量利用插值表达式{{}}在模板中使用,一旦变量改变模板也随之改变。
- 组件化:保留了react的优点,实现了组件之前的封装和重用也以便于后期维护和扩展
- 虚拟DOM:原生的DOM比较庞大属性也很多所以操作起来比较耗费性能,所以vue引入了VNode来记录页面的变化并映射DOM,最后再反映到页面上。
1.2 MVVM、MVC、MVP模型
-
mvvm:由Model、View、ViewModel三部分组成。Model代表数据模型;View代表UI组件负责将数据模型转化成页面展现出;ViewModel通过双向数据绑定把View层和Model层连接起来。因此只需关注业务逻辑也不需要手动操作DOM。
-
mvc:由Model、View、Controller三部分组成。Model代表数据模型;View代表模型包含的数据的可视化;Controller控制器作用于模型和视图上。它控制数据流向模型对象,并在数据变化时更新视图。比如python的Django就是典型的mvc模型。
-
mvp:由Model、View、Presenter三部分组成。Model代表数据模型;View代表数据转化的网页、Presenter的功能与controller相同,也是负责连接view与model,但它可以俩俩进行双向通信的
1.3 数据双向绑定
vue采用数据劫持结合发布-订阅者模式的方式,通过Object.defineProperty()来给各个属性添加setter、getter并劫持监听,在数据变动时发布消息给订阅者,触发相应的监听回调。
1、数据监听器Observer给data中的数据进行递归遍历,包括子属性对象的属性都加上setter和getter进行监听,若有变动就拿到最新的值通知订阅者。
2、指令解析器Compile对模板中每个元素节点的指令进行扫描和解析,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者一旦数据有变动收到通知更新视图。
3、订阅者Wathcer是Observer和Compile之间通信的桥梁,能够订阅并收到每个属性变动的通知。在自身实例化时往属性订阅器(dep)里添加自己,且必须有一个update()方法待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调,则功成身退。
4、MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化 -> 视图更新。
<div id="app">
<h2>姓名</h2>
<p>{{name}}</p>
<h2>年龄</h2>
<p>{{age}}</p>
</div>
// 当初始的 HTML 文档被完全加载和解析完成之后,DOMContentLoaded 事件被触发,
// 而无需等待样式表、图像和子框架的完全加载。
document.addEventListener("DOMContentLoaded", () => {
const options = {
el: '#app',
data: {
name: '初始化中...',
age: 18
}
}
const vm = new Vue(options)
setTimeout(() => {
options.data.name = '香风智乃'
}, 1000);
}, false)
class Vue {
constructor(opt) {
this.opt = opt
this.observe(opt.data)
const root = document.querySelector(opt.el)
this.compile(root)
}
// 为响应式对象data里的每一个key绑定观察者对象
observe(data) {
Object.keys(data).forEach(key => {
const obv = new Observer()
data['_' + key] = data[key]
Object.defineProperty(data, key, {
get() {
Observer.target && obv.addSubNode(Observer.target)
return data['_' + key]
},
set(newVal) {
obv.update(newVal)
data['_' + key] = newVal
}
})
})
console.log('data', data);
}
// 初始化时遍历DOM,收集每一个key变化时随之调整位置,以观察者方法存起来
compile(node) {
[].forEach.call(node.childNodes, child => {
if (!child.firstElementChild && /\{\{(.*)\}\}/.test(child.innerHTML)) {
let key = RegExp.$1.trim()
child.innerHTML = child.innerHTML.replace(
new RegExp(`\\{\\{\\s*${key}\\s*\\}\\}`, 'gm'),
this.opt.data[key]
)
Observer.target = child
this.opt.data[key]
Observer.target = null
} else if (child.firstElementChild) {
this.compile(child)
}
})
}
}
// 观察者
class Observer {
constructor() {
this.subNode = []
}
addSubNode(node) {
this.subNode.push(node)
}
update(newVal) {
this.subNode.forEach(node => {
node.innerHTML = newVal
})
}
}
1.4 响应式原理
在数据发生改变时视图会重新渲染匹配更新为最新的值。Object.defineProerty为data对象中的每一个属性,设置get和set方法,每一个被声明的属性都会有一个专属的依赖收集器subs,当页面使用到某个属性时,触发Object.defineProerty的get函数,页面的watcher就会被放到属性依赖收集器subs中在数据变化时通知更新;当数据改变时,触发Object.defineProperty 的set函数,数据会遍历自己的 依赖收集器 subs,逐个通知 watcher,视图开始更新。
Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
Object.defineProperty(obj, prop, descriptor) —— obj:要定义属性的对象;prop:要定义或修改的属性的名称或 Symbol ;descriptor:要定义或修改的属性描述符。
1.5 生命周期
- beforeCreate(创建前):vue实例挂载的元素$el和数据对象data都是undefined还未初始化,在此阶段可以使用loading事件
- created(创建后):vue实例的数据对象data有了,$el还没有更新,在此阶段可以发ajax请求。
- bedoreMount(渲染前):vue实例的$el和data都初始化了,但还是虚拟dom节点。
- mounted(渲染后):vue实例挂载完成,真实dom成功渲染到模板中,在此阶段可以使用路有钩子。
- beforeUpdate(更新后):在data更新时触发。
- updated(更新后):在data更新时触发,在此阶段可以使用watch进行观测。
- beforeDestroy(销毁前):组件将要销毁时触发。
- destroyed(销毁后):组件销毁时触发,vue实例解除了事件监听以及和dom的绑定(无响应了),但DOM节点依旧存在。
父子组件生命周期顺序
- 创建、挂载阶段:父beforeCreate→ 父created→ 父beforeMounte→ 子beforCreate→ 子created→ 子beforeMount→ 父mounted。
- 组件更新:父beforeUpdate→ 子beforeUpdate→ 子updated→ 父updated。
- 组件销毁:父beforeDestroy→ 子beforeDestroy→ 子destroyed→ 父destroyed。
1.6 内置指令
- v-text:更新元素的 textContent。如果要更新部分的 textContent,需要使用 {{ Mustache }} 插值。
- v-html:更新元素的 innerHTML。注意:内容按普通 HTML 插入 - 不会作为 Vue 模板进行编译。如果试图使用 v-html 组合模板,可以重新考虑是否通过使用组件来替代。
- v-show:根据表达式之真假值,切换元素的 display CSS property。
- v-if:根据表达式的值的 truthiness 来有条件地渲染元素。在切换时元素及它的数据绑定 / 组件被销毁并重建。如果元素是 ,将提出它的内容作为条件块。
当和 v-if 一起使用时,v-for 的优先级比 v-if 更高
- v-for: 遍历数组,渲染数据
- v-on:绑定事件监听器。事件类型由参数指定。表达式可以是一个方法的名字或一个内联语句,如果没有修饰符也可以省略。
修饰符:
- .stop - 调用 event.stopPropagation()。
- prevent - 调用 event.preventDefault()。
- .capture - 添加事件侦听器时使用 capture 模式。
- .self - 只当事件是从侦听器绑定的元素本身触发时才触发回调。
- .{keyCode | keyAlias} - 只当事件是从特定键触发时才触发回调。
- .native - 监听组件根元素的原生事件。
- .once - 只触发一次回调。
- .left - (2.2.0) 只当点击鼠标左键时触发。
- .right - (2.2.0) 只当点击鼠标右键时触发。
- .middle - (2.2.0) 只当点击鼠标中键时触发。
- .passive - (2.3.0) 以 { passive: true } 模式添加侦听器
- v-bind:动态地绑定一个或多个 attribute,或一个组件 prop 到表达式。在绑定 class 或 style attribute 时,支持其它类型的值,如数组或对象
修饰符:
- .prop - 作为一个 DOM property 绑定而不是作为 attribute 绑定。
- .camel - (2.1.0+) 将 kebab-case attribute 名转换为 camelCase。
- .sync (2.3.0+) 语法糖,会扩展成一个更新父组件绑定值的 v-on 侦听器。
- v-model:在表单控件或者组件上创建双向绑定。
修饰符:
- .lazy - 取代 input 监听 change 事件。
- .number - 输入字符串转为有效的数字。
- .trim - 输入首尾空格过滤’
- v-slot:提供具名插槽或需要接收 prop 的插槽。
- v-pre:跳过这个元素和它的子元素的编译过程。可以用来显示原始 Mustache 标签。跳过大量没有指令的节点会加快编译。
- v-cloak:这个指令保持在元素上直到关联实例结束编译。和 CSS 规则如 [v-cloak] { display: none } 一起用时,这个指令可以隐藏未编译的 Mustache 标签直到实例准备完毕。
- v-once:只渲染元素和组件一次。随后的重新渲染,元素/组件及其所有的子节点将被视为静态内容并跳过。这可以用于优化更新性能。
1. 7 自定义指令
自定义指令是用来操作DOM的。尽管Vue推崇数据驱动视图的理念,但并非所有情况都适合数据驱动。自定义指令就是一种有效的补充和扩展,不仅可用于定义任何的DOM操作,并且是可复用的。
1.7.1 用法
- 全局指令:通过 Vue.directive() 函数注册一个全局的指令。
Vue.directive("focus", {
inserted: function(el){
el.focus();
}
})
- 局部指令:通过在Vue实例中添加 directives 对象数据注册局部自定义指令。
directives: {
focus: {
inserted: function(el){
el.focus();
}
}
}
1.7.2 钩子函数
- bind:只调用一次,指令第一次绑定到元素时调用。可以进行一次性的初始化设置。
- inserted:被绑定元素插入父节点时调用。
- undate:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 。
- componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。unbind:只调用一次,指令与元素解绑时调用。
1.7.3 钩子的参数
- el:指令所绑定的元素,可以用来直接操作 DOM。
- binding:一个对象,包含以下 property:
- name:指令名,不包括 v- 前缀。
- value:指令的绑定值。
- oldValue:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子 中可用。无论值是否改变都可用。
- expression:字符串形式的指令表达式。
- arg:传给指令的参数。
- modifiers:一个包含修饰符的对象。
- vnode:Vue 编译生成的虚拟节点。
- oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。
1.7.4 案例
在图片未完成加载前,用随机的背景色占位,图片加载完成后才直接渲染出来
<div id="app" v-image = "item " v-for="item in imageList"></div>
<script>
Vue.directive("image", {
inserted: function(el,binding){
var color = Math.floor(Math,random()*1000000)
el.style.backgroundColor = "#" + color
var img = new Image()
img.src = binding.vaule
img.onload = function(){
el.style.backgroundImage = “url(” + binding.vaule + ")"
}
}
})
new Vue({
el: "#app",
data: {
imageList: [
{
url: "http://consumer-img.huawei.com/content/dam/huawei-cbg-site/greate-china/cn/mkt/homepage/section4/home-s4-p10-plus.jpg"
},
{
url: "http://consumer-img.huawei.com/content/dam/huawei-cbg-site/greate-china/cn/mkt/homepage/section4/home-s4-watch2-pro-banner.jpg"
},
{
url: "http://consumer-img.huawei.com/content/dam/huawei-cbg-site/en/mkt/homepage/section4/home-s4-matebook-x.jpg"
}
]
}
})
</script>
1. 8 组件通信
- props和$emit
父组件向子组件传递数据是通过prop传递的,子组件传递数据给父组件是通过$emit触发事件来做到的。
Vue.component('child',{
data(){
return {
mymessage:this.message
}
},
template:`
<div>
<input type="text" v-model="mymessage" @input="passData(mymessage)"> </div>
`,
props:['message'],//设置props属性值,得到父组件传递过来的数据
methods:{
passData(val){
//触发父组件中的事件,向父组件传值
this.$emit('getChildData',val)
}
}
})
Vue.component('parent',{
template:`
<div>
<p>this is parent compoent!</p>
<child :message="message" v-on:getChildData="getChildData"></child>
</div>
`,
data(){
return {
message:'hello'
}
},
methods:{
//执行子组件触发的事件
getChildData(val){
console.log(val)
}
}
})
prop 只可以从上一级组件传递到下一级组件(父子组件),即所谓的单向数据流。而且 prop 只读,不可被修改,所有修改都会失效并警告。
- $attrs和$listeners
多级组件嵌套需要传递数据时,通常使用的方法是通过vuex。但如果仅仅是传递数据,而不做中间处理,使用 vuex 处理,未免有点大材小用。为此Vue2.4 版本提供了另一种方法----$attrs/$listeners。- $attrs:包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind=“$attrs” 传入内部组件。通常配合 inheritAttrs 选项一起使用。
- $listeners:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on=“$listeners” 传入内部组件。
Vue.component('C',{
template:`
<div>
<input type="text" v-model="$attrs.messagec" @input="passCData($attrs.messagec)"> </div>
`,
methods:{
passCData(val){
//触发父组件A中的事件
this.$emit('getCData',val)
}
}
})
Vue.component('B',{
data(){
return {
mymessage:this.message
}
},
template:`
<div>
<input type="text" v-model="mymessage" @input="passData(mymessage)">
<!-- C组件中能直接触发getCData的原因在于 B组件调用C组件时 使用 v-on 绑定了$listeners 属性 -->
<!-- 通过v-bind 绑定$attrs属性,C组件可以直接获取到A组件中传递下来的props(除了B组件中props声明的) -->
<C v-bind="$attrs" v-on="$listeners"></C>
</div>
`,
props:['message'],//得到父组件传递过来的数据
methods:{
passData(val){
//触发父组件中的事件
this.$emit('getChildData',val)
}
}
})
Vue.component('A',{
template:`
<div>
<p>this is parent compoent!</p>
<B :messagec="messagec" :message="message" v-on:getCData="getCData" v-on:getChildData="getChildData(message)"></B>
</div>
`,
data(){
return {
message:'hello',
messagec:'hello c' //传递给c组件的数据
}
},
methods:{
getChildData(val){
console.log('这是来自B组件的数据')
},
//执行C子组件触发的事件
getCData(val){
console.log("这是来自C组件的数据:"+val)
}
}
})
$attrs与$listeners 是两个对象,$attrs 里存放的是父组件中绑定的非 Props 属性,$listeners里存放的是父组件中绑定的非原生事件。
- eventBus
又称为事件总线,在vue中可以使用它来作为沟通桥梁的概念, 就像是所有组件共用相同的事件中心,可以向该中心注册发送事件或接收事件, 所以组件都可以通知其他组件。
Vue.component('brother1',{
data(){
return {
mymessage:'hello brother1'
}
},
template:`
<div>
<p>this is brother1 compoent!</p>
<input type="text" v-model="mymessage" @input="passData(mymessage)">
</div>
`,
methods:{
passData(val){
//触发全局事件globalEvent
bus.$emit('globalEvent',val)
}
}
})
Vue.component('brother2',{
template:`
<div>
<p>this is brother2 compoent!</p>
<p>brother1传递过来的数据:{{brothermessage}}</p>
</div>
`,
data(){
return {
mymessage:'hello brother2',
brothermessage:''
}
},
mounted(){
//绑定全局事件globalEvent
bus.$on('globalEvent',(val)=>{
this.brothermessage=val;
})
}
})
//中央事件总线
var bus=new Vue();
var app=new Vue({
el:'#app',
template:`
<div>
<brother1></brother1>
<brother2></brother2>
</div>
`
})
eventBus也有不方便之处, 当项目较大,就容易造成难以维护的灾难。
- provide和inject
父组件中通过provide来提供变量,然后在子组件中通过inject来注入变量,不论子组件有多深,只要调用了inject那么就可以注入provider中的数据,而不是局限于只能从当前父组件的prop属性来获取数据,只要在父组件的生命周期内,子组件都可以调用。
Vue.component('child',{
inject:['for'],//得到父组件传递过来的数据
data(){
return {
mymessage:this.for
}
},
template:`
<div>
<input type="tet" v-model="mymessage">
</div> `
})
Vue.component('parent',{
template:`
<div>
<p>this is parent compoent!</p>
<child></child>
</div>
`,
provide:{
for:'test'
},
data(){
return {
message:'hello'
}
}
})
provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的。实现响应式的方法如下:
- provide祖先组件的实例,然后在子孙组件中注入依赖,这样就可以在子孙组件中直接修改祖先组件的实例的属性,不过这种方法有个缺点就是这个实例上挂载很多没有必要的东西比如props,methods
- 使用2.6最新API Vue.observable 优化响应式 provide(推荐)
Vue.component('child',{
inject:['for'],//得到父组件传递过来的数据
data(){
return {
mymessage:this.for
}
},
template:`
<div>
<input type="tet" v-model="mymessage">
</div> `
})
Vue.component('parent',{
template:`
<div>
<p>this is parent compoent!</p>
<button @click="() => changeMessage()">message</button>
<child></child>
</div>
`,
provide() {
return {
for: this //方法一:提供祖先组件的实例
}
},
// 方法二:使用2.6最新API Vue.observable 优化响应式 provide
// provide() {
// this.for= Vue.observable({
// msg: "hello"
// });
// return {
// for: this.for
// };
// },
data(){
return {
message:'hello'
}
},
methods: {
changeMessage(msg) {
if (msg) {
this.message= msg;
} else {
this.message= this.message=== "hello" ? "red" : "blue";
}
}
}
})
- $parent / $children与 ref / refs
- ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例,可以通过实例直接调用组件的方法或访问数据。
- -$parent / $children:访问父 / 子实例。如在#app上拿$parent得到的是new Vue()的实例,在这实例上再拿$parent得到的是undefined,而在最底层的子组件拿$children是个空数组。$children 的值是数组,而$parent是个对象。
/***********ref*****************/
// component-a 子组件
export default {
data () {
return {
title: 'Vue.js'
}
},
methods: {
sayHello () {
window.alert('Hello');
}
}
}
// 父组件
<template>
<component-a ref="comA"></component-a>
</template>
<script>
export default {
mounted () {
const comA = this.$refs.comA;
console.log(comA.title); // Vue.js
comA.sayHello(); // 弹窗
}
}
</script>
/***********$parent / $children*****************/
// 父组件中
<template>
<div class="hello_world">
<div>{{msg}}</div>
<com-a></com-a>
<button @click="changeA">点击改变子组件值</button>
</div>
</template>
<script>
import ComA from './test/comA.vue'
export default {
name: 'HelloWorld',
components: { ComA },
data() {
return {
msg: 'Welcome'
}
},
methods: {
changeA() {
// 获取到子组件A
this.$children[0].messageA = 'this is new value'
}
}
}
// 子组件中
<template>
<div class="com_a">
<span>{{messageA}}</span>
<p>获取父组件的值为: {{parentVal}}</p>
</div>
</template>
<script>
export default {
data() {
return {
messageA: 'this is old'
}
},
computed:{
parentVal(){
return this.$parent.msg;
}
}
}
上面两种方式用于父子组件之间的通信, 而使用props进行父子组件通信更加普遍; 二者皆不能用于非父子组件之间的通信。
-
vuex
vuex 解决了多个视图依赖于同一状态和来自不同视图的行为需要变更同一状态的问题,将开发者的精力聚焦于数据的更新而不是数据在组件之间的传递上。
- dispatch:操作行为触发方法,是唯一能执行action的方法。
- actions:操作行为处理模块,由组件中的$store.dispatch(‘action 名称’, data1)来触发。然后由commit()来触发mutation的调用 , 间接更新 state。
- commit:状态改变提交操作方法。对mutation进行提交,是唯一能执行mutation的方法。
- mutations:状态改变操作方法,由actions中的commit(‘mutation 名称’)来触发。是Vuex修改state的唯一推荐方法。
- state:页面状态管理容器对象。集中存储Vue components中data对象的零散数据,全局唯一,以进行统一的状态管理。
- getters:如vue中的计算属性一样,基于state数据的二次包装,常用于数据的筛选和多个数据的相关性计算
-
v-model
父组件通过v-model传递值给子组件时,会自动传递一个value的prop属性,在子组件中通过this.$emit(“input”,val)自动修改v-model绑定的值
Vue.component('child',{
props:{
value:String, //v-model会自动传递一个字段为value的prop属性
},
data(){
return {
mymessage:this.value
}
},
methods:{
changeValue(){
this.$emit('input',this.mymessage);//通过如此调用可以改变父组件上v-model绑定的值
}
},
template:`
<div>
<input type="text" v-model="mymessage" @change="changeValue">
</div> `
})
Vue.component('parent',{
template:`
<div>
<p>this is parent compoent!</p>
<p>{{message}}</p>
<child v-model="message"></child>
</div>
`,
data(){
return {
message:'hello'
}
}
})
var app=new Vue({
el:'#app',
template:`
<div>
<parent></parent>
</div>
`
})
1.9 模板编译原理
1.10 虚拟dom
1.11 diff算法
1.12 SSR
1.13 computed、watch 、methods
1.14 生命周期
1.15 vuex
1.16 路由
1.17 过滤器
1.18 mixin
1.19 nextTick
1.20 keep-alive
1.21 slot
1.22 设计模式
1.22 性能优化
2. React
待更新中…
3. Uni-app
待更新中…
总结
程序员的生活就是活到老,学到老。