在业余时间中,无聊的我在网上看了一些面试题和技术博文突然发现其中还有一些很不错的知识点就想收藏起来, 但是越到后面发现东西太多太杂再回头看的时候不好梳理和记忆,于是乎我决定在看的同时把它们记录起来汇总成一个知识大全,以便于之后方便与查找和理解记忆为此巩固自己的基本功。
待更新中…
层叠样式表 (Cascading Style Sheets,缩写为 CSS),是一种 样式表 语言,用来描述 HTML 或 XML(包括如 SVG、MathML、XHTML 之类的 XML 分支语言)文档的呈现。CSS 描述了在屏幕、纸质、音频等其它媒体上的元素应该如何被渲染的问题。
CSS 是开放网络的核心语言之一,由 W3C 规范 实现跨浏览器的标准化。CSS 节省了大量的工作。样式可以通过定义保存在外部.css 文件中,同时控制多个网页的布局,这意味着开发者不必经历在所有网页上编辑布局的麻烦。CSS 被分为不同等级:CSS1 现已废弃,CSS2.1 是推荐标准, CSS3 分成多个小模块且正在标准化中。
在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等等
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>
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。
官话:当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定时间到来之前,又触发了事件,就重新开始延时。也就是说当一个用户一直触发这个函数,且每次触发函数的间隔小于既定时间,那么防抖的情况下只会执行一次。
白话:在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);
}
}
官话:固定周期内,只执行一次动作,若有新事件触发,不执行。周期结束后,又有事件触发,开始新的周期。
白话:在按钮中,当用户连续不停的点击触发事件之后只会在规定时间内触发最行的回调
节流: <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);
}
}
}
拷贝基本数据类型时,不受任何影响,当拷贝引用类型时,源对象也会被修改且浅拷贝只拷贝已存在对象的对象属性的引用,其余非对象属性是占用新的内存空间,并非与原对象共享。
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}]
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() 拷贝的是属性值。假如源对象的属性值是一个指向对象的引用,它也只拷贝那个引用值。
用扩展运算符对数组或者对象进行拷贝时,只能扩展和深拷贝第一层的值,对于第二层极其以后的值,扩展运算符将不能对其进行打散扩展,也不能对其进行深拷贝,即拷贝后和拷贝前第二层中的对象或者数组仍然引用的是同一个地址,其中一方改变,另一方也跟着改变。
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}}
深拷贝就是把一个对象,从内存中完整的拷贝出来,从堆内存中开辟了新区域,用来存新对象,并且修改新对象不会影响原对象
可以深拷贝的数组和对象,但是不能拷贝函数,可以进行对象或者数组的嵌套拷贝
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
}
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 是一个一致性、模块化、高性能的 JavaScript 实用工具库
const objects = [{ 'a': 1 }, { 'b': 2 }];
let deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]); // => false
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
}
ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。Set 本身是一个构造函数,用来生成 Set 数据结构。
let arr_2 = [...new Set(arr)]
console.log(arr_2); // [1, 2, 35, '香风智乃', '雏鹤爱']
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);
利用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
}
先利用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
}
利用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
}
按照后端返回的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()
第一个参数是目标对象: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
}
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/'
)
js的数据类型分为两种:基本数据类型(原始类型)、引用数据类型(对象类型),基本类型数据存放在栈中 引用数据类型存放在堆中
Null:空值,undefined:未定义,Boolean:布尔值,Number:数字,String:字符串,Symbol:表示独一无二的值 、BigInt :表示任意大的整数。
Object、Array、 function、Date、RegExp。 JavaScript不支持创建任何自定义类型的数据,也就是说JavaScript中所有值的类型都是上面8中之一。
基本数据类型:直接存储在栈内存中,占据空间小,大小固定是属于被频繁使用的数据。
引用数据类型:将指针存储在栈中,值存储在堆中。当对象值赋值给另一个变量时,赋值的是对象的指针,指向同一块内存地址。
作用域是定义变量的区域,它有一套访问变量的规则,这套规则来管理浏览器引擎如何在当前作用域以及嵌套的作用域中根据变量(标识符)进行变量查找。函数内部局部作用域(函数作用域),函数外面全局作用域。
全局作用域就是Js中最外层的作用域,在哪里都可以访问。函数作用域是js通过函数创建的一个独立作用域,只能在函数内部访问,函数可以嵌套,所以作用域也可以嵌套。Es6中新增了块级作用域(由大括号包裹,比如:if(){},for(){}等)
各个作用域的嵌套关系组成一条作用域链。作用域链主要是进行标识符(变量和函数)的查询,标识符解析就是沿着作用域链一级一级的搜索标识符的过程,而作用域链就是保证对变量和函数的有序访问。
如果自身作用域中声明该变量,则无需使用作用域链。如果自身作用域中未声明该变量,则需要使用作用域链进行查找。
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,该传入对象的自有可枚举属性(即其自身定义的属性,而不是其原型链上的枚举属性)将为新创建的对象添加指定的属性值和对应的属性描述符。
返回值
一个新对象,带着指定的原型对象和属性。
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 `${item}`
})
console.log(result4)
/*[ '1',
'2', ]*/
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}
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() 把指定的字符串替换成为别的字符
在全局的环境下this是指向window 的普通函数调用直接调用中的this 会指向 window, 严格模式下this会指向 undefined,自执行函数 this 指向 window,定时器中的 this 指向 window
在对象里调用的this,指向调用函数的那个对象,在构造函数以及类中的this,构造函数配合 new 使用, 而 new 关键字会将构造函数中的 this 指向实例化对象,所以构造函数中的 this 指向 当前实例化的对象。
在方法中的this谁调用就指向谁。箭头函数没有自己的 this,箭头函数的this在定义的时候,会继承自外层第一个普通函数的this
闭包的概念就是:只有权利访问另一个函数作用域中的变量,一般就是函数包裹着函数。
闭包可以重用一个变量,且保证这个变量不会被污染的一种机制。这些变量的值始终保持在内存中,不会被垃圾回收机制处理。
闭包的缺点:由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
为什么要用闭包:使用场景 : 防抖、节流、函数套函数避免全局污染
闭包原理
函数执行分成两个阶段(预编译阶段和执行阶段)。
1.在预编译阶段,如果发现内部函数使用了外部函数的变量,则会在内存中创建一个“闭包”对象并保存对应变量值,
如果已存在“闭包”,则只需要增加对应属性值即可。
2.执行完后,函数执行上下文会被销毁,函数对“闭包”对象的引用也会被销毁,但其内部函数还持用该“闭包”的引用,
所以内部函数可以继续使用“外部函数”中的变量
利用了函数作用域链的特性,一个函数内部定义的函数会将包含外部函数的活动对象添加到它的作用域链中,函数执行完毕,其执行作用域链销毁,
但因内部函数的作用域链仍然在引用这个活动对象,所以其活动对象不会被销毁,直到内部函数被烧毁后才被销毁。
都是改变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))
}
}
把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
函数柯里化是对闭包的一种应用形式,延迟计算、参数复用、动态生成函数(都是闭包的用途)。
柯里化函数:把一个多参数的函数转化为单参数函数的方法。并且返回接受余下的参数而且返回结果的新函数的技术。
// 简单的相加函数
var add = function (x,y) {
return x + y
}
// 调用:
add(1,2)
// 柯里化以后
var add = function (x) { //柯里化函数(闭包)
return function (y) {
return x + y
}
}
add(1)(2)
接收函数作为参数或者返回函数的函数
function higherOrderFunction(param,callback){
return callback(param);
}
首先在堆内存中开辟一个空间
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
}
浏览器的js具有自动垃圾回收机制,垃圾回收机制也就是自动内存管理机制,垃圾收集器会定期的找出那些不在继续使用的变量,然后释放内存。但是这个过程不是实时的,因为GC开销比较大并且时停止响应其他操作,所以垃圾回收器会按照固定的时间间隔周期性的执行。
标记清除:大部分浏览器使用这种垃圾回收,当变量进入执行环境(声明变量)的时候,垃圾回收器将该变量进行了标记,当该变量离开环境的时候,将其再度标记,随之进行删除。
引用计数:这种方式常常会引起内存的泄露,主要存在于低版本的浏览器。它的机制就是跟踪某一个值得引用次数,当声明一个变量并且将一个引用类型赋值给变量得时候引用次数加1,当这个变量指向其他一个时引用次数减1,当为0时出发回收机制进行回收。
如果 那些不再使用的变量,它们所占用的内存 不去清除的话就会造成内存泄漏。
内存泄露其实就是我们的程序中已经动态分配的堆内存,由于某些原因没有得到释放,造成系统内存的浪费导致程序运行速度减慢甚至系统崩溃等严重后果。
比如说:
由于js是单线程语言,所以任务是一个一个顺序执行的。如果一个任务耗时过长,那么后面的一个任务就必须等待。那么问题来了,假如浏览网站时图片资源很大加载的很慢,难道网页要一直卡到图片完全显示出来?所以js的任务分为了两类同步任务和异步任务。而异步任务又分为宏任务和微任务,因此js执行的过程为:同步任务->微任务->宏任务。
3-6的这个循环称为事件循环Event Loop。
事件循环是JavaScript实现异步的一种方法,也是JavaScript的执行机制。
在主线程上排队执行的任务,只有前一个任务执行完毕,才能继续执行下一个任务。new promise()、console.log()属于同步任务。
不进入主线程、而进入"任务队列"的任务;只有等主线程任务全部执行完毕,"任务队列"的任务才会进入主线程执行。
js异步有一个机制,遇到宏任务先执行宏任务,将宏任务放入eventqueue,然后在执行微任务,将微任务放入eventqueue里。但这两个queue不是同一个。当调用时先从微任务里拿这个回调函数,然后再从宏任务里的queue中拿回调函数。
JavaScript 是单线程(js不走完下面不会走是因为同步)会阻塞DOM的解析,因此也就会阻塞DOM的加载。所以有时候我们希望延迟JS的加载来提高页面的加载速度。
JS代码在执行前,浏览器会对js代码进行扫描,默认的把所有带var和function声明的变量进行提前的声明或者定义,遵循先解析后使用的原则。分为两类变量预解析(变量提升)和函数预解析(函数提升)。
JS会把申明的变量,提升到当前作用域的最前面,只申明不赋值。let、const和var都有提升,但是let定义的变量没有赋值之前是不可以使用,var可以使用是undefined。任何一个作用域执行代码之前都要预解析。
var声明的变量会被挂在到running execution context(执行上下文)的VariableEnvironment(变量环境)上,会在当前作用域的变量对象实例化的过程中被声明并赋值为undefined。 在赋值语句执行时才会为变量赋值。
let,const 声明的变量会被挂在到running execution context(执行上下文)的LexicalEnvironment(词法环境)上,会在当前作用域的变量对象实例化的过程中被创建,但是直到变量的语法声明代码被执行之前,他们都是不可被访问的。同样在赋值语句执行时才会为变量赋值。
JS会把带有名字的函数,提升到当前作用域最前面,只定义不调用。并不是所有的函数都要预解析,比如匿名函数(表达式函数)不会预解析。
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
*/
在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代码的容错性,使一些不规范的代码也可以正常执行
函数是一等公民,当函数声明与变量声明冲突时,变量提升时函数优先级更高,会忽略同名的变量声明
服务端渲染的模式下,当用户第一次请求页面时,由服务器把需要的组件或页面渲染成 HTML 字符串,然后把它返回给客户端。客户端拿到手的,是可以直接渲染然后呈现给用户的 HTML 内容,不需要为了生成 DOM 内容自己再去跑一遍 JS 代码。使用服务端渲染的网站,可以说是“所见即所得”,页面上呈现的内容,我们在 html 源文件里也能找到。有了服务端渲染,当请求用户页面时,返回的body里已经有了首屏的html结构,之后结合css显示出来。
vue全家桶或者react全家桶,都是推荐通过服务端渲染来实现路由的。
区别: ①onload事件是DOM事件,onDOMContentLoaded是HTML5事件。 ②onload事件会被样式表、图像和子框架阻塞,而onDOMContentLoaded不会。 ③当加载的脚本内容并不包含立即执行DOM操作时,使用onDOMContentLoaded事件是个更好的选择,会比onload事件执行时间更早。
一个大小不超过4K的小型文本数据,一般由服务器生成,可以设置失效时间;若没有设置时间,关闭浏览器cookie失效,若设置了 时间,cookie就会存放在硬盘里,过期才失效,每次http请求,header都携带cookie。
5M或者更大,永久有效,窗口或者浏览器关闭也会一直保存,除非手动永久清除或者js代码清除,因此用作持久数据,不参与和服务器的通信。
关闭页面或浏览器后被清除。存 放数据大小为一般为 5MB,而且它仅在客户端(即浏览器)中保存,不参与和服务器的通信。
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。
对象存储了当前文档位置(URL)相关的信息,简单地说就是网页地址字符串。使用 window 对象的 location 属性可以访问。
href会重新定位到一个URL,hash会跳到当前页面中的anchor名字的标记(如果有),而且页面不会被重新加载
window 对象给我们提供了一个 history 对象,与浏览器历史记录进行交互。该对象包含用户(在浏览器窗口中) 访问过的 URL。
window 对象给我们提供了一个 history 对象,与浏览器历史记录进行交互。该对象包含用户(在浏览器窗口中) 访问过的 URL。
element.offsetParent | 返回作为该元素带有定位的父级元素,如果父级都没有定位则返回body |
element.offsetTop | 返回元素相对带有定位父元素上方的偏移 |
element.offsetLeft | 返回元素相对带有定位父元素左边框的偏移 |
element.offsetWidth | 返回自身包括padding、边框、内容区的宽度,返回数值不带单位 |
element.offsetHeight | 返回自身包括padding、边框、内容区的高度,返回数值不带单位 |
clientTop | 返回元素上边框的大小 |
clientLeft | 返回元素左边框的大小 |
clientWidth | 返回自身包括padding 、内容区的宽度,不含边框,返回数值不带单位 |
clientHeight | 返回白身包括padding 、内容区的高度,不含边框,返回数值不带单位 |
scrollTop | 返回被卷去的上侧距离,返回数值不带单位 |
scrollLeft | 返回被卷去的左侧距离,返回数值不带单位 |
scrollWidth | 返回自身实际的宽度。不含边框,返回数值不带单位 |
scrollHeight | 返回自身实际的高度。不含边框,返回数值不带单位 |
拿到指定节点
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样式`
专注于”如何去做”,这样不管”做什么”,都会按照你的命令去做。
例如你想通过点击改变页面中某一个元素,首先要获取按钮,再给按钮添加点击事件,获取要改变的元素,执行点击函数,改变元素达到自己的目的,这是一步一步的步骤操作,就如同给计算机发布命令,一步一步执行,这就是命令式编程。
let a=1;
let b=2;
let c=a+b
专注于”做什么”而不是”如何去做”。在更高层面写代码,更关心的是目标,而不是底层算法实现的过程。
以Vue为例,在页面中通过 {{ }} 显示一个data里面的变量,你只需要改变这个变量,页面就会跟着刷新,这就是你只需要结果,vue 内部去处理过程,这就是声明式编程。
SELECT * FROM collection WHERE num > 1;
把运算过程抽象成一个函数,任何地方都可去重用这些函数,屏蔽实现的细节,只要管目标的逻辑。
抽象出来的函数是细腻的函数,可以将其组合成功能更加强大的函数。
// 非函数式
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)
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()方法提供了一种受控机制来规避此限制,只要正确的使用,这种方法就很安全。
在计算机中数字无论是定点数还是浮点数都是以多位二进制的方式进行存储的。
在JS中数字采用的IEEE 754的双精度标准进行存储(存储一个数值所使用的二进制位数比较多,精度更准确)。
对于像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 这个现象。 当然,也并非所有的近似值相加都得不到正确的结果。
// 方式一:将浮点数转化成整数计算。因为整数都是可以精确表示的。
console.log((0.1*10+0.2*10)/10===0.3)
// 方式二: js的Number对象有一个保留小数位数的方法:toFixed()。
console.log(parseFloat((0.1+0.2).toFixed(5)))
当前端应用越来越复杂时,我们想要将代码分割成不同的模块,便于复用、按需加载等。
require 和 import 分别是不同模块化规范下引入模块的语句。
module变量代表当前模块,它的exports属性是对外的接口。通过exports可以将模块从模块中导出,其他文件加载该模块实际上就是读取module.exports变量,他们可以是变量、函数、对象等。在node中如果用exports进行导出的话系统会系统帮您转成module.exports的,只是导出需要定义导出名。
const fs = require('fs')
exports.fs = fs
module.exports = fs
是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'
CommonJS 加载的是一个对象(即 module.exports 属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。- 阮一峰
require/exports 输出的是值的拷贝。也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。
import/export 模块输出的是值的引用。JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。若文件引用的模块值改变,require 引入的模块值不会改变,而 import 引入的模块值会改变。
require 相当于module.exports的传送门,module.exports 后面的内容是什么,require 的结果就是什么,比如对象、数字、字符串、函数等,然后再把 require 的结果赋值给某个变量。并可以运用在代码的任何地方,甚至不需要赋值给某个变量之后再使用。
import 是编译时运行的(require是运行时的),它必须放在文件开头,而且使用格式也是确定的,不容置疑。它不会将整个模块运行后赋值给某个变量,而是只选择import的接口进行编译,这样在性能上比require好很多。
ES 的全称是 ECMAScript , 它是由 ECMA 国际标准化组织,制定的一项脚本语言的标准化规范。
() => {} //():代表是函数; =>:必须要的符号,指向哪一个代码块;{}:函数体
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;
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指向。
ES6中新增了用于声明变量的关键字。
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;
}
声明常量,常量就是值(内存地址)不能变化的量。
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.
能用const的情况下尽量使用const,大多数情况使用let,避免使用var。 const > let > var const声明的好处,一让阅读代码的人知道该变量不可修改,二是防止在修改代码的过程中无意中修改了该变量导致报错,减少bug的产生
都是循环遍历数组中的每一项 forEach和map方法里每次执行匿名函数都支持3个参数,参数分别是item(当前每一项)、index(索引值)、arr(原数组),需要用哪个的时候就写哪个 匿名函数中的this都是指向window 只能遍历数组
注意:forEach对于空数组是不会调用回调函数的。
map方法返回一个新的数组,数组中的元素为原始数组调用函数处理后的值。(原数组进行处理之后对应的一个新的数组。) map()方法不会改变原始数组 map()方法不会对空数组进行检测 forEach()方法用于调用数组的每个元素,将元素传给回调函数.(没有return,返回值是undefined)
- 推荐在循环对象属性的时候,使用 for…in,在遍历数组的时候的时候使用for…of。
- for in遍历的是数组的索引,而for of遍历的是数组元素值。
- for…of 不能循环普通的对象,需要通过和 Object.keys()搭配使用。
- for…in 遍历顺序以数字为先 无法遍历 symbol 属性 可以遍历到公有中可枚举的。
- 从遍历对象的角度来说,for···in会遍历出来的为对象的key,但for···of会直接报错。
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
对比
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('哈哈')
它们对于值的引用都是不计入垃圾回收机制的,所以名字里面才会有一个"Weak",表示这是弱引用。
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
它是一个 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 保存的这个键值对,也会自动消失。
*/
promise是一个对异步操作进行封装并返回其结果的构造函数。
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 出现错误的时候都会执行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 => '')))
async/await相比较Promise 对象then 函数的嵌套,与 Generator 执行的繁琐(需要借助co才能自动执行,否则得手动调用next() ), Async/Await 可以让你轻松写出同步风格的代码同时又拥有异步机制,更加简洁,逻辑更加清晰。
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
是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进行遍历了
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
简单来说就是简化信息提取,在编码过程中,我们经常定义许多对象和数组,然后有组织地从中提取相关的信息片段。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
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 }"
每一次调用next方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含value和done两个属性的对象。其中,value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束。
JavaScript 模块化规范,包括原生规范 ES6 模块、Node.js 采用的 CommonJS,以及开源社区早期为浏览器提供的规范 AMD,具有 CommonJS 特性和 AMD 特性的 CMD,让 CommonJS 和 AMD 模块跨端运行的 UMD。
它通过 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(X)
X是一个Node核心模块,比如path、http直接返回核心模块,并且停止查找。
X是以./ 或…/ 或/(根目录)开头。
将X当做一个文件在对应的目录下查找:
如果有后缀名,按照后缀名的格式查找对应的文件。
如果没有后缀名,会按照如下顺序:
没有找到对应的文件,将X作为一个目录查找目录下面的index文件:
如果没有找到,那么报错:not found
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);
这种方案和 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 个属性:
采用严格模式:use strict:
- 虽然大部分主流浏览器支持 ES6 模块,但是和引入普通 JS 的方式略有不同,需要在对应 script 标签中将属性 type 值设置为“module”才能被正确地解析为 ES6 模块。
- 在 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';
}
在语句声明的前面直接加上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 {标识符列表} 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);
这里是在当前js文件中导入math.js 和 format.js,然后统一将导入的模块以当前文件的形式导出
import {add, sub} from './math.js'
import {timeFormat, priceFormat} from './format.js'
export {
add,
sub,
timeFormat,
priceFormat
}
在一个模块中,只能有一个默认导出(default export)。
//foo.js
const foo = "foo value";
export default foo;
//main.js
import pjy from "./foo.js"
console.log(pjy);//foo value
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 ...
});
待更新中…
vue是一套构建于用户界面的自底向上方 增量开发 渐进式基于MVVM的框架核心思想是数据驱动,组件化。数据驱动:视图内容根据数据的改变而改变组件化:增加代码复用性,可维护性,可测试性,提高开发效率,方便重复使用,体现了高内聚,低耦合。
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,但它可以俩俩进行双向通信的
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
})
}
}
在数据发生改变时视图会重新渲染匹配更新为最新的值。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:要定义或修改的属性描述符。
父子组件生命周期顺序
- 创建、挂载阶段:父beforeCreate→ 父created→ 父beforeMounte→ 子beforCreate→ 子created→ 子beforeMount→ 父mounted。
- 组件更新:父beforeUpdate→ 子beforeUpdate→ 子updated→ 父updated。
- 组件销毁:父beforeDestroy→ 子beforeDestroy→ 子destroyed→ 父destroyed。
当和 v-if 一起使用时,v-for 的优先级比 v-if 更高
修饰符:
- .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 } 模式添加侦听器
修饰符:
- .prop - 作为一个 DOM property 绑定而不是作为 attribute 绑定。
- .camel - (2.1.0+) 将 kebab-case attribute 名转换为 camelCase。
- .sync (2.3.0+) 语法糖,会扩展成一个更新父组件绑定值的 v-on 侦听器。
修饰符:
- .lazy - 取代 input 监听 change 事件。
- .number - 输入字符串转为有效的数字。
- .trim - 输入首尾空格过滤’
自定义指令是用来操作DOM的。尽管Vue推崇数据驱动视图的理念,但并非所有情况都适合数据驱动。自定义指令就是一种有效的补充和扩展,不仅可用于定义任何的DOM操作,并且是可复用的。
Vue.directive("focus", {
inserted: function(el){
el.focus();
}
})
directives: {
focus: {
inserted: function(el){
el.focus();
}
}
}
在图片未完成加载前,用随机的背景色占位,图片加载完成后才直接渲染出来
<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>
Vue.component('child',{
data(){
return {
mymessage:this.message
}
},
template:`
`,
props:['message'],//设置props属性值,得到父组件传递过来的数据
methods:{
passData(val){
//触发父组件中的事件,向父组件传值
this.$emit('getChildData',val)
}
}
})
Vue.component('parent',{
template:`
this is parent compoent!
`,
data(){
return {
message:'hello'
}
},
methods:{
//执行子组件触发的事件
getChildData(val){
console.log(val)
}
}
})
prop 只可以从上一级组件传递到下一级组件(父子组件),即所谓的单向数据流。而且 prop 只读,不可被修改,所有修改都会失效并警告。
Vue.component('C',{
template:`
`,
methods:{
passCData(val){
//触发父组件A中的事件
this.$emit('getCData',val)
}
}
})
Vue.component('B',{
data(){
return {
mymessage:this.message
}
},
template:`
`,
props:['message'],//得到父组件传递过来的数据
methods:{
passData(val){
//触发父组件中的事件
this.$emit('getChildData',val)
}
}
})
Vue.component('A',{
template:`
this is parent compoent!
`,
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里存放的是父组件中绑定的非原生事件。
Vue.component('brother1',{
data(){
return {
mymessage:'hello brother1'
}
},
template:`
this is brother1 compoent!
`,
methods:{
passData(val){
//触发全局事件globalEvent
bus.$emit('globalEvent',val)
}
}
})
Vue.component('brother2',{
template:`
this is brother2 compoent!
brother1传递过来的数据:{{brothermessage}}
`,
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:`
`
})
eventBus也有不方便之处, 当项目较大,就容易造成难以维护的灾难。
Vue.component('child',{
inject:['for'],//得到父组件传递过来的数据
data(){
return {
mymessage:this.for
}
},
template:`
`
})
Vue.component('parent',{
template:`
this is parent compoent!
`,
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:`
`
})
Vue.component('parent',{
template:`
this is parent compoent!
`,
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";
}
}
}
})
/***********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 解决了多个视图依赖于同一状态和来自不同视图的行为需要变更同一状态的问题,将开发者的精力聚焦于数据的更新而不是数据在组件之间的传递上。
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:`
`
})
Vue.component('parent',{
template:`
this is parent compoent!
{{message}}
`,
data(){
return {
message:'hello'
}
}
})
var app=new Vue({
el:'#app',
template:`
`
})
待更新中…
待更新中…
程序员的生活就是活到老,学到老。