# ES6+
# let const
用 let
声明的变量会绑定到当前作用域,用 let
声明变量可以保证代码命名不重复。
const
声明的变量不能更改(对象等不能改变类型,可以改变对象属性)
import
可以变量提升
一般我们尽可能用const
,如果这个值需要改变,我们再使用let
,let
和const
声明的值不会被放到全局上
# 箭头函数
特点: 箭头函数中没有 this
, arguments
, prototype
。没有就向上找~
# 解构赋值
展开运算符 ...
常用于合并数组或对象
剩余运算符 ...
(要放在最后面)
解构的方式都是根据 key 来实现的 (这句是重点)
对象的话修改 key 对应的变量名称用 :
(这个东西叫冒号) 如果没有对应的属性可以直接设置默认值
代码示例:
// 解构赋值 ...
// 解构的方式都是根据key来实现的
let arr = ["姓名", "年龄"];
let [, age] = arr;
console.log(age);
let { objName, objAge } = { objName: "Mopecat", objAge: "forever18" }; // 这里的两边的key要相同
console.log(objName, objAge);
// 如果想要修改变量名称 可以通过:来修改,如果没有对应属性的话 可以设置默认值
let { changeName: changed, noName = "我王*泽难道不配拥有姓名吗?" } = {
changeName: "Feely"
};
console.log("修改了变量名称啊扑街", changed);
console.log("设置了默认值得变量", noName);
// 剩余运算符
// 示例只取出第一项剩下的还是一个数组 剩余运算符 ... 必须是最后一个元素 对象的用法相同
let [name1, ...args] = ["Mopecat", 18, "北京"];
console.log("取出的变量name1", name1);
console.log("剩下的还是一个数组args", args);
// 展开运算符 常用于合并数组或对象
let number = [1, 2, 3, 4];
console.log("展开后的number", ...number);
# set 和 map
set
和 map
是es6
中的新的数据类型
上面都有一下增删改查清空的方法,区别的话 set
是add
, map
是set
、get
还有对应Object
的差不多功能方法 set.keys()
、set.values()
、set.entries()
特点: 不能放重复项 (数组去重什么的简直不要太方便)
对应的还有弱保持类型 WeakSet
和 WeakMap
WeakSet
: 对象是一些对象值的集合, 并且其中的每个对象值都只能出现一次. 与 set 的区别主要有一下两点
WeakSet
对象中只能存放对象引用, 不能存放值, 而Set
对象都可以.WeakSet
对象中存储的对象值都是被弱引用的, 如果没有其他的变量或属性引用这个对象值, 则这个对象值会被当成垃圾回收掉. 正因为这样,WeakSet
对象是无法被枚举的, 没有办法拿到它包含的所有元素.
WeakMap
: 原生的 WeakMap
持有的是每个键或值对象的“弱引用”,这意味着在没有其他引用存在时垃圾回收能正确进行, WeakMap
的 key
也是不可枚举的
代码示例:
// set 和 map是es6中的新的数据类型 特点 不能放重复项
let set = new Set([1, 2, 3, 4, 4, 1, 2, 3, 4]);
console.log(set); // 没有key属性
set.add(6); // 添加方法
// set.clear(); // 清空方法
// set.delete(2); // 删除某一项
// 下面这几个方法跟Object.keys/ Object.values/ Object.entries 功能上差不多
console.log(set.keys());
console.log(set.values());
console.log(set.entries()); // [value, value] 形式的数组 分别对应着keys和values
// set 可以被迭代 即 set有Symbol.iterator
set.forEach(item => console.log(item));
set.has(1); // 判断set中是否有1
let a1 = [1, 2, 3];
let a2 = [1, 2, 3, 4, 5, 6];
// 数组并集去重
let arrbing = [...new Set([...a1, ...a2])];
console.log(arrbing);
// 数组取交集
let s1 = new Set(a1);
let s2 = new Set(a2);
let arrjiao = [...s2].filter(item => s1.has(item));
console.log(arrjiao);
// 数组取差集
let s3 = new Set(a1);
let s4 = new Set(a2);
let arrcha = [...s4].filter(item => !s3.has(item));
console.log(arrcha);
// map和set的用法基本一致,会有内存泄漏的问题,如下面的例子
class Example {
constructor(a) {
this.a = a;
}
}
let a = new Example();
let newMap = new Map([a, 1]); // 一个对应key 一个对应value
a = null; // 即使a被释放 内存中仍然存在Example这个类 可以将这部分代码放在一个html中打开用memory拍一个快照搜索一下
// 上述这种情况可以通过WeakMap来解决 即 当map的引用源对象被释放 那么map的引用也被释放
// 深拷贝 应用了weakMap
// 如果对于对象来讲 ...展开运算符只是展开一层,如果是多层对象就不是很实用了 => 是浅拷贝 功能跟Object.assign一样
let info1 = {
name: "Mopecat",
age: "forever18",
detail: { tall: 190, body: "strong", face: "老好看了" }
};
let info2 = { hobby: "篮个球", detail: { face: "男神啊我的天" } };
let newObj = { ...info1, ...info2 };
console.log(newObj); // detail 被覆盖了
// 那么什么是深拷贝呢? 就是拷贝出来的结果和以前没有关系
// 如何实现深拷贝呢?
newObj = JSON.parse(JSON.stringify(newObj)); // 一般这样实现,但是这样有一定缺点,什么缺点呢: 只能实现json格式的深拷贝 什么意思呢 ,不能有fucntion,不能有undefined,不能有正则
// 靠谱的深拷贝,递归拷贝
// 想要实现递归拷贝首先得判断数据类型 那么如何判断类型
// 1) typeof 无法区分 Array 和 Object
// 2) Object.prototype.toString().call() 无法判断是谁的实例
// 3) instanceof 可以判断类型,可以判断是谁的实例
// 4) constructor 构造函数
// 下面有注解为什么要用map / WeakMap
const deepClone = (value, hash = new WeakMap()) => {
// 排除 null 和 undefined
if (value == null) return value;
if (typeof value !== "object") return value; // 包含了函数类型
if (value instanceof RegExp) return new RegExp(value); // 如果是正则 返回一个新的正则
if (value instanceof Date) return new Date(value); // 如果是日期 返回一个新的日期
// .....特殊的要求继续判断
// 拷贝的可能是一个对象 或者是一个数组 既能循环数组 又能 循环对象 => for in 循环
let instance = new value.constructor(); // 根据当前属性构建一个新的实例
if (hash.has(value)) {
return hash.get(value);
}
hash.set(value, instance);
// console.log(instance);
for (let key in value) {
// 过滤掉原型链上的属性,如果是实例上的属性 再拷贝
if (value.hasOwnProperty(key)) {
// 将hash 继续向下传递 保证这次拷贝能拿到以前的拷贝结果
instance[key] = deepClone(value[key], hash);
}
}
return instance;
};
let cloneInfo = deepClone(info2);
cloneInfo.detail.face = "无敌炸天帅";
console.log(cloneInfo);
console.log(info2);
// 注解示例 为什么要用 map / WeakMap
// 用WeakMap代替Map是为了防止内存泄漏
// 如果不使用map/WeakMap 则下面的这个示例会陷入死循环不能自拔 用map做相对简单点不然要每次存一下对象 然后传到下一次里面 然后在判断是否有重复 跟现在的逻辑是一样的 但是实现起来相对麻烦很多
let objExample = { a: 1 };
objExample.b = objExample;
console.log(deepClone(objExample));
# class
es5
中没有类,用构造函数模拟类。es5
中的类可以当做函数来调用,es6
中不行
ES5
中的原型链:
// 面试题
// 怎么用es5来模拟es6中的class
// new的原理
// es5中没有类 用构造函数来模拟类
// 类中有两种属性 1)实例上的属性 2)公共属性
function Animal() {
// 判断当前是不是通过new来调用的
if (!(this instanceof Animal)) {
throw new Error("没有new呢哦");
}
this.name = { name: "九儿" }; // 实例上的属性
this.age = 1;
}
// es5中的类可以当做函数来调用,es6中不可以
// Animal(); // 这样调用的时候构造函数里的 this是undefined
let a1 = new Animal(); // new的时候构造函数里的this指向新的实例
let a2 = new Animal();
// 两个实实例上都有相同的属性(实例属性),但是并不相等
console.log(a1, a2);
console.log(a1.name === a2.name); // false
// 公共属性一般加在类的原型上
Animal.prototype.say = function() {
console.log("喵~");
};
// 一般情况下不要直接操作__proto__
// 实例上有个属性 __proto__ 指向类的原型,类的原型上有constrcutor指向类本身(构造函数),所以实例__proto__的constructor指向类本身(构造函数)
console.log(a1.__proto__ === Animal.prototype);
console.log(a1.__proto__.constructor === Animal);
// 每个类都有个__proto__ 包括类的原型,原型的__proto__指向父类的prototype 形成链条,知道指向Object.prototype 然后Object.__proto__ === null
console.log(a1.__proto__.__proto__ === Object.prototype);
console.log(Object.prototype.__proto__); // null
// _--------------------------------------------
// 类的继承 继承实例上的属性,继承公共属性
function Animal1() {
this.type = "哺乳类";
}
Animal1.prototype.say = function() {
console.log("我是动物");
};
function Tiger(name) {
this.name = name;
Animal1.call(this); // 调用父类的构造函数,并且让this指向子类 =>继承实例上的属性
}
// 如果要拿到父类原型上的方法还要继承原型 =>继承公共属性
// Tiger.prototype = Animal1.prototype; // 这是错误的,相当于直接与父类的原型一样了,这样会造成修改Tiger上的属性时同时影响了Animal1
// Tiger.prototype.__proto__ = Animal1.prototype; // 根据上面原型链的学习可以知道=>子类原型的__proto__指向父类的原型
// 但是要遵循尽量不要操作__proto__的原则,下一行代码是es6的
// Object.setPrototypeOf(Tiger.prototype, Animal1.prototype); // 原理是一样的
// 下面的是用的最多的
Tiger.prototype = Object.create(Animal1.prototype, {
constructor: { value: Tiger } // 将fn的constructor赋值为Tiger
});
// create 原理
// function create(parentProto, child) {
// function Fn() {}
// Fn.prototype = parentProto; // Animal1
// let fn = new Fn();
// fn.constructor = child; // Tiger
// return fn;
// }
let tiger = new Tiger("大九儿");
console.log(tiger.constructor);
console.log(tiger.type);
tiger.say();
// 继承一般就两种 Object.create 和 __proto__改变指向
// 继承实例的属性就是call
class
应用代码示例:
// 类
// __proto__ 指向所属类的原型
// prototype 所有类都有一个prototype属性
// constructor prototype.constructor 每个类的原型上都有这个属性
class Animal {
// type = "哺乳类"; // 可以这样声明在实例上 (但是现在还不支持,还是实验性语法)
constructor(name) {
this.name = name;
this.type = "哺乳类"; // 现在还是要这样写的,将来上面那样写就可以了
}
// 在原型上声明属性 Animal.prototype.a = 1
get a() {
// 这里的实现原理是 Object.defineProperty(Animal.prototype,a)
return 1;
}
// 放到原型上的方法 相当于 Animal.pototype.say
say() {
console.log(this, "===");
}
// 静态属性就是定义在类上的属性
static flag = "动物"; // 这样用=赋值是es7的语法 es6中只有静态方法 flag(){ return '动物'}
// es6中的静态属性的写法是
static get flagES6() {
return "ES6 => flag";
}
}
let animal = new Animal();
let say = animal.say; // 如果将类中的方法拿出来用必须绑定this 否则默认指向undefined
say(); // undefined
// 应该用bind绑定一下
let say1 = animal.say.bind(animal);
say1();
console.log(Animal.flag, Animal.flagES6);
class Tiger extends Animal {
// 如果子类里写了constructor 就必须调用super 不然会报错 Must call super constructor in derived class before accessing 'this' or returning from derived constructor
constructor(name) {
super(name); // 相当于 Animal.call(tiger,name) 在constructor中只能被调用一次
}
static getAnimal() {
console.log(super.flag, "静态方法中的super就是父类");
}
say() {
super.say(); //
console.log("原型上的方法中的super是父类的原型");
}
}
let tiger = new Tiger("老虎");
console.log(Tiger.flag); // 静态方法和静态属性在es6中也会被子类继承
console.log(tiger);
tiger.say();
# 装饰器
- 装饰模式:不改变原有状态,在访问原来函数之前做一系列操作
- AOP 面向切片编程
- 重写原型方法 例子,vue 里调用数组方法实现视图的更新 函数劫持,下面例子
let oldProto = Array.prototype; // 保留数组原有的原型
let proto = Object.create(oldProto); // 创建了一个新proto
["push", "unshift", "shift"].forEach(method => {
proto[method] = function(...args) {
update();
oldProto[method].call(this, ...args);
};
});
function observer(obj) {
// 只将我们传入的数组中的方法重写
if (Array.isArray(obj)) {
obj.__proto__ = proto;
}
}
observer(arr);
arr.push(1);
console.log(arr);
[].push(4, 5, 6);
其他代码示例:
// 装饰器 装饰模式, 在执行类之前,对类,类的属性还有类中的方法进行包装(类中的属性,还有类中的方法)参数分别为 类的原型,装饰的key,key对应的属性装饰器
// 装饰器必须是个函数 传参了就再包一层返回一个函数
@type1("传了个参数1")
@type2("传了个参数2")
class Animal {}
// 对类进行扩展 如果不传参 就近执行 先执行2后执行1, 如果传参了 就先执行外层函数的1,2,然后执行内层函数的2,1,像切洋葱一样的执行顺序
function type1(args) {
console.log(`t1`);
return function(Constructor) {
console.log("inner1");
Constructor.type1 = args;
};
}
function type2(args) {
console.log(`t2`);
return function(Constructor) {
console.log("inner2");
Constructor.type2 = args;
};
}
let animal = new Animal();
console.log(animal);
class Circle {
@readonly PI = 3.14; // 实例上的属性
@before
area(radius) {
console.log(this);
console.log("圆的面积是:" + radius * radius * this.PI);
}
}
function readonly(circlePrototype, key, descriptor) {
console.log(circlePrototype, key, descriptor);
descriptor.writable = false;
}
function before(circlePrototype, key, descriptor) {
let func = descriptor.value; // 赋值原函数
descriptor.value = function(args) {
console.log(
"求圆的面积之前呢 可以做点什么,顺便求个周长吧:" + this.PI * args * 2
);
func.bind(this)(args); // 调用原函数,绑定当前的this,circle
};
}
let circle = new Circle();
circle.PI = 3.111; // 修改失败
console.log(circle.PI);
circle.area.bind(circle)(10); // 将area的this指向circle 用es6 class声明的原型上的方法中的this指向undefined
// mixin
let obj = {
name: "Feely",
age: "forever 18",
info() {
console.log("Feely贼几把帅");
}
};
@mixin(obj)
class Feely {}
// const mixin = obj => Feely => Object.assign(Feely.prototype, obj);
// 试了一下上面的简便写法 报错,错误是const不支持变量提升这种错误,所以目前来看装饰器的外层函数必须要用function来写了
function mixin(obj) {
return function(Feely) {
Object.assign(Feely.prototype, obj);
};
}
let feely = new Feely();
console.log(feely); // obj的属性和方法都已经挂到了 Feely的原型上
feely.info();
# generator
生成器 生成迭代器的 (很快就被 async+await 取代掉了,但是相对来讲更加灵活)
应用实例: redux-saga
用法:生成器函数上加 *
函数中 关键字 yield
可以暂停执行,返回值是迭代器 iterator
可以通过 iterator.next()
返回对象及当前执行状态类似于这种{value: 11, done: false}
代码示例:
// 生成器 生成迭代器的 => es6 (很快就被async+await取代掉了,但是相对来讲更加灵活)
// 用法 * 生成器函数 ,返回值叫迭代器
function* read() {
yield 1;
yield 2;
yield 3;
}
// iterator 迭代器
let it = read();
console.log(it.next()); // {value: 1, done: false} done表示没有结束 不停地next执行下去
// 将类数组转化为数组
// 类数组的定义: 1.索引 2、长度
function add() {
console.log([
...{
0: 1,
1: 2,
2: 3,
length: 3,
// 生成器函数实现 返回的就是迭代器
[Symbol.iterator]: function*() {
let index = 0;
while (index !== this.length) {
yield this[index++];
}
}
// 迭代器实现
// [Symbol.iterator]() {
// let len = this.length;
// let index = 0;
// // 迭代器是有next方法 而且方法执行后需要返回value,done
// return {
// next: () => {
// return { value: this[index++], done: index === len + 1 };
// }
// };
// }
}
]);
}
add(1, 2, 3, 4, 5);
// 延伸面试题 ...和Array.from有什么区别: ...所展开的对象,类数组,数组等必须有迭代器方法
function* read1() {
// 可以用try catch
try {
let a = yield 1;
console.log("a", a);
let b = yield 2; // 这里的返回值是上一次调用next传入的参数
console.log("b", b);
let c = yield 3;
console.log("c", c);
} catch (e) {
console.log("e", e);
}
}
let it1 = read1();
console.log(it1.next()); // 第一次传入的参数毫无意义
console.log(it1.next()); // a,undefined
console.log(it1.next(100)); // b, 100
it1.throw("有错误了");
const fs = require("fs").promises;
function* read2() {
let content = yield fs.readFile("./name1.txt", "utf8");
let age = yield fs.readFile(content, "utf8");
return age;
}
let it2 = read2();
// 用法很复杂
// it2.next().value.then(data => {
// it2.next(data).value.then(data => {
// let r = it2.next(data);
// console.log(r.value);
// });
// });
// 封装一个co方法 用以简便上面复杂过程
function co(it) {
return new Promise((resolve, reject) => {
// 异步迭代需要先提供一个next方法
function next(data) {
let { value, done } = it.next(data);
if (!done) {
Promise.resolve(value).then(
data => {
next(data);
},
err => {
reject(err);
}
);
} else {
resolve(value);
}
}
next();
});
}
co(read2()).then(data => console.log(data));
// async + await 其实是 generator + co的语法糖
async function read3() {
try {
let content = await fs.readFile("./name1.txt", "utf8");
let age = await fs.readFile(content, "utf8"); // await会阻塞代码,必须要等待上一个await执行完才能执行这一部分,如果要求并发可以用Promise.all来实现
return age;
} catch (err) {}
}
read3().then(data => console.log(data));
# Symbol
5中基本的数据类型: string,boolean,null,number,undefined
Symbol
是新增的一种数据类型,独一无二,一旦声明完跟别的值都不相等,所以一般用作声明常量
# 基础用法
const s1 = Symbol("mopecat"); // 接收参数是string || number
const s2 = Symbol("mopecat"); // 接收参数是string || number
console.log(s1 === s2); // false
另一种创建方式 Symbol.for()
如果没有就创建新的 如果有就返回
let sf1 = Symbol.for("sf");
let sf2 = Symbol.for("sf");
console.log(sf1 === sf2); // true
console.log(Symbol.keyFor(sf1)); // 返回key值 上面的sf
在对象中定义 Symbol
属性 可以用来做属性私有化
let obj = {
[s1]: "Symbol定义的值" // es6的写法 []的含义是将s1结果取出来作为key
};
console.log(obj[s1]); // 缺陷是不能用obj.s1来取Symbol的值了
# 元编程
可以改变js原有的功能
# 1) instanceof
可以判断谁是谁的实例
let o = {
name: 1
};
let obj1 = {
// 下面在使用instanceof时会默认调用下面的这个方法 但是定义了Symbol.hasInstance的对象要在instanceof的右侧
[Symbol.hasInstance]() {
return "name" in o;
}
};
console.log(o instanceof obj1); // true
# 2) Symbol.toPrimitive
当转换原始类型的时候会调此方法
let obj2 = {
// 当转换原始类型的时候会调此方法
[Symbol.toPrimitive](value) {
console.log(value);
return "hello";
},
a: 1
};
// valueOf toString
console.log(obj2 + 1);
# 3)修改衍生对象的类的构造函数
class MyArray extends Array {
constructor(...args) {
super(...args); // 继承父类
}
// 静态 属性上的方法
static get [Symbol.species]() {
return Array; // 控制衍生出来的对象的类的构造函数是Array 不是 MyArray
}
}
let myarr = new MyArray(1, 2, 3);
let newArr = myarr.map(item => item * 2); // newArr就是衍生出来的数组对象
console.log(newArr instanceof MyArray); // false
# 4) Symbol.split
可以重写数组的分割方法
# 5) Symbol.search
可以重写数组的搜索方法
# 6)Symbol.match
可以重写字符串的match方法
# 7)Symbol.unscopables()
可以声明一下属性 不在with中使用
console.log(Array.prototype[Symbol.unscopables]); // 数组上不能用在with中的方法
with (Array.prototype) {
fill(112); // fill不能用在with中所以报错
}
# 8) concat
不展开数组 isConcatSpreadable
let arr = [1, 2, 3];
arr[Symbol.isConcatSpreadable] = false;
console.log(arr.concat(4, 5, 6)); // [[1,2,3],4,5,6]
# 例子:
// 再来一个例子
class Me {
showName() {
console.log("Mopecat");
}
get [Symbol.unscopables]() {
return { showName: true };
}
}
with (Me.prototype) {
showName(); // 报错 如果注掉class Me里的Symbol.unscopables就不会报错了 就会正常打印Mopecat了
}
# proxy
proxy
兼容性差,代理,可以创建一个代理帮我们干某些事
# 用法:
let obj = {
a: 1,
b: 2
};
let proxy = new Proxy(obj, {
// 只能代理当前的对象obj 也就是一层
get(target, key) {
// return target[key];
return Reflect.get(target, key); // 跟上一行等价
},
set(target, key, value) {
console.log("更新视图", target, key, value);
// target[key] = value;
return Reflect.set(target, key, value); // 跟上一行等价
}
});
proxy.c = 3;
console.log(obj);
# 如果是多层对象呢 该怎样实现代理呢 看下面的代码~
let obj1 = {
a: { a: 122 },
b: 2
};
let handler = {
get(target, key) {
if (typeof target[key] === "object" && target[key] !== null) {
return new Proxy(target[key], handler);
}
return Reflect.get(target, key);
},
set(target, key, value) {
console.log("更新");
return Reflect.set(target, key, value);
}
};
let proxy1 = new Proxy(obj1, handler);
proxy1.a.a = 1000;
console.log(obj1);
# 数组一样可以实现代理监控
let arr = [1, 2, 3, 4];
let proxyArr = new Proxy(arr, {
get(target, key) {
return Reflect.get(target, key);
},
set(target, key, value) {
if (key === "length") return true; // 如果操作会更改数组的length 比如push等 会触发两次set 一次更改 对应的key,一次修改length,所以屏蔽修改length触发的set
console.log("更新");
return Reflect.set(target, key, value);
}
});
proxyArr.push(100);
console.log(arr);
# Reflect 反射
有部分是对象的方法,放到了 Reflect
上,功能是基本一致的,但是用起来更加的方便
# 1) get/set
let obj = {};
obj.name = "Mopecat";
// Reflect用法
Reflect.set(obj, "age", "forever18"); // 参数分别为 target(目标对象) key(属性名) value(值)
# 2) has
console.log("a" in { a: 1 });
console.log(Reflect.has({ a: 1 }, "a")); // 参数分别为 target(目标对象) key(属性名)
# 3) defineProperty
用法和功能与Object.defineProperty
基本一致
const obj3 = { a: 1 };
Object.freeze(obj3); // 这个对象就被冻结了 对象里的属性就不能配置了 如果用Object.defineProperty 配置就会报错了 但是用Reflect.defineProperty就不会 但是如果配置失败会返回false
let flag = Reflect.defineProperty(obj3, "a", {
value: 100
});
console.log(flag); // false
# 4) getOwnPropertyDescriptor
获取对象自有属性对应的属性描述 与 Object.getOwnPropertyDescriptor
一样
const obj4 = { a: 1 };
console.log(Reflect.getOwnPropertyDescriptor(obj4, "a"));
# 5) ownKeys = [...Object.getOwnPropertyNames , ...Object.getOwnPropertySymbols]
Object.getOwnPropertyNames
返回一个由指定对象的所有自身属性的属性名(包括不可枚举属性但不包括Symbol
值作为名称的属性)组成的数组
Object.getOwnPropertySymbols
返回一个给定对象自身的所有 Symbol
属性的数组
const obj5 = { a: 1, [Symbol()]: 1 };
Object.getOwnPropertySymbols;
let arr5 = Reflect.ownKeys(obj5);
console.log(arr5);
# 6) getPrototypeOf / setPrototypeOf
获取或者设置一个对象的原型 参数是 target
目标对象 prototype
该对象的新原型(也就是一个对象或者null
)
const obj6 = { a: 1 };
Reflect.setPrototypeOf(obj6, null);
let obj6R = Reflect.getPrototypeOf(obj6);
console.log(obj6R); // null
# 7) 函数的apply方法
// 延伸:经典面试题 bind call apply 的区别 共同点都能改变this的指向
const fn = function(a, b) {
console.log(this, a, b);
};
// apply 支持多个参数传参
fn.apply(1, [2, 3]); // this:1 , a:2 , b:3
// call 参数要一个一个传 并直接执行函数
fn.call(1, 2, 3);
// bind产生一个新的函数 剩下的参数也要一个一个传(bind函数可以用函数柯里化写出来)
let fn2 = fn.bind(1, 2, 3);
Reflect.apply(fn, 1, [2, 3]); // fn 绑定的函数,1 this的指向,[2,3]是参数 等价于 Function.prototype.apply.call(fn,1,[2,3]) 这样做的是为了 调用原型上的apply方法 (比如在实例上写了一个apply方法 但是你想调用的原型上的 就这样用行了)
# 8) construct
构造器 跟new
的功能一样
class Example {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
let example = Reflect.construct(Example, ["Mopecat", "forever18"]); // 参数 target构造函数 ,[]参数数组
console.log(example);
# 10) isExtensible / preventExtensions
是否可扩展 / 组织扩展
let obj10 = {};
Reflect.preventExtensions(obj10);
obj10.a = 1;
console.log(obj10, Reflect.isExtensible(obj10));
# Object.defineProperty
# 用法:
let obj1 = {};
let val = ""; // 修改的时候一样也要用个第三方变量中转一下
Object.defineProperty(obj1, "a", {
configurable: true, // 是否可配置(删除)
// writable: true, // 是否可写 (写了get 和 set 要删除这个属性 默认就是可写的了)
enumerable: true, // 是否可枚举
get() {
return val;
},
set(value) {
console.log(value);
val = value;
}
});
console.log(obj1);
# vue中的数据劫持 就是给每一个对象都添加一个getter和setter 当值变化了 可以实现更新视图的功能 原理代码~~
let obj3 = {
a: { a: 1 },
b: 2
};
// 更新视图方法
let updateView = () => {
console.log("更新了视图");
};
// 缺陷就是无法监控数组的变化
function observer(obj) {
if (typeof obj !== "object" || obj == null) {
return;
}
for (let key in obj) {
// 因为defineProperty需要一个特公共的值去修改,所以利用函数作用域将value保存下来作为公共的值去修改
defineReactive(obj, key, obj[key]);
}
}
function defineReactive(obj, key, value) {
observer(value); // 递归增加getter 和 setter
Object.defineProperty(obj, key, {
get() {
return value;
},
set(val) {
updateView();
value = val;
}
});
}
observer(obj3);
obj3.b = 100;
console.log(obj3.b);
因为递归更加消耗性能 且 不能监控数组变化 且 无法增加被监控的属性(需要通过vm.$set
来实现) 所以新版改用proxy
来实现