一、ES5 var 和 ES6 let 、const 声明变量的优劣
1.1、var的不好地方
1.1.1 var 声明的变量 (污染全局变量)
var globalVar = '老铁,666'
console.log(window.globalVar);
1.1.2 var 会造成变量提升
console.log(improveVar);
var improveVar = '老铁,没毛病'
1.1.3 var 可以重复声明
var repeatVar = '从前有座山,山里有座庙,庙里有个老和尚在说:'
var repeatVar = '从前有座山,山里有座庙,庙里有个老和尚在说:'
var repeatVar = '从前有座山,山里有座庙,庙里有个老和尚在说:'
console.log(repeatVar);
2.1. let 的好处
2.1.1 块级作用域
{
let bolckVar = '快域'
}
console.log(bolckVar);
2.1.2 块级作用域的提升用法
比较var 和 let
for (var i =0 ;i < 5; i++) {
setTimeout(()=>{
console.log(i);
})
}
var 申明的结果都一样
let 申明的都会有一个作用域,数值不同
for (let i =0 ;i < 5; i++) {
setTimeout(()=>{
console.log(i);
})
}
2.1.3 注意let用法造成的暂存死区
let blockVarDied = '死海'
{
console.log(blockVarDied);
let blockVarDied = '死死海'
}
3、 const 常量
3.1.1. cosnt 申明的常量简单数据类型不可变
const finalVar = '万古长存'
finalVar = '枯木逢春'
console.log(finalVar);
3.1.2 const 申明的引用类型只要地址值不变,里面的对象可以赋予新的值
const finalObjVar = {title: '万古长存'}
finalObjVar.title = '枯木逢春'
console.log(finalObjVar);
二、 扩展运算符
2.1 合并数组
let concatArr1 = [1,2,3]
let concatArr2 = [4,5,6]
let concatArr = [...concatArr1,...concatArr2]
console.log(concatArr);
2.2 合并对象
let mergeObj1 = {name: '孙圣东'}
let mergeObj2 = {age: 18}
let mergeObj = {...mergeObj1,...mergeObj2}
console.log(mergeObj);
2.3 扩展运算符只能拷贝第一层的对象
let mergeObj1 = {name: '孙圣东'}
let mergeObj2 = {friend: {name: '刘翔'}}
let mergeObj = {...mergeObj1,...mergeObj2}
mergeObj2.friend.name = '翔刘'
console.log(mergeObj);
2.4 要想用扩展运算符拷贝多层数据, 需要提前知道数据结构
扩展运算符, 相同的key 后面的会覆盖前面的
let mergeObj1 = {name: '孙圣东'}
let mergeObj2 = {friend: {name: '刘翔'}}
let newMergeObj2 = {...mergeObj2, friend: {...mergeObj2.friend}}
let mergeObj = {...mergeObj1,...newMergeObj2}
mergeObj2.friend.name = '翔刘'
console.log(mergeObj);
解决知道数据结构前提下的多层拷贝
2.5 使用JSON 搭配扩展运算符 解决多层拷贝问题、
let mergeObj1 = {name: '孙圣东'}
let mergeObj2 = {friend: {name: '刘翔'}}
let mergeObj = {...mergeObj1,...mergeObj2}
mergeObj = JSON.parse(JSON.stringify(mergeObj))
mergeObj2.friend.name = '翔刘'
console.log(mergeObj);
2.6 使用JSON 搭配扩展运算符 解决多层拷贝的弊端
- 无法拷贝函数、undefined等数据
let mergeObj1 = {name: '孙圣东', paly: ()=> {console.log('唱、跳、rap');}, music: null, sex: undefined}
let mergeObj2 = {friend: {name: '刘翔'}}
let mergeObj = {...mergeObj1,...mergeObj2}
mergeObj = JSON.parse(JSON.stringify(mergeObj))
mergeObj2.friend.name = '翔刘'
console.log(mergeObj);
2.7 自己实现深拷贝
2.7.1 首先实现浅拷贝
- 拷贝对象
function deepClone(obj) {
// 1. 空obj 直接返回 ; 因为 null == undefined 为true , 所以只需要判断一个就可以了
if(obj == null) return obj
// 2. 特殊的 类型
if( obj instanceof Date) return new Date(obj)
if( obj instanceof RegExp) return new RegExp(obj)
// 3. 如果不是对象,想方法之类的没必要拷贝直接返回
if( typeof obj != 'object') return obj
// 4.是对象, 要不就是{} 对象, 要不就是[] 对象
// obj.constructor 是对象的构造方法, new 这个构造方法可以得到一个同类型对象
let cloneObj = new obj.constructor
for (let key in obj) {
console.log(key);
// 只考虑自己的属性,不需要拷贝继承的
if(obj.hasOwnProperty(key)) {
cloneObj[key] = obj[key]
}
}
return cloneObj
}
let obj = {name: '张易发', age: 18}
console.log(deepClone(obj));
- 拷贝数组
let obj = [{name: '张易发', age: 18}, {name: '鲁班7号', age: 18}]
console.log(deepClone(obj));
拷贝数组的key就是数组的索引
- 这样还是无法拷贝深层对象的
let obj ={name: '张易发', age: 18, friend: {name: '张立'}}
let cloneObj = deepClone(obj)
obj.friend.name = '666'
console.log(cloneObj);
2.7.2 一键开启深拷贝
- 只需要使用递归的方式调用我们自己封装的深拷贝方法就可以了
function deepClone(obj) {
// 1. 空obj 直接返回 ; 因为 null == undefined 为true , 所以只需要判断一个就可以了
if(obj == null) return obj
// 2. 特殊的 类型
if( obj instanceof Date) return new Date(obj)
if( obj instanceof RegExp) return new RegExp(obj)
// 3. 如果不是对象,想方法之类的没必要拷贝直接返回
if( typeof obj != 'object') return obj
// 4.是对象, 要不就是{} 对象, 要不就是[] 对象
// obj.constructor 是对象的构造方法, new 这个构造方法可以得到一个同类型对象
let cloneObj = new obj.constructor
for (let key in obj) {
console.log(key);
// 只考虑自己的属性,不需要拷贝继承的
if(obj.hasOwnProperty(key)) {
// ! 递归调用
cloneObj[key] = deepClone(obj[key])
}
}
return cloneObj
}
// let obj = [{name: '张易发', age: 18, friend: {name: '张立'}}, {name: '鲁班7号', age: 18}]
let obj ={name: '张易发', age: 18, friend: {name: '张立'}}
let cloneObj = deepClone(obj)
obj.friend.name = '666'
console.log(cloneObj);
深拷贝成功
2.7.3 自己实现深拷贝的弊端
- 循环引用导致的堆栈溢出 创建了这个对象,然后然对象中的某个属性的值等于这个对象,然后再用自己封装的深拷贝方法去深拷贝,就死循环了
let obj ={name: '张易发', age: 18, friend: {name: '张立'}}
obj.circle = obj
let cloneObj = deepClone(obj)
// obj.friend.name = '666'
console.log(cloneObj);
哈哈,我电脑配置还可以, 没有报堆栈溢出的错误,只是一直在执行拷贝方法
- 后记: 如何解决这个问题呢?我们先学完set和map回过头再来看看
三、set和map
3.1 set去重集合
- 可以理解为[] 的升级版, 只不过加了去重方法, 当然既然是升级版,当然多了些好用的api
let collectionOne = new Set([1,2,3,4,1,3])
console.log(collectionOne);
3.2 set的添加和删除
- add和delete方法
// 1 set 去重
let collectionOne = new Set([1,2,3,4,1,3])
// 添加
collectionOne.add('5')
// 删除
collectionOne.delete(1)
console.log(collectionOne);
3.3 set的遍历
- forEach方法
collectionOne.forEach(v => {console.log(v);})
3.4 其他的一些api
- values获取值, 集合大小size,keys获取所有的键,has集合中有没有某个元素, 不再举例
3.5 多个数组的结果集
3.5.1 两个数组中的去重数组
let arr1 = [1,2,3,4,3,2,1]
let arr2 = [4,5,6,5,6,7]
function union(arr1, arr2) {
let unionArr = new Set([...new Set(arr1), ...new Set(arr2)])
return [...unionArr]
}
console.log(union(arr1, arr2));
3.5.2 两个数组取交集数组
function intersection(arr1, arr2) {
// let unionArr = new Set([...new Set(arr1), ...new Set(arr2)])
let set2 = new Set(arr2);
let intersectionArr = [...new Set(arr1)].filter(v => set2.has(v))
return intersectionArr
}
console.log(intersection(arr1, arr2));
3.5.3 两个数组取差集数组
function diff(arr1, arr2) {
// let unionArr = new Set([...new Set(arr1), ...new Set(arr2)])
let set2 = new Set(arr2);
let intersectionArr = [...new Set(arr1)].filter(v => !set2.has(v))
return intersectionArr
}
console.log(diff(arr1, arr2));
3.6、map
- map是映射关系,一个键对应一个值,键相同,值会覆盖
3.6.1 map的set用法
let map1 = new Map()
map1.set('name','张三')
map1.set('name','李四')
console.log(map1);
3.6.2 引用类型的key
- 引用类型的key重置为null,原来的对象还是被map引用
let map1 = new Map()
map1.set('name','张三')
map1.set('name','李四')
let obj = {'name':'jack'}
map1.set(obj,666)
obj = null
console.log(map1);
3.7、weakMap
3.7.1 WeakMap的key不能是简单类型
let map2 = new WeakMap()
map2.set('name','张三')
console.log(map2);
引用数据类型可以
let map2 = new WeakMap()
let obj2 = {'name':'jack'}
map2.set(obj2,'123')
console.log(map2);
3.8、利用map不引用原来对象的特点,完善我们之前的递归方法
function deepClone(obj,hash = new WeakMap()) {
// 1. 空obj 直接返回 ; 因为 null == undefined 为true , 所以只需要判断一个就可以了
if(obj == null) return obj
// 2. 特殊的 类型
if( obj instanceof Date) return new Date(obj)
if( obj instanceof RegExp) return new RegExp(obj)
// 3. 如果不是对象,想方法之类的没必要拷贝直接返回
if( typeof obj != 'object') return hash.get(obj)
// 4.是对象, 要不就是{} 对象, 要不就是[] 对象
// obj.constructor 是对象的构造方法, new 这个构造方法可以得到一个同类型对象
if(hash.has(obj)) return obj
let cloneObj = new obj.constructor
hash.set(obj,cloneObj)
for (let key in obj) {
console.log(key);
// 只考虑自己的属性,不需要拷贝继承的
if(obj.hasOwnProperty(key)) {
cloneObj[key] = deepClone(obj[key], hash)
}
}
return cloneObj
}
现在就不会死循环了, 返回的是一个循环引用对象
四、Object.defineProperty
- 为什么需要使用Object.defineProperty, 我们创建了一个对象{name: '张三'}, 我们既无法对这个对象进行监视,比如谁读了这个对象,谁改了这个对象,能不能改,能不能删某个属性。这时我们就需要用到Object.defineProperty,这个也是Vue2中用到的定义对象的方法
4.1 初体验Object.defineProperty
let obj = {}
Object.defineProperty(obj, 'name', {
value: '张三'
})
console.log(obj.name);
可以见到,一个对象的属性就被我们设置好了
4.2 Object.defineProperty的可见性
- 我们直接打印上述的obj, 并不能看见里面的值
let obj = {}
Object.defineProperty(obj, 'name', {
value: '张三'
})
console.log(obj);
- 如果我们加一个可枚举性, 就可以看见了,默认的enumerable是false
let obj = {}
Object.defineProperty(obj, 'name', {
enumerable: true,
value: '张三'
})
console.log(obj);
4.3 Object.defineProperty的可配置性
- 比如我们使用 delete 删除obj中的name属性,默认是删除不了的
let obj = {}
Object.defineProperty(obj, 'name', {
enumerable: true,
value: '张三'
})
delete obj.name
console.log(obj);
- 配置 configurable: true 之后就可以删除了
let obj = {}
Object.defineProperty(obj, 'name', {
enumerable: true,
configurable: true,
value: '张三'
})
delete obj.name
console.log(obj);
4.4 Object.defineProperty的可写入性
- 我们直接赋值, name属性并没有被改变
let obj = {}
Object.defineProperty(obj, 'name', {
enumerable: true,
configurable: true,
value: '张三'
})
obj.name = '李四'
console.log(obj);
- 开启writable后就可以写入值
let obj = {}
Object.defineProperty(obj, 'name', {
enumerable: true,
configurable: true,
writable: true,
value: '张三'
})
obj.name = '李四'
console.log(obj);
4.5 Object.defineProperty的get,set 拦截器
- 注意writable, value 和 set方法不能同时使用
let obj = {}
let nameV = ''
Object.defineProperty(obj, 'name', {
enumerable: true, // 可枚举
configurable: true, // 可配置
get() {
return nameV + '---读拦截'
},
set(val) {
nameV = '写拦截---' + val
}
// writable: true, // 可重写
// value: '张三' // 值
})
obj.name = '李四'
console.log(obj.name);
4.6 setter和getter在对象中的简写
let obj2 = {
value: '',
get name () {
return this.value + '---读拦截'
},
set name (val) {
this.value = '写拦截--' + val
},
}
obj2.name = 'javascript'
console.log(obj2.name);
4.7 使用 Object.defineProperty实现vue的的数据劫持
4.7.1 第一层数据劫持
let people = {
name: '圣光',
age: 6,
friend: {
sex: 'man'
}
}
// 观察方法
function observe(obj) {
if( typeof obj !== 'object') return
for(let key in obj) {
defineReactive(obj, key , obj[key])
}
}
// 更新时的操作
function update(val) {
console.log(val, '更新视图');
}
// 双向数据响应式
function defineReactive(obj, key , value) {
Object.defineProperty(obj, key , {
get() {
return value
},
set(val) {
update(val)
value = val
}
})
}
observe(people)
people.name = '光圣'
4.7.2 多层数据劫持
如果我们改第二层friend的性别,并没有执行update方法
people.friend.sex = 'woman'
- 要想多层数据劫持
- 对传过来的value 递归劫持
- 如果我们设置的值是一个新的对象,然后改变这个对象的属性,这个对象是不会被观测的
people.friend = {
age: 18
}
people.friend.age = 19
完成的观测方法:
let people = {
name: '圣光',
age: 6,
friend: {
sex: 'man'
}
}
// 观察方法
function observe(obj) {
if( typeof obj !== 'object') return
for(let key in obj) {
defineReactive(obj, key , obj[key])
}
}
// 更新时的操作
function update(val) {
console.log(val, '更新视图');
}
// 双向数据响应式
function defineReactive(obj, key , value) {
observe(value)
Object.defineProperty(obj, key , {
get() {
return value
},
set(val) {
if(value !== val) {
observe(val)
update(val)
value = val
}
}
})
}
observe(people)
people.friend.sex = 'woman'
people.friend = {
age: 18
}
people.friend.age = 19
4.7.2 对数组的劫持
let methods = ['push','slice','pop','sort','reverse','unshift']
methods.forEach(v => {
let oldMethod = Array.prototype[v];
Array.prototype[v] = function () {
update();
oldMethod.call(this,...arguments);
}
})
people.likes.push('item...')
五、箭头函数
- () => {} 没有this 2. 没有arguments
5.1. 箭头函数初体验
let sum = (x,y) => x + y
console.log(sum(1,2));
5.2. 单个参数, 返回值简写
let increment = x => ++x
console.log(increment(1));
5.3. 高阶函数
let sumHoc = (x) => {
return (y) => {
return x + y
}
}
console.log(sumHoc(1)(2));
5.3.1 高阶函数简写
let sumHoc2 = x => y => x + y
// console.log(sumHoc(1)(2));
5.4. 返回一个对象
let backFunc = age => ({age})
console.log(backFunc(18));
5.5 测试this指向
let a = 1
let objA = {
a: 2,
fn: function () {
console.log(this.a);
}
}
objA.fn()
六、代理Proxy
- 我们知道vue2中实现原理是Object中defineProperty, vue3中是基于代理Proxy和反射ReflectProxy中提供了, set和get方法去设置值, 在proxy的set中使用Reflect反射的set去设置值, 相比于vue2的实现方式, Object.defineProperty 不能监控数组的变化,需要重写数组的方法。
- 下面是Proxy, get和set的简单用法
// 更新时的操作
function update(key,val) {
console.log(key,val, '更新视图');
}
let arr = [1,2,3,4]
let proxy = new Proxy(arr, {
set(target,key,value) {
update(key,value)
return Reflect.set(target,key,value)
// target[key] =value
},
get(target,key){
return Reflect.get(target,key)
}
})
proxy.push(1)
console.log( proxy[0] = 100);
console.log(proxy);›
- 可见在尾部push添加了一个数组, 在头部通过索引赋值的方式, 添加了一个数, 中间调用update可以拿到更新的数据。
七、Array中的Reduce详解
7.1 reduce 收敛方法
let arr = [1, 2, 3, 4, 5]
let sum = arr.reduce((x, y) => x + y)
console.log(sum);
7.2 reduce 数组嵌套对象求和, 可以传第二个参数, 初始化的参数
let arr2 = [{ price: 300, count: 2 }, { price: 400, count: 3 }, { price: 500, count: 4 }]
let sum2 = arr2.reduce((x, y) => x + y.price * y.count, 0)
7.3 reduce 合并两个对应数组为对象
let arrKey = ['name', 'age']
let arrValue = ['zs', 18]
7.4 reduce 的高级用法
- 需要使用reduce合并三个函数, 实现下面的效果
function sum3(x, y) {
return x + y
}
function toUpperCase(str) {
return str.toUpperCase()
}
function add(str) {
return `****${str}****`
}
let _r = add(toUpperCase(sum3('zfpw', 'jw')))
console.log(_r);
接下来使用reduce实现, 不需要我们一个个函数的去调用, 自己定义一个compose方法
function compose(...fns) {
return function (str1,str2) {
let lastMethod = fns.pop()
return fns.reduceRight((x,y) => {
return y(x)
},lastMethod(str1,str2))
}
}
let reduce_str = compose(add, toUpperCase, sum3)('zfpw', 'jw')
console.log(reduce_str);
使用es6简写上述方法
function compose2(...fns) {
return fns.reduce((x,y) => {
return (...args) => x(y(...args))
})
}
继续在简写
let compose = (...fns) => fns.reduce((a, b) => (...args) => a(b(...args)))
- 浓缩就是精华, react源码中就是这行代码实现的, 大道至简
7.5 自己实现reduce方法
Array.prototype.reduce = function (callback, prev) {
for (let i = 0; i < this.length; i++) {
if (prev == undefined) {
prev = callback(this[i], this[i + 1], i + 1, this)
i++;
} else {
prev = callback(prev,this[i],i,this)
}
}
return prev
}
let __r =[1,2,3].reduce((a,b) => a +b, 10 )
console.log(__r);
八 Class方法
- 就是function的升级版, 不过有些construct构造函数,和新增的构子方法
8.1 先看class的前身function
8.1.1 function的construct、prototype、 proto
function Animal(name) {
this.name = name
this.skill = ['eat','foot']
}
Animal.prototype.address = [{location: '地球'}]
let animal1 = new Animal('猴子')
let animal2 = new Animal('小狗')
console.log(animal1.skill === animal2.skill);
console.log(animal1.address === animal2.address);
// 原型链
console.log(animal1.__proto__ === Animal.prototype);
console.log(animal1.constructor === Animal);
console.log(Animal.__proto__ === Function.prototype);
console.log(animal1.__proto__.__proto__ === Object.prototype);
console.log( Object.prototype.__proto__);
- 哪些相等, 哪些不相等, 你中招了没?
8.1.2 function中的继承
// 1. 继承实例上的属性
function Animal(name) {
this.name = name
this.skill = ['eat','foot']
this.play = ['football']
}
function Tiger(name) {
this.name = name
this.skill = ['eat','foot']
// Animal.call(this)
}
// 2. __proto__
function Tiger(name) {
this.name = name
this.skill = ['eat','foot']
}
let tiger = new Tiger('小老虎')
Tiger.prototype.__proto__ = Animal.prototype;
8.1.3 Object.create
Tiger.prototype = Object.create(Animal.prototype);
console.log(tiger.play);
8.2 再来看class
8.2.1 new Class 、 实例方法、静态方法
class Animal {
// 构造器
constructor(name) {
// 实例方法
this.name = name
this.eat = '吃肉'
}
// 原型方法
sayHellow() {
console.log('你好');
}
// es7支持静态属性
static flag = '666'
static flagFunc () {
return '静态方法'
}
}
8.2.2 继承
- 这里的继承超级简单, extend就可以了, 不需要像function一样,通过call去改变this指向
class Tiger extends Animal {
constructor(name){
super(name)
}
}
let tiger = new Animal()
// 使用实例方法
console.log(tiger.eat);
// 使用静态方法
console.log(Tiger.flagFunc());