需要的几个Object对象的方法
开始之前先介绍几个Object对象的方法。
- Object.create(原型[ , 属性配置 ]):创建对象并指定对象的原型;
let web = Object.create(user, {
name: {
value: "wed of name", // name的值
configurable: true, // 可配置属性
enumerable: true, // 该属性可遍历
writable: true // 该属性可更改值
},
}
- Object.getOwnPropertyDescriptors(object):查看object对象的属性描述信息;
类似下图的信息。
- Object.setPrototypeOf(target, proto) :设置target对象的原型为proto;
- Object.getPrototypeOf(target):获取target对象的原型;
// 把js的原型设置为user
Object.setPrototypeOf(js, user);
console.log(Object.getPrototypeOf(js));
- Object.defineProperty(obj , 属性 , 配置对象) :
Object.defineProperty(obj,atrr,cfigObj)
obj :参数是设置属性的对象
atrr:是指设置的属性名
cfigObj:是指设置属性的配置属性,四个属性
1)value : 属性值
2)enumerable:是否可遍历
3)writable:是否可更改
4)configurable:是否可配置,简单理解就是能不能对属性进行增、删、该、以及是否可以把属性修改为访问器属性。
- Object.assign(target,…source):获取source对象的所以属性和方法添加到target对象中;
圣杯模式
我的这篇文章——圣杯模式详细的写了整个过程,再次学习Object对象时,发现一个问题,就是子类继承了父类之后,实例化出来的对象的原型中constructor属性是可遍历的,假如我们使用 for of语句遍历对象,则会把 constructor 属性遍历出来,这样就不好了,这里更改一下 constructor 属性的设置。
采取Object.defineProperty() 方法定义属性。
// 继承封装函数
function extend(Sub, Super) {
// 圣杯模式继承
let F = function () {};
F.prototype = Super.prototype;
Sub.prototype = new F();
Sub.prototype.uber = Super.prototype;
// Son.prototype.constructor = Son;
// 存在问题,constructor是可遍历的,我们需要设置constructor不可遍历
Object.defineProperty(Sub.prototype, "constructor", {
value: Sub,
enumerable: false, // 不可遍历
writable: true, // 可更改
configurable: true // 可配置
})
}
原型工厂
使用 Object.create() 方法设置对象的原型,最后要定义一下对象的原型中constructor属性不可遍历
function extend(Sub,Super){
// 2. 新创建一个对象 并为其指定原型为Super.prototype 原型工厂
Sub.prototype = Object.create(Super.prototype);
Object.defineProperty(Sub.prototype, "constructor", {
value: Sub,
// 存在问题,constructor是可遍历的,我们需要设置不可遍历
enumerable: false, // 不可遍历
writable: true, // 可更改
configurable: true // 可配置
})
// 需要在原型上添加方法或者属性时,
// 一定要在继承之后添加,否则会覆盖,导致新添加的属性和方法消失
}
原型的继承
简单的一句话,子类构造函数的原型的原型对象设置为父类的构造函数的原型。
函数它本身也是一个对象,构造函数同样是一个对象,所以构造函数也有自己的__proto__ 属性,指向构造函数的原型。
继承的核心思想就是让原型继承原型,而不是构造函数继承原型。
function extend(Sub,Super){
Sub.prototype.__proto__ = Super.prototype;
}
测试
函数封装完毕后,写程序测试一下。
User.prototype.run = function () {
this.show();
}
User.prototype.sayAge = function () {
console.log(this.age);
}
function User(name, age) {
this.name = name;
this.age = age;
};
extend(Vip, User);
// 重写父类的show方法
Vip.prototype.show = function () {
console.log("Vip show meothd");
}
// ...在接收参数时是聚合成数组,args接收参数组成的数组;...在使用参数时是打散数组,展开
function Vip(...args) { // 接收参数时是一个数组
// 借用父类属性
User.apply(this, args); // 在使用的时候args是数组,若需要使用每个元素,展开即可,...args
}
// 测试继承
let user1 = new User("ff", 18);
let vip = new Vip("tt", 15);
vip.run();
vip.sayAge();
// console.log(Vip.prototype.constructor); // 指回自己的构造器
// 测试constructor属性不可遍历
console.dir(Object.getOwnPropertyDescriptors(Vip.prototype));
for (const key in vip) {
console.log(key); // 没有遍历出constructor
}
解决多继承问题 —— 混合 (mixin)
多继承会导致整个代码结构很混乱,代码结构不健壮,而且可读性比较差,可以使用混合,其实混合这个概念一听比较高大上,听起来有点混乱,但是当我看到是 assign() 方法时,瞬间放松了。混合核心就是把目标对象所需要的功能或者属性添加到目标对象中,刚好assign() 方法能够实现属性的合并。
Object.assign ( ) 方法
Object.assign(targetObj,...source)
assign方法 :
参数1: 目标对象
参数2: 源对象
返回值: 目标对象
函数作用: 把一个或多个源对象中所有可枚举的所有属性的值复制到目标对象
简单理解可是属性的合并
混合的实现
定义两个构造函数,User构造出来的实例对象,有请求后台的功能、获取积分的功能、登录的功能…
如果把每个功能都写成函数,就太乱了,我们应该把每个功能封装成一个对象,需要其中的属性或方法之间调取即可,这样结构清晰,可读性高,代码显得更加健壮。
function User(name, age) {
this.name = name;
this.age = age;
};
function Vip(...args) { // 接收参数时是一个数组
// 借用父类属性
User.apply(this, args); // 在使用的时候args是数组,若需要使用每个元素,展开即可,...args
}
分别定义各个功能模块的对象
// 把各个模块功能全部变成对象,功能中的小功能变成方法
const Request = {
quest() {
return "请求后台";
}
};
const Integral = {
total() {
console.log( "获取积分");
}
};
const Login = {
login() {
console.log("跳转登录页面");
}
};
const VipPic = {
showVip() {
console.log(this.name + "是vip会员");
}
}
对象添加 功能模块对象
使得User实例化的对象能够调用各个功能模块中的方法;
Vip实例化对象可以获得vip相关信息的功能。
let user = new User("ff", 18);
// User需要 Request Integral Login中的方法
User.prototype = Object.assign(User.prototype, Request, Integral, Login);
user.total();
let vip = new Vip("tt", 18);
// Vip需要使用 VipPick对象的方法
Vip.prototype = Object.assign(Vip.prototype, VipPic);
vip.showvip();
功能模块对象之间相互添加
在获取积分时,肯定是请求后台,我们接收后台返回的数据最终获取,这时候就需要在获取积分的时候去请求后台了,换句话说就是需要在获取积分的功能块中添加请求后台的功能。
使用super关键字,实现功能与功能之间的添加使用。
两步操作:
- 目标对象的__proto__ 属性指向添加的功能对象;
- 使用super关键字调用相应的属性或方法。
const Integral = {
__proto__: Request,
// super == >this.__proto__
total() {
console.log(super.quest() + ": 获取积分");
}
};
此时再去实例化对象,调用积分的total方法时,就能调用请求后台的方法了。
let user = new User("ff", 18);
// User需要 Request Integral Login中的方法
User.prototype = Object.assign(User.prototype, Request, Integral, Login);
user.total();
继承的学习一开始会有点绕,多看几遍就可以。
加油!!!