ES6面向对象
合并
取值函数Object.assign()
/*** 合并对象* Object.assign(target, ...sourses);* 将需要合并的对象合并到对象里* @target 目标对象* @sourses 可能是多个的源对象* @返回值 目标对象所对应的值*/let obj = {a: 1};let tar = {};let copy = Object.assign(tar, obj);console.log(copy); //{a: 1}
//把几个对象进行合并//后面的属性会覆盖前面的属性const tar = {a: 1,b: 1};const tar2 = {b: 2,c: 2};const tar3 = {c: 3};Object.assign(tar, tar2, tar3);console.log(tar);//{a: 1, b: 2, c: 3}
利用Object.assign({},obj)拷贝对象,复制的值是浅拷贝对象
var obj = Object.create({foo: 1}, {bar: {value: 2},baz: {value: 3,enumerable: true}})console.log(obj);//{baz: 3, bar: 2}//不能拷贝继承属性和不可枚举属性var copy = Object.assign({}, obj);console.log(copy);//{baz: 3}
拷贝数组 替换数组元素
var a = Object.assign([1, 2, 3], [4, 5]);console.log(a);//[4, 5, 3]
//拷贝的不是函数体本身而是具体的值const sourse = {get foo() {return 1;}}const target = {};Object.assign(target, sourse);console.log(target);//{foo: 1}
克隆对象
const obj = {a: 1,b: 2,c: 3};const clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));console.log(clone);//{a: 1, b: 2, c: 3}
原型
访问原型的方法代替person.__proto__
Object.setPrototypeOf():写Object.getPrototypeOf():读取Object.create(obj):生成
//通过Object.setPrototypeOf()方式手动指定原型let proto = {y: 20,z: 40};let obj = {x: 10};Object.setPrototypeOf(obj, proto);console.log(obj);/*** {x: 10}x: 10__proto__:y: 20z: 40*/
super
super指向的是对象的原型对象(父类)
//super指向原型let proto = {y: 20,z: 40,};let obj = {x: 10,//对象的简写方式访问原型上的y//只有对象的简写的写法才能生效foo() {console.log(super.y);}};Object.setPrototypeOf(obj, proto);obj.foo(); //20/*** {x: 10}x: 10__proto__:y: 20z: 40*/
关于super()
class Father{constructor(){//如果没有实例化Father类时,没有this绑定}}class Son extends Father{constructor(){//子类的super()方法实际上做了生成this绑定,并将this绑定为父类的this//所以如果在super()之前访问this会报错this.hobby = 'traval';super();}}
属性描述符
ES5之前没有提供检测属性特征的方法,检测属性是否是只读/可遍历
ES6提供属性描述符
属性描述符的方法有:
Object.defineProperty()Object.defineProperties()
为什么会有属性描述符的方法?
因为JavaScript是弱类型语言,对于对象属性,变量的描述是不够彻底的,所以才有属性描述符方法对其进行完善处理
/*** 查找属性描述符* Object.getOwnPropertyDescriptor()* @参数1 目标对象* @参数2 当前对象的属性描述符* @返回值 一个包含value,writable,enumerable,configurable属性的对象* @value 当前值* @writable 可写* @enumerable 可枚举* @configurable 可配置*/let obj = {a: 2};console.log(Object.getOwnPropertyDescriptor(obj, 'a'));//{value: 2, writable: true, enumerable: true, configurable: true}
/*** Object.defineProperty()* 能够修改一个已有的属性,或添加一个新的属性* @参数1 需要定义的对象* @参数2 当前对象的属性* @参数3 对象,定义属性的描述* @返回值 修改后的对象* @value 当前值* @writable 可写 默认不可修改* @enumerable 可枚举 默认不可枚举* @configurable 可配置 默认不可删除*/let obj = {};Object.defineProperty(obj, 'a', {value: 2,enumerable: true,writable: true,configurable: true});console.log(obj);//{a: 2}//试着打印当前描述状态console.log(Object.getOwnPropertyDescriptor(obj, 'a'));//{value: 2, writable: true, enumerable: true, configurable: true}
//当writable为不可写(静默失败)时,却可以被删除值//但configurable为不可配置时,不能删除值let obj = {};Object.defineProperty(obj, 'a', {value: 2,enumerable: true,writable: false,configurable: true});delete.obj.a;console.log(obj); //undefined
function defineProperty(){var _obj = {};var a = 1;//每次定义属性的时候,都会有getter/setter的机制//该机制会对每次获取或重新赋值操作属性时都生效//getter/setter操作时会可以增加额外的操作Object.defineProperties(_obj, {a: {//注意:此处不能加writable或value描述符(不能与get,set共存),否则报错get(){//额外的操作},set(newValue){//额外的操作}},b: {...}});return _obj;}//执行var obj = defineProperty();//使用:赋值obj.a = 1;
Class
访问原型的方式有:
Object.getPrototypeOf(person);Person.prototype
关键字class表示类,本质是语法糖
//ES5写法function Person(name, age) {this.name = name;this.age = age;}Person.prototype.say = function () {console.log('My name is ' + this.name + ', my age is' + this.age);}new Person('zhangsan', '18');/*** Person {name: "zhangsan", age: "18"}* age: "18"* name: "zhangsan"* __proto__:* say: ƒ ()* constructor: ƒ Person(name, age)* __proto__: Object*/
//ES6写法//区别:class类里面的方法是不可枚举的class Person {//类似ES6简写方法的写法constructor(name, age) {//私有属性//实例化的属性配置this.name = name;this.age = age;}//共有方法(对应原型上的方法)say() {console.log('i can say');}}new Person('lisi', '20');/*** Person {name: "lisi", age: "20"}* age: "20"* name: "lisi"* __proto__:* constructor: class Person* say: ƒ say()* __proto__: Object*/
尝试利用Object.keys()方法遍历原型上的方法
//ES5console.log(Object.keys(Person.prototype)); //["say"]
尝试利用Object.keys()方法遍历class上的方法
//ES6console.log(Object.keys(Person.prototype)); //[]
说明区别:
类内部的方法是不可枚举的
类里的constructor能否更改this的指向呢?
class Person {constructor() {//手动指定this//返回一个新的对象会更改this指向return Object.create(null);}}console.log(new Person() instanceof Person); //false
奇怪的写法:函数表达式的写法
let Person = class {say() {console.log('111');}}new Person().say(); //111
更奇怪的写法(理解即可,不能用于实际开发)
//立即执行//必须通过new的方式执行类let person = new class {say() {console.log('111');}}();person.say(); //111
注意点:
//不存在函数声明提升//跟let一样形成暂时性死区(TDZ)console.log(new Person());class Person{}
类里面是否可以含有共有属性?
class Person {a = 1;constructor(name) {this.a = name;}}console.log(new Person('wangwu'));/*** Person {a: 1, name: "wangwu"}* a: "wangwu"* __proto__: Object*///说明并没有共有属性,默认把属性在constructor底下定义了(私有属性)
如何定义共有方法私有化?外部不能访问
//利用Symbol生成独一无二的字符串定义方法名const eat = Symbol();class Person {constructor(name) {this.name = name;}say() {console.log('saying');};[eat]() {console.log('eating');}}let person = new Person('wangwu');person.say(); //sayingperson.eat(); //报错
static关键字:静态方法,一般情况下不能用于定于属性
//静态方法一般不会被new 实例执行的,而是直接通过类名调用执行class Person {static a() {console.log(1);};}Person.a(); //1
类里自动定义取值函数和存值函数getter/setter
class Person {get a() {console.log(1);}set a(value) {console.log(2);}}let person = new Person();person.a; //1person.a = '3'; //2
注:类里默认是严格模式
继承
extends关键字:实现继承关系
class Parent {constructor() {this.name = 'zhangsan';}}//继承父级class Child extends Parent {}console.log(new Child());/*** Child {name: "zhangsan"}* name: "zhangsan"* __proto__: Parent*/
派生类
//通过类进行传值class Parent {constructor() {this.name = 'zhangsan';}}class Child extends Parent {constructor() {this.age = '19';}}console.log(new Child().age);//必须通过super关键字指定this//Uncaught ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor//增加super方法class Child extends Parent {constructor() {//super必须写在constructor里的最前面super();this.age = '19';}}console.log(new Child().age); //19
super当对象的时候:
- 在对象当中指代对象的原型
- 在静态方法中指向自己的父类
getter/setter
get/put操作是获取属性/赋值操作
ES5中getter/setter的存在为了改写当前的[[Get]]/[[Put]]的默认操作,getter/setter操作时会可以增加额外的操作
关于数据劫持:
把一个对象里的属性进行可配置,可写,可枚举的设置,通过getter/setter方法对取值赋值进行逻辑上的扩展
let obj = {a: 1};//这里是通过什么方式访问到a?//通过 [[Get]] 属性获取操作//对象默认的内置[[Get]]操作如何实现查找属性的?//[[Get]]默认操作:查找当前属性,如没有往原型找,如果原型都没有,返回undefinedobj.a;//通过 [[Put]] 属性赋值操作//[[Put]]默认操作://1.访问是否是属性描述符getter/setter//2.如果不是,再看writable的状态是否为false//3.赋值obj.a = 3;
//模拟get方法var obj = {log: ['example', 'test'],//自定义get方法获取obj里的属性get latest() {if (this.log.length === 0) {return undefined;}//返回当前数组的最后一项return this.log[this.log.length - 1];}}console.log(obj.latest); //test
//变种写法//通过defineProperty来定义一个gettervar myObject = {get a() {return 2;}}Object.defineProperty(myObject, 'b', {get: function () {//拿到值乘以2return this.a * 2;},enumerable: true})console.log(myObject.a); //2console.log(myObject.b); //4
//setter 调用set方法var language = {//参数形式赋值到name里set current(name) {this.log.push(name);},log: []}language.current = 'EN';console.log(language.log); //["EN"]
//get和set一般请求都是成对出现的//getter/setter操作,覆盖了原本[[Get]][[Put]]var obj = {get a() {return this._a;},set a(val) {return this._a = val * 2;}}obj.a = 3;console.log(obj.a); //6
const obj = {get foo() {},set foo(x) {}}//获取属性描述符var descriptor = Object.getOwnPropertyDescriptor(obj, 'foo');//打印可描述性属性的取值函数 get fooconsole.log(descriptor.get.name);
禁止拓展/判断是否可以拓展
//对象常量 设置//不可修改/不可删除 configurable:false//不可重写 writable: false//但可以往对象里新增属性和值var obj = {a: 2};//禁止对象拓展,阻止拓展属性 prventExtensions()Object.preventExtensions(obj);obj.b = 3;console.log(obj); //{a: 2}
//isExtensionsible()判断是否可以拓展//false禁止拓展/true可以拓展console.log(Object.isExtensible(obj)); //false
描述符的默认状态
//属性描述符 默认是falsevar obj = {a: 2};Object.defineProperty(obj, 'b', {value: 6})console.log(Object.getOwnPropertyDescriptor(obj, 'b'));//{value: 6, writable: false, enumerable: false, configurable: false}
//属性描述符 全都是falsevar obj = {a: 2};obj.b = 3;console.log(Object.getOwnPropertyDescriptor(obj, 'b'));//{value: 3, writable: true, enumerable: true, configurable: true}
Proxy
Proxy代理与defineProperty()定义属性方法像完成同一个功能,但原理完全不一样,有了vue存在,proxy与defineProperty有了交集
本质区别:
defineProperty给对象增加属性,修改数组的长度,用索引设置元素的值,数组的push, pop, 等一系列方法是无法出发defineProperty里的set方法,所以vue2.x版本里对数组的所有的操作都是vue再封装,并不是原生的push,pop等方法,导致vue代码非常的重,但Proxy可以正常使用原生的数组方法,比较轻便,不影响set方法使用,在数据拦截编写操作时更加合理,功能更加强大
写法:
let obj = new Proxy(target, handler);//target 目标对象 要进行处理的对象//handler 容器 无数可以处理对象属性的方法
Proxy是一个ES6的构造函数
var proxy = new Proxy(obj, {...});
Proxy与defineProperty区别:
defineProperty:数据劫持 -> 给对象进行拓展 -> 属性进行设置Proxy:并不是数据劫持,通过处理一个对象,返回一个代理对象,操作代理对象对数据进行操作
Proxy能够产生什么样的作用?
自定义对象属性的获取,赋值,枚举,函数调用的等功能
//如何使用proxy读取属性?var target = {a: 1,b: 2}let proxy = new Proxy(target, {get(target, prop){//注意:get里面必须带有return否则undefinedreturn `This is propery value ${target[prop]}`;}});//如果访问proxy.a,则返回代理后的对象里的属性console.log(proxy.a); //1//如果访问没有代理过的对象target,则访问原来对象里的属性console.log(target.a); //1
//如何使用proxy更改属性?var target = {a: 1,b: 2}let proxy = new Proxy(target, {//target 处理的对象//prop 处理的对象里的属性//value 新修改的属性值set(target, prop, value){//需要处理新修改的值target[prop] = value;}});proxy.b = 3;console.log(target);//注意:代理后会更改原有对象的值//{a: 1, b: 3};
Proxy不仅可以操作对象,还可以操作数组和函数
var arr = [];var fn = function(){...}let proxy1 = new Proxy(arr, {get(arr, prop){return arr[prop];}});let proxy2 = new Proxy(fn, {get(fn, prop){return fn[prop];}});
let star = {name: 'wyf',age: 30,phone: 'star 15800000000'}/*** Proxy()* @param1 代理目标对象* @param2 定义代理值行为的一个函数的对象* @target 目标对象* @key 目标对象的键名* @value 目标对象的键值*///目标某些信息被拦截,且改写为中间商的信息let agent = new Proxy(star, {//查找明星的联系方式//读取操作get: function (target, key) {if (key === 'phone') {//返回代理人的电话return 'agent: 13788888888';}if (key === 'price') {//出场费return 120000;}return target[key];},//赋值操作set: function (target, key, value) {if (value < 1000000) {console.log('出场费太低,拒绝');} else {target[key] = value;return true;}},//has操作has: function (target, key) {console.log('请联系agent:14800000000');if (key === 'customPrice') {return target[key];} else {return false;}}});console.log(agent.phone); //agent: 13788888888console.log(agent.price); //120000console.log(agent.name); //wyfconsole.log(agent.age); //30agent.customPrice = 1500000;console.log(agent.customPrice); //1500000
拦截对象属性的读取,比如proxy.foo和proxy['foo']
get(target, propKey, receiver);
拦截对象属性的设置,比如proxy.foo = v和proxy['foo'] = v ,返回一个布尔值
set(target, propKey, receiver);
拦截propKey in proxy的操作,返回一个布尔值
has(target, propKey);
拦截delete proxy[propKey]的操作,返回一个布尔值
deleteProperty(target, propKey);
//拦截Object.getOwnPropertyNames(proxy)Object.getOwnPropertySymbols(proxy)Object,keys(proxy)//返回一个数组,改方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性
Object.getOwnPropertyDescriptor(target, propKey);Object.getOwnPropertyDescriptor(proxy, propKey);//返回属性的描述对象
//拦截Object.defineProperty(target, propKey, propDesc);Object.defineProperties(proxy, propDesc);//返回一个布尔值
//拦截Object.preventExtensions(proxy);//返回一个布尔值
//拦截Object.preventExtensions(proxy);//返回一个布尔值
//拦截Object.getPrototypeOf(proxy);//返回一个对象
//拦截Object.isExtensible(proxy);//返回一个布尔值
//拦截Object.setPrototypeOf(proxy, proto);//返回一个布尔值,如果目标对象是函数,那么还有两种操作以外可以拦截
//拦截Proxy实例作为函数调用的操作//比如proxy(...args), proxy.call(object, ...args);proxy.apply(target, object, args)
//拦截Proxy实例作为函数调用的操作//比如new Proxy(...args)construct(target, args);
重写一个Proxy
//基本写法var target = {a: 1,b: 2}let proxy = new Proxy(target, {get(target, prop){return 'This is property value' + target[prop];},set(target, prop, value){target[prop] = value;}});console.log(proxy.a);console.log(target.a);
function MyProxy(target, handler) {//传入的target需要处理,并且是一个新的target克隆对象let _target = deepClone(target);function deepClone(org, t ar) {var tar = tar || {},toStr = Object.prototype.toString,arrType = '[object Array]';//遍历原来传入的对象for (var key in org) {//判断是否含有自身属性if (org.hasOwnProperty(key)) {//判断自身的属性值是否是对象并且不能为nullif (typeof (org[key]) === 'object' && org[key] !== null) {//判断对象里属性值是否为数组if (toStr.call(org[key]) === arrType) {//是数组 将新的对象的属性值创建为一个新的空数组tar[key] = [];} else {//不是数组 将新的对象的属性值创建为一个新的空对象tar[key] = {};}//递归底层的属性值 深克隆deepClone(org[key], tar[key]);} else {//不是对象的情况就让其变为对象tar[key] = org[key];}}}return tar;}//拷贝完后,遍历键名和键值//Object.keys()遍历出自身的可枚举的键名(不含继承属性)Object.keys(_target).forEach(function (key) {//Proxy实现用defineProperty定义属性Object.defineProperty(_target, key, {//handler里面有get/set函数//get有target, prop参数//set有target, prop, value参数//get函数里必须有返回值returnget() {//有get函数时执行它并传入对象和属性return handler.get && handler.get(target, key);},set(newValue) {handler.set && handler.set(target, key, newValue);}});});//最终返回处理好的对象return _target;}
总结:
Proxy的内部方法如has/set/get/deletePropert等转发给target对象,调用Proxy的方法相当于调用target相应的方法, handler方法可以复写任意代理内部的方法,可以通过handler重写Proxy上面的内部的方法,外界每次通过Proxy访问target对象属性的时候会经过handler里面每一个方法,因此可以通过重写handler对象中的一些方法来做一些拦截
