- 原型基础
-
- 原型对象
-
- 使用数组原型对象的 concat 方法完成连接操作
- 默认情况下创建的对象都有原型。
- 以下 x、y 的原型都为元对象 Object,即JS中的根对象
- 创建一个极简对象(纯数据字典对象)没有原型(原型为 `null`)
- 函数拥有多个原型,prototype 用于实例对象使用,`__proto__` 用于函数对象使用
- 原型关系分析,与方法继承的示例
- 使用构造函数创建对象的原型体现
- `constructor` 存在于 `prototype` 原型中,用于指向构建函数的引用。
- 使用对象的 `constructor` 创建对象
- 原型链
- 原型检测
- 属性遍历
- 借用原型 *
- this 不受原型继承影响
- 原型总结
-
- prototype
- Object.create
- `__proto__`
- 使用建议
- 构造函数
-
- 原型属性
- constructor
- 使用优化
-
- 通过原型定义方法不会产生函数复制
- 演示使用原型为多个实例共享属性
- 体验继承
- 继承与多态
-
- 继承实现
- 构造函数
- 方法重写
- 多态
- 深挖继承
-
- 构造函数
- 原型工厂
- 对象工厂 *
- Mixin模式
- 实例操作
每个对象都有一个原型prototype对象,通过函数创建的对象也将拥有这个原型对象。 原型是一个指向对象的指针。
- 可以将原型理解为对象的父亲,对象从原型对象继承来属性
- 原型就是对象除了是某个对象的父母外没有什么特别之处
- 所有函数的原型默认是 Object 的实例,所以可以使用toString / toValues / isPrototypeOf等方法的原因。
- 使用原型对象为多个对象共享属性或方法
- 如果对象本身不存在属性或方法将到原型上查找
- 使用原型可以解决,通过构建函数创建对象时复制多个函数造成的内存占用问题
- 原型包含constructor属性,指向构造函数
- 对象包含__proto__指向他的原型对象
下例使用的就是数组原型对象的concat方法完成的连接操作。
let hd = ["a"]; console.log(hd.concat("b")); console.log(hd);
let hd = { name: "wgchen" }; console.log(hd);
let x = {}; let y = {}; console.log(Object.getPrototypeOf(x) == Object.getPrototypeOf(y)); //true // Object.getPrototypeOf()方法返回指定对象的原型(即内部属性的值)。[[Prototype]]创建一个极简对象(纯数据字典对象)没有原型(原型为null)
我们也可以创建一个极简对象(纯数据字典对象)没有原型(原型为null)
// hasOwnProperty() 方法会返回一个布尔值,指示对象自身属性中是否具有指定的属性(也就是,是否有指定的键)。 let hd = { name: 3 }; console.log(hd.hasOwnProperty("name")); // true let xj = Object.create(null, { name: { value: "wgchen" } }); console.log(xj.hasOwnProperty("name")); // Uncaught TypeError: xj.hasOwnProperty is not a function //Object.keys是静态方法,不是原型方法所以是可以使用的 console.log(Object.keys(xj));
console.log(xj);
function User() {} User.__proto__.view = function() { console.log("User function view method"); }; User.view(); User.prototype.show = function() { console.log("wgchen"); }; let hd = new User(); hd.show(); console.log(User.prototype == hd.__proto__);
let hd = new Object(); hd.name = "wgchen"; Object.prototype.show = function() { console.log("blog"); }; hd.show(); function User() {} let xj = new User(); xj.show(); User.show();
- 构造函数拥有原型
- 创建对象时构造函数把原型赋予对象
function User() {} let xj = new User(); console.log(xj.__proto__ == User.prototype); // true
下面使用数组会产生多级继承即原型链
let hd = []; console.log(hd); console.log(hd.__proto__ == Array.prototype); let str = ""; console.log(str.__proto__ == String.prototype);
下面使用setPrototypeOf与getPrototypeOf获取与设置原型
let hd = {}; let parent = { name: "parent" }; Object.setPrototypeOf(hd, parent); console.log(hd); console.log(Object.getPrototypeOf(hd));
使用自定义构造函数创建的对象的原型体现
function User() {} let hd = new User(); console.log(hd);constructor存在于prototype原型中,用于指向构建函数的引用。
function hd() { this.show = function() { return "show method"; }; } const obj = new hd(); console.log(obj instanceof hd); //true const obj2 = new obj.constructor(); console.dir(obj2.show()); //show method使用对象的constructor创建对象
function User(name, age) { this.name = name; this.age = age; } function createByObject(obj, ...args) { const constructor = Object.getPrototypeOf(obj).constructor; return new constructor(...args); } let hd = new User("wgchen"); let xj = createByObject(hd, "willem", 12); console.log(xj);
通过引用类型的原型,继承另一个引用类型的属性与方法,这就是实现继承的步骤。
使用Object.setPrototypeOf可设置对象的原型,下面的示例中继承关系为obj>hd>cms。
Object.getPrototypeOf用于获取一个对象的原型。
let obj = { name: "wgchen" }; let hd = { web: "blog" }; let cms = { soft: "willem" }; //让obj继承hd,即设置obj的原型为hd Object.setPrototypeOf(obj, hd); Object.setPrototypeOf(hd, cms); console.log(obj.web); // blog console.log(Object.getPrototypeOf(hd) == cms); //true原型检测
instanceof检测构造函数的prototype属性是否出现在某个实例对象的原型链上。
function A() {} function B() {} function C() {} const c = new C(); B.prototype = c; const b = new B(); A.prototype = b; const a = new A(); console.dir(a instanceof A); //true console.dir(a instanceof B); //true console.dir(a instanceof C); //true console.dir(b instanceof C); //true console.dir(c instanceof B); //false
使用isPrototypeOf检测一个对象是否在另一个对象的原型链中
const a = {}; const b = {}; const c = {}; Object.setPrototypeOf(a, b); Object.setPrototypeOf(b, c); console.log(b.isPrototypeOf(a)); //true console.log(c.isPrototypeOf(a)); //true console.log(c.isPrototypeOf(b)); //true属性遍历
使用in检测原型链上是否存在属性,使用hasOwnProperty只检测当前对象。
let a = { url: "blog" }; let b = { name: "wgchen" }; Object.setPrototypeOf(a, b); console.log("name" in a); // true console.log(a.hasOwnProperty("name")); // false console.log(a.hasOwnProperty("url")); // true
使用for/in遍历时同时会遍历原型上的属性如下例
let hd = { name: "wgchen" }; let xj = Object.create(hd, { url: { value: "blog", /* 属性四种特性之一enumerable: 对象属性是否可通过for-in循环,或Object.keys() 读取 */ enumerable: true } }); for (const key in xj) { console.log(key); }
hasOwnProperty方法判断对象是否存在属性,而不会查找原型。
所以如果只想遍历对象属性使用以下代码
let hd = { name: "wgchen" }; let xj = Object.create(hd, { url: { value: "blog", enumerable: true } }); for (const key in xj) { if (xj.hasOwnProperty(key)) { console.log(key); // url } }借用原型 *
使用call或apply可以借用其他原型方法完成功能。
下面的 xj 对象不能使用 max 方法,但可以借用 hd 对象的原型方法。
let hd = { data: [1, 2, 3, 4, 5] }; Object.setPrototypeOf(hd, { max: function() { return this.data.sort((a, b) => b - a)[0]; } }); console.log(hd.max()); // 5 let xj = { lessons: { js: 100, php: 78, node: 78, linux: 125 }, get data() { return Object.values(this.lessons); } }; console.log(hd.__proto__.max.apply(xj));// 125
上例中如果方法可以传参,那就可以不在xj对象中定义get方法了。
let hd = { data: [1, 2, 3, 4, 5] }; Object.setPrototypeOf(hd, { max: function(data) { return data.sort((a, b) => b - a)[0]; } }); console.log(hd.max(hd.data)); // 5 let xj = { lessons: { js: 100, php: 78, node: 78, linux: 125 } }; console.log(hd.__proto__.max.call(xj, Object.values(xj.lessons))); // 125
因为Math.max就是获取最大值的方法,所以代码可以再次优化
let hd = { data: [1, 2, 3, 4, 5] }; console.log(Math.max.apply(null, Object.values(hd.data))); // 5 let xj = { lessons: { js: 100, php: 78, node: 78, linux: 125 } }; console.log(Math.max.apply(xj, Object.values(xj.lessons))); // 125
下面是获取设置了class属性的按钮,但DOM节点不能直接使用数组的filter等方法,但借用数组的原型方法就可以操作了。
<body> <button message="wgchen" class="red">wgchen but</button> <button message="blog">blog but</button> </body> <script> let btns = document.querySelectorAll("button"); btns = Array.prototype.filter.call(btns, item => { return item.hasAttribute("class"); }); </script>
this 指向调用属性时使用的是对象。
let hd = { name: "wgchen" }; let ycc = { name: "willem", show() { return this.name; } }; hd.__proto__ = ycc; console.log(hd.show()); // wgchen原型总结 prototype
函数也是对象,也有原型,函数有prototype属性指向他的原型
为构造函数设置的原型指,当使用构造函数创建对象时把这个原型赋予给这个对象
function User(name) { this.name = name; } User.prototype = { show() { return this.name; } }; let xj = new User("wgchen"); console.log(xj.show()); // wgchen
函数默认prototype指包含一个属性constructor的对象,constructor指向当前构造函数
function User(name) { this.name = name; } let xj = new User("wgchen"); // User {name: 'wgchen'} console.log(xj); console.log(User.prototype.constructor == User); //true console.log(xj.__proto__ == User.prototype); //true let lisi = new xj.constructor("李四"); console.log(lisi.__proto__ == xj.__proto__); //true
原型中保存引用类型会造成对象共享属性,所以一般只会在原型中定义方法。
function User() {} User.prototype = { lessons: ["JS", "VUE"] }; const lisi = new User(); const wangwu = new User(); lisi.lessons.push("CSS"); console.log(lisi.lessons); //["JS", "VUE", "CSS"] console.log(wangwu.lessons); //["JS", "VUE", "CSS"]
为 Object 原型对象添加方法,将影响所有函数
<body> <button onclick="this.hide()">wgchen</button> </body> <script> Object.prototype.hide = function() { this.style.display = "none"; }; </script>
了解了原型后可以为系统对象添加方法,比如为字符串添加了一截断函数。
不能将系统对象的原型直接赋值
String.prototype.truncate = function (len = 5) { return this.length <= len ? this : this.substr(0, len) + '...'; } console.log('人不是机器都会有累的时候'.truncate(3)); //人不是...Object.create
使用Object.create创建一个新对象时,使用现有对象做为新对象的原型对象。
使用Object.create 设置对象原型
let user = { show() { return this.name; } }; let hd = Object.create(user); hd.name = "wgchen"; console.log(hd.show()); // wgchen
在设置时使用第二个参数设置新对象的属性
let user = { show() { return this.name; } }; let hd = Object.create(user, { name: { value: "wgchen" } }); console.log(hd); // {name: 'wgchen'}__proto__
在实例化对象上存在__proto__记录了原型,所以可以通过对象访问到原型的属性或方法。
- __proto__不是对象属性,理解为prototype的get/set实现,他是一个非标准定义。
- __proto__内部使用get/set控制值,所以只允许对象或null
- 建议使用Object.setPrototypeOf与Object.getProttoeypOf替代__proto__
下面修改对象的__proto__是不会成功的,因为_proto__内部使用get/set控制值,所以只允许对象或null
let xj = {}; xj.__proto__ = "willem"; console.log(xj);
下面定义的__proto__就会成功,因为这是一个极简对象,没有原型对象所以不会影响__proto__赋值。
let hd = Object.create(null); hd.__proto__ = "wgchen"; console.log(hd);
下面通过改变对象的__proto__原型对象来实现继承,继承可以实现多层,
let hd = { name: "wgchen" }; let ycc = { show() { return this.name; } }; let xj = { handle() { return `用户: ${this.name}`; } }; ycc.__proto__ = xj; hd.__proto__ = ycc; console.log(hd.show()); console.log(hd.handle()); console.log(hd);
构造函数中的__proto__使用
function User(name, age) { this.name = name; this.age = age; } User.prototype.show = function () { return `姓名:${this.name},年龄:${this.age}`; }; let lisi = new User('李四', 12); let xiaoming = new User('小明', 32); console.log(lisi.__proto__ == User.prototype); //true
console.log(lisi); console.log(xiaoming);
可以使用__proto__或Object.setPrototypeOf设置对象的原型,
使用Object.getProttoeypOf获取对象原型。
function Person() { this.getName = function() { return this.name; }; } function User(name, age) { this.name = name; this.age = age; } let lisi = new User("李四", 12); Object.setPrototypeOf(lisi, new Person()); console.log(lisi.getName()); //李四
对象设置属性,只是修改对象属性并不会修改原型属性,使用hasOwnProperty判断对象本身是否含有属性并不会检测原型。
function User() {} const lisi = new User(); const wangwu = new User(); lisi.name = "小明"; console.log(lisi.name); // 小明 console.log(lisi.hasOwnProperty("name")); // true //修改原型属性后 lisi.__proto__.name = "张三"; console.log(wangwu.name); // 张三 //删除对象属性后 delete lisi.name; console.log(lisi.hasOwnProperty("name")); // false console.log(lisi.name); // 张三
使用in会检测原型与对象,而hasOwnProperty只检测对象,所以结合后可判断属性是否在原型中。
function User() { } User.prototype.name = "wgchen"; const lisi = new User(); //in会在原型中检测 console.log("name" in lisi); // true //hasOwnProperty 检测对象属性 console.log(lisi.hasOwnProperty("name")); // false使用建议
通过前介绍我们知道可以使用多种方式设置原型,下面是按时间顺序的排列
- prototype构造函数的原型属性
- Object.create创建对象时指定原型
- __proto__声明自定义的非标准属性设置原型,解决之前通过Object.create定义原型,而没提供获取方法
- Object.setPrototypeOf设置对象原型
这几种方式都可以管理原型,一般以我个人情况来讲使用 prototype 更改构造函数原型,使用Object.setPrototypeOf与Object.getPrototypeOf获取或设置原型。
构造函数 原型属性构造函数在被new时把构造函数的原型(prototype)赋值给新对象。 如果对象中存在属性将使用对象属性,不再原型上查找方法。
构造函数只会产生一个原型对象
function hd() { this.show = function() { return "show in object"; }; } hd.prototype.show = function() { return "show in prototype"; }; const obj = new hd(); console.log(obj.show()); // show in object
对象的原型引用,构造函数的原型对象,是在创建对象时确定的,当构造函数原型对象改变时会影响后面的实例对象。
function hd() {} hd.prototype.name = "wgcms"; const obj1 = new hd(); console.log(obj1.name); //wgcms hd.prototype = { name: "willem" }; const obj2 = new hd(); console.dir(obj2.name); //willemconstructor
构造函数的原型中包含属性 constructor 指向该构造函数,以下代码说明了这一点
function User(name) { this.name = name; } let hd = new User("wgchen"); let xj = new hd.constructor("willem"); console.log(xj); // User {name: 'willem'}
以下代码直接设置了构造函数的原型将造成 constructor 丢失
function User(name) { this.name = name; } User.prototype = { show: function() {} }; let hd = new User("wgchen"); let xj = new hd.constructor("willem"); console.log(xj); // String {'willem'}
正确的做法是要保证原型中的 constructor 指向构造函数
function User(name) { this.name = name; } User.prototype = { constructor: User, show: function() {} }; let hd = new User("wgchen"); let xj = new hd.constructor("willem"); console.log(xj);
使用构造函数会产生函数复制造成内存占用,及函数不能共享的问题。
function User(name) { this.name = name; this.get = function() { return this.name; }; } let lisi = new User("小明"); let wangwu = new User("王五"); console.log(lisi.get == wangwu.get); //false通过原型定义方法不会产生函数复制
function User(name) { this.name = name; } User.prototype.get = function() { return "wgchen" + this.name; }; let lisi = new User("小明"); let wangwu = new User("王五"); console.log(lisi.get == wangwu.get); //true //通过修改原型方法会影响所有对象调用,因为方法是共用的 lisi.__proto__.get = function() { return "willem" + this.name; }; console.log(lisi.get()); // willem小明 console.log(wangwu.get()); // willem王五演示使用原型为多个实例共享属性
function User(name, age) { this.name = name; this.age = age; this.show = () => { return `你在${this.site}的姓名:${this.name},年龄:${this.age}`; } } User.prototype.site = 'wgchen'; let lisi = new User('李四', 12); let xiaoming = new User('小明', 32); console.log(lisi.show()); //你在wgchen的姓名:李四,年龄:12 console.log(xiaoming.show()); //你在wgchen的姓名:小明,年龄:32
将方法定义在原型上为对象共享,解决通过构造函数创建对象函数复制的内存占用问题
function User(name) { this.name = name; } User.prototype.get = function () { return 'wgchen' + this.name; } let lisi = new User('小明'); let wangwu = new User('王五'); console.log(lisi.get == wangwu.get); //true //通过修改原型方法会影响所有对象调用,因为方法是共用的 lisi.__proto__.get = function () { return 'willem' + this.name; } console.log(lisi.get()); // willem小明 console.log(lisi.get()); // willem小明 console.log(wangwu.get()); // willem王五
使用Object.assign一次设置原型方法来复用,后面会使用这个功能实现Mixin模式
function User(name, age) { this.name = name; this.age = age; } Object.assign(User.prototype, { getName() { return this.name; }, getAge() { return this.age; } }); let lisi = new User('李四', 12); let xiaoming = new User('小明', 32); console.log(lisi.getName()); //李四 console.log(lisi.__proto__)
下面为 Stu 更改了原型为 User 的实例对象,lisi 是通过构造函数 Stu 创建的实例对象
- lisi 在执行 getName 方法时会从自身并向上查找原型,这就是原型链特性
- 当然如果把 getName 添加到对象上,就不继续追溯原型链了
"use strict"; function User() {} User.prototype.getName = function() { return this.name; }; function Stu(name) { this.name = name; } Stu.prototype = new User(); const lisi = new Stu("李四"); console.log(lisi.__proto__); console.log(lisi.getName());
当对象中没使用的属性时,JS会从原型上获取这就是继承在JavaScript中的实现。
继承实现下面使用 Object.create 创建对象,做为Admin、Member的原型对象来实现继承。
function User() {} User.prototype.getUserName = function() {}; function Admin() {} Admin.prototype = Object.create(User.prototype); Admin.prototype.role = function() {}; function Member() {} Member.prototype = Object.create(User.prototype); Member.prototype.email = function() {}; console.log(new Admin()); console.log(new Member());
不能使用以下方式操作,因为这样会改变User的原型方法,这不是继承,这是改变原型
... function User() {} User.prototype.getUserName = function() {}; function Admin() {} Admin.prototype = User.prototype; Admin.prototype.role = function() {}; ...构造函数
有多种方式通过构造函数创建对象
function Admin() {} console.log(Admin == Admin.prototype.constructor); //true let hd = new Admin.prototype.constructor(); console.log(hd); let xj = new Admin(); console.log(xj);
因为有时根据得到的对象获取构造函数,然后再创建新对象所以需要保证构造函数存在,但如果直接设置了Admin.prototype属性会造成constructor丢失,所以需要再次设置constructor值。
function User() {} function Admin() {} Admin.prototype = Object.create(User.prototype); Admin.prototype.role = function() {}; let xj = new Admin(); console.log(xj.constructor); //constructor丢失,返回User构造函数 Admin.prototype.constructor = Admin; let hd = new Admin(); console.log(hd.constructor); //正确返回Admin构造函数 //现在可以通过对象获取构造函数来创建新对象了 console.log(new hd.constructor());
使用Object.defineProperty定义来禁止遍历 constructor 属性
function User() {} function Admin(name) { this.name = name; } Admin.prototype = Object.create(User.prototype); Object.defineProperty(Admin.prototype, "constructor", { value: Admin, enumerable: false //禁止遍历 }); let hd = new Admin("wgchen"); for (const key in hd) { console.log(key); // name }
完全重写构建函数原型,只对后面应用对象有效
function User() {} const lisi = new User(); User.prototype = { show() { return "prototype show"; } }; const wangwu = new User(); console.log(wangwu.show()); // prototype show console.log(lisi.show()); // lisi.show is not a function方法重写
下而展示的是子类需要重写父类方法的技巧。
function Person() {} Person.prototype.getName = function() { console.log("parent method"); }; function User(name) {} User.prototype = Object.create(Person.prototype); User.prototype.constructor = User; User.prototype.getName = function() { //调用父级同名方法 Person.prototype.getName.call(this); console.log("child method"); }; let hd = new User(); hd.getName();
parent method child method多态
根据多种不同的形态产生不同的结果,下而会根据不同形态的对象得到了不同的结果。
function User() {} User.prototype.show = function() { console.log(this.description()); }; function Admin() {} Admin.prototype = Object.create(User.prototype); Admin.prototype.description = function() { return "管理员在此"; }; function Member() {} Member.prototype = Object.create(User.prototype); Member.prototype.description = function() { return "我是会员"; }; function Enterprise() {} Enterprise.prototype = Object.create(User.prototype); Enterprise.prototype.description = function() { return "企业帐户"; }; for (const obj of [new Admin(), new Member(), new Enterprise()]) { obj.show(); }
继承是为了复用代码,继承的本质是将原型指向到另一个对象。
构造函数我们希望调用父类构造函数完成对象的属性初始化,但像下面这样使用是不会成功的。因为此时 this 指向了window,无法为当前对象声明属性。
function User(name) { this.name = name; console.log(this);// Window } User.prototype.getUserName = function() { return this.name; }; function Admin(name) { User(name); } Admin.prototype = Object.create(User.prototype); Admin.prototype.role = function() {}; let xj = new Admin("wgchen"); console.log(xj.getUserName()); //undefined
解决上面的问题是使用call/apply为每个生成的对象设置属性
function User(name) { this.name = name; console.log(this); // Admin } User.prototype.getUserName = function() { return this.name; }; function Admin(name) { User.call(this, name); } Admin.prototype = Object.create(User.prototype); let xj = new Admin("wgchen"); console.log(xj.getUserName()); //wgchen
原型工厂是将继承的过程封装,使用继承业务简单化。
function extend(sub, sup) { sub.prototype = Object.create(sup.prototype); sub.prototype.constructor = sub; } function Access() {} function User() {} function Admin() {} function Member() {} extend(User, Access); //User继承Access extend(Admin, User); //Admin继承User extend(Member, Access); //Member继承Access Access.prototype.rules = function() {}; User.prototype.getName = function() {}; console.log(new Admin()); // 继承关系: Admin>User>Access>Object console.log(new Member()); //继承关系:Member>Access>Object
在原型继承基础上,将对象的生成使用函数完成,并在函数内部为对象添加属性或方法。
function User(name, age) { this.name = name; this.age = age; } User.prototype.show = function() { console.log(this.name, this.age); }; function Admin(name, age) { let instance = Object.create(User.prototype); User.call(instance, name, age); instance.role=function(){ console.log('admin.role'); } return instance; } let hd = Admin("wgchen", 19); hd.show(); function member(name, age) { let instance = Object.create(User.prototype); User.call(instance, name, age); return instance; } let lisi = member("李四", 28); lisi.show();
JS不能实现多继承,如果要使用多个类的方法时可以使用 mixin 混合模式来完成。
- mixin 类是一个包含许多供其它类使用的方法的类
- mixin 类不用来继承做为其它类的父类
其他语言也有类似的操作比如php 语言中可以使用 trait 完成类似操作。
下面是示例中 Admin需要使用 Request.prototype 与 Credit 的功能,因为JS 是单继承,我们不得不将无关的类连接一下,显然下面的代码实现并不佳
function extend(sub, sup) { sub.prototype = Object.create(sup.prototype); sub.prototype.constructor = sub; } function Credit() {} function Request() {} function User(name, age) { this.name = name; this.age = age; } extend(Request, Credit); extend(User, Request); Credit.prototype.total = function() { console.log("统计积分"); }; Request.prototype.ajax = function() { console.log("请求后台"); }; User.prototype.show = function() { console.log(this.name, this.age); }; function Admin(...args) { User.apply(this, args); } extend(Admin, User); let hd = new Admin("wgchen", 19); hd.show(); // wgchen 19 hd.total(); //统计积分 hd.ajax(); //请求后台
下面分拆功能使用Mixin实现多继承,使用代码结构更清晰。 只让 Admin 继承 User 原型
function extend(sub, sup) { sub.prototype = Object.create(sup.prototype); sub.prototype.constructor = sub; } function User(name, age) { this.name = name; this.age = age; } User.prototype.show = function() { console.log(this.name, this.age); }; const Credit = { total() { console.log("统计积分"); } }; const Request = { ajax() { console.log("请求后台"); } }; function Admin(...args) { User.apply(this, args); } extend(Admin, User); Object.assign(Admin.prototype, Request, Credit); let hd = new Admin("wgchen", 19); hd.show(); // wgchen 19 hd.total(); //统计积分 hd.ajax(); //请求后台
mixin 类也可以继承其他类,比如下面的 Create 类获取积分要请求后台,就需要继承 Request 来完成。
super 是在 mixin 类的原型中查找,而不是在 User 原型中
function extend(sub, sup) { sub.prototype = Object.create(sup.prototype); sub.prototype.constructor = sub; } function User(name, age) { this.name = name; this.age = age; } User.prototype.show = function() { console.log(this.name, this.age); }; const Request = { ajax() { return "请求后台"; } }; const Credit = { __proto__: Request, total() { console.log(super.ajax() + ",统计积分"); } }; function Admin(...args) { User.apply(this, args); } extend(Admin, User); Object.assign(Admin.prototype, Request, Credit); let hd = new Admin("wgchen", 19); hd.show(); // wgchen 19 hd.total(); //统计积分 hd.ajax(); //请求后台实例操作
使用call/apply制作选项卡
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>wgchen</title> </head> <style> * { padding: 0; margin: 0; } body { display: flex; justify-content: center; align-items: center; width: 100vw; height: 50vh; } main { width: 400px; flex-direction: column; position: relative; margin-right: 20px; } main nav { display: flex; height: 50px; align-items: center; } main nav a { background: #95a5a6; margin-right: px; padding: 10px 20px; border: solid 1px #333; color: #fff; text-decoration: none; } main nav a:first-of-type { background: #e67e22; } section { height: 200px; width: 100%; background: #f1c40f; position: absolute; font-size: 5em; display: none; } .hd-tab section:first-of-type { display: block; } section:nth-child(even) { background: #27ae60; } </style> <body> <main class="tab1"> <nav> <a href="javascript:;">wgchen</a> <a href="javascript:;">blog</a> </nav> <section>1</section> <section>2</section> </main> <main class="tab2"> <nav> <a href="javascript:;">willem</a> <a href="javascript:;">cms</a> </nav> <section>1</section> <section>2</section> </main> </body> <script> //继承工厂 function extend(sub, sup) { sub.prototype = Object.create(sup.prototype); sub.prototype.constructor = sub; } //动作类 function Animation() { } Animation.prototype.show = function () { this.style.display = "block"; }; //隐藏所有元素 Animation.prototype.hide = function () { this.style.display = "none"; }; //必变元素集合背景 Animation.prototype.background = function (color) { this.style.background = color; }; //选项卡类 function Tab(tab) { this.tab = tab; this.links = null; this.sections = null; } extend(Tab, Animation); Tab.prototype.run = function () { this.links = this.tab.querySelectorAll("a"); this.sections = this.tab.querySelectorAll("section"); this.bindEvent(); this.action(0); }; //绑定事件 Tab.prototype.bindEvent = function () { this.links.forEach((el, i) => { el.addEventListener("click", () => { this.reset(); this.action(i); }); }); }; //点击后触发动作 Tab.prototype.action = function (i) { this.background.call(this.links[i], "#e67e22"); this.show.call(this.sections[i]); }; //重置link与section Tab.prototype.reset = function () { this.links.forEach((el, i) => { this.background.call(el, "#95a5a6"); this.hide.call(this.sections[i]); }); }; new Tab(document.querySelector(".tab1")).run(); new Tab(document.querySelector(".tab2")).run(); </script> </html>