开始之前先介绍几个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 }多继承会导致整个代码结构很混乱,代码结构不健壮,而且可读性比较差,可以使用混合,其实混合这个概念一听比较高大上,听起来有点混乱,但是当我看到是 assign() 方法时,瞬间放松了。混合核心就是把目标对象所需要的功能或者属性添加到目标对象中,刚好assign() 方法能够实现属性的合并。
定义两个构造函数,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();继承的学习一开始会有点绕,多看几遍就可以。 加油!!!