开幕
领导:“赶紧去找个对象啊”,
我: “找不到啊”,
领导:“new一个”,
我: “没有原型,new出来的也是空对象啊”,
领导: “。。。”
做为一个程序员,每天都在面向“对象”编程,可是却总是找不到对象,想想也是心塞啊。为了解决找不到“对象”问题,这次我们就来谈一下对象吧(哈哈哈哈,有点标题党了,内容如果有误或者理解不够的地方,欢迎评论区指正)
第一场 对象的创建
对象不会凭空而来,但会悄悄离开(垃圾回收机制)。在使用对象之前,首先你要有对象(这不是废话吗)。如果你没有对象,却硬要使用对象,那么就不要怪别人用小拳拳捶你胸口了。
JavaScript中有几种创建对象的方法,大家可以根据平时相处结果自行选择
1、对象直接量创建对象
let obj1 = {};
let obj2 = {a: 'one', b: 'two'};
复制代码
2、new 关键字创建
function Person (name, age) {
if (this instanceof Person) {
this.name = name;
this.age = age;
} else {
return new Person(name,age);
}
}
Person.prototype.getName = function () {
return this.name;
}
Person.prototype.getAge = function () {
return this.age;
}
let stu = new Person('xiaoming', 18);
stu.getName();
stu.getAge();
复制代码
3、Object.create()。可以使用Object.create(null)创建不继承任何属性和方法的对象。
第二场 对象的继承
有了“对象”之后,是不是要考虑一下传宗接代,啊呸,继承的问题呢?
继承是对象的三大特性之一,谈到对象就不能不谈继承。那么对象是靠什么机制来实现继承的呢?
假设要查询对象Obj的属性X,如果Obj中不存在X,那么将会继续在Obj的原型对象中查找该属性。如果原型对象也没有改属性,单改原型对象也有原型,那么继续在这个原型对象的原型中执行查询,直到找到X或者查找到一个原型是null的对象为止。可以看到,对象的原型属性构成了一个“链”,通过这个“链”可以实现对象的继承。
ES6之前,JavaScript并没有提供对象继承的接口,但我们知道对象是通过原型链来实现继承的,那么我们就可以实现自己的继承方法了function inherit (source) {
if (source === null) {
throw TypeError();
}
if (Object.create) {
// 以source为原型创建对象实现继承
return Object.create(source);
} else {
// 兼容ES5以前的版本
let type = typeof source;
if (type !== 'object' && type !== 'function') {
throw TypeError();
}
// f的原型指向source实现继承
function f() {};
f.prototype = source;
return new f();
}
}
复制代码
ES6中引入了class的概念,而使用 extends 关键字可以很方便的实现class之间的继承
class Parent {
constructor (name, age) {
this.name = name;
this.age = age;
}
sayHello () {
console.log('hello');
}
}
class Child extends Parent {
constructor (name, age, sex) {
super(name, age);
this.sex = sex;
}
getName () {
return this.name;
}
}
复制代码
在控制台的执行结果如下,可以看到Child子类继承了Parent父类的属性和方法
第三场 对象的复制
虽然“对象”得到了延续(继承),但如果我们想要更多的“对象”那该怎么办呢?那么多“对象”,你会不会认错他们呢?他们之间会不会互相纠缠不清呢?
JavaScript中的数据类型可以分成两大类:基本类型和引用类型。基本类型包括:undefined,null,boolean,string,number。基本类型的值是保持在栈中的,复制前后其在栈中的结果可用下列示意图表示
引用类型的值是保存在堆内存中的。与其他语言不同,JavaScript不允许直接访问堆内存中的位置,也就是说不能直接操作对象的内存空间。在操作对象时,实际上是在操作对象的引用。1、浅复制:只会将对象的各个属性进行依次复制,并不会进行递归复制,而JavaScript存储对象都是存对象引用(内存地址)的,所以浅复制会导致对象的引用指向同一块堆内存地址
function shallowCopy (source) {
let target = Array.isArray(source) ? [] : {};
// 非数组或对象类型的直接返回原数据即可
if (typeof source !== 'object') {
return source;
}
// 遍历复制对象属性
for (key in source) {
if (source.hasOwnProperty(key)) {
target[key] = source[key];
}
}
return target;
}
复制代码
2、深复制:它不仅将原对象的各个属性逐个复制出去,而且将原对象各个属性所包含的对象也依次采用深复制的方法递归复制到新对象上。
function deepCopy (source) {
let target = Array.isArray(source) ? [] : {};
// 非数组或对象类型的直接返回原数据即可
if (typeof source !== 'object') {
return source;
}
// 递归遍历复制对象属性
for (key in source) {
if (source.hasOwnProperty(key)) {
if (typeof source[key] === 'object') {
// 递归实现深复制
target[key] = deepCopy(source[key]);
} else {
target[key] = source[key];
}
}
}
return target;
}
复制代码
第四场 对象的扩展
经过一系列深浅复制后,你拥有的“对象”越来越多。但是,在这个物竞天择,适者生存的环境下(内存空间有限),你是否希望你的对象能够拥有所有对象的优点,集真善美与一体呢?方法当然是有的。
ES6中的Object.assign可以将源对象中可枚举的自有属性复制到目标对象中,并返回一下新的对象。但是该接口没有实现对象的深复制
let o1 = {};
let o2 = {a: 'a', b: 'b', c: {c: 'c'}};
Object.assign(o1, o2);
复制代码
在控制台中执行上面的代码,可以发现Object.assign对于对象的复制只是浅复制而已
有了上一节对象深复制的基础及对Object.assign原理的理解,我们就可以撸起袖子加油干,写一个自己的对象扩展方法了。
function extend (target, ...source) {
// 将source中可枚举的自有属性复制到target中, 相同属性名对应的值会被覆盖
for (let value of source) {
for (key in value) {
if (value.hasOwnProperty(key)) {
if (typeof value[key] === 'object') {
// 对象类型深复制
target[key] = deepCopy(value[key]);
} else {
target[key] = value[key];
}
}
}
}
return target;
}
复制代码
在控制台上执行下面代码,extend函数在实现Object.assign的功能的同时,也实现了对对象的深复制
第五场 对象的代理
经过扩展后,你的“对象”已经汲取了天地之精华,成为内存中的武林盟主。为了应对武林中错综复杂的人际关系,你的“对象”急需一个代理来全权处理这些琐事。那么,到哪去找代理人呢?别急,车到山前必有路,有路必有代理商。
ES6中新增的Proxy对象,为我们提供了用于修改某些默认Object方法的行为。可以将其理解为在目标对象前的一个“拦截器”,所有对该目标对象的操作都会经过拦截器处理。因此,它提供了一种机制,可以对外界的访问进行过滤和改写。
let proxy = new Proxy(target , handler);
复制代码
Proxy支持的拦截操作如下
handler = {
apply: function (target, object, args) {
// 拦截Proxy实例作为函数调用的操作,比如proxy(...args), proxy.call(object, ...args),
},
construct: function (target, args,proxy) {
// 拦截Proxy实例作为构造函数调用的操作
},
get: function (target, propKey, receiver) {
// 拦截对象属性的读取。最后一个参数可选,当target设置了propKey属性的get函数时,
// receiver对象会绑定get还是的this对象
},
set: function (target, value, receiver) {
// 拦截对象属性的设置
},
getPrototypeOf: function (target) {
// 拦截Object.getPrototypeOf(target), 返回一个对象
},
setPrototypeOf: function (target, proto) {
// 拦截Object.setPrototypeOf(target, proto), 返回一个布尔值
},
has: function (target, propKey) {
// 拦截propKey in target操作,返回一个布尔值
},
hasOwn: function (target, propKey) {
// 拦截Object.hasOwnProperty(target, propKey), 返回一个布尔值
},
ownKeys: function (target) {
// 拦截Object.getOwnPropertyNames(target), Object.getOwnPropertySymbols(target), Object.keys(target)
// 返回一个数组,该方法返回对象所有的自身属性。
},
getOwnPropertyDescriptor: function (target, propKey) {
// 拦截Object.getOwnPropertyDescriptor(target, propKey), 返回属性的描述对象
},
enumerate: function (target) {
// 拦截 for(let key in target) 或者 Object.keys()操作, 返回一个遍历器
},
deleteProperty: function (target, propKey) {
// 拦截delete target[propKey]操作,返回一个布尔值
},
defineProperty: function (target, propKey, propDesc) {
// 拦截Object.defineProperty(target, propKey, propDesc), Object.defineProperties(target, propKey, propDesc),
// 返回一个布尔值
},
isExtensible: function (target) {
// 拦截Object.isExtensible(target), 返回一个布尔值
},
preventExtensions: function (target) {
// 拦截Object.preventExtensions(target), 返回一个布尔值
}
}
复制代码
剧终
至此,我们已经交代了对象如何而来(创建),怎么传宗接代(继承),怎么野蛮生长(复制),怎么打野升级(对象扩展),怎么寻找代理商,那么你是否知道了对象如何而来,怎么传宗接代,怎么野蛮生长,怎么打野升级,怎么寻找代理呢?知道了就赶紧找对象去吧。