首页 > 其他分享 >JS基础 原型与继承

JS基础 原型与继承

时间:2023-03-24 13:00:53浏览次数:39  
标签:function console name 继承 JS 原型 User prototype log


阅读目录

  • 原型基础
  • 原型对象
  • 使用数组原型对象的 concat 方法完成连接操作
  • 默认情况下创建的对象都有原型。
  • 以下 x、y 的原型都为元对象 Object,即JS中的根对象
  • 创建一个极简对象(纯数据字典对象)没有原型(原型为 `null`)
  • 函数拥有多个原型,prototype 用于实例对象使用,`__proto__` 用于函数对象使用
  • 原型关系分析,与方法继承的示例
  • 使用构造函数创建对象的原型体现
  • `constructor` 存在于 `prototype` 原型中,用于指向构建函数的引用。
  • 使用对象的 `constructor` 创建对象
  • 原型链
  • 原型检测
  • 属性遍历
  • 借用原型 *
  • this 不受原型继承影响
  • 原型总结
  • prototype
  • Object.create
  • `__proto__`
  • 使用建议
  • 构造函数
  • 原型属性
  • constructor
  • 使用优化
  • 通过原型定义方法不会产生函数复制
  • 演示使用原型为多个实例共享属性
  • 体验继承
  • 继承与多态
  • 继承实现
  • 构造函数
  • 方法重写
  • 多态
  • 深挖继承
  • 构造函数
  • 原型工厂
  • 对象工厂 *
  • Mixin模式
  • 实例操作

原型基础

原型对象

每个对象都有一个原型 prototype 对象,通过函数创建的对象也将拥有这个原型对象。
原型是一个指向对象的指针。

  1. 可以将原型理解为对象的父亲,对象从原型对象继承来属性
  2. 原型就是对象除了是某个对象的父母外没有什么特别之处
  3. 所有函数的原型默认是 Object 的实例,所以可以使用 toString / toValues / isPrototypeOf等方法的原因。
  4. 使用原型对象为多个对象共享属性或方法
  5. 如果对象本身不存在属性或方法将到原型上查找
  6. 使用原型可以解决,通过构建函数创建对象时复制多个函数造成的内存占用问题
  7. 原型包含 constructor 属性,指向构造函数
  8. 对象包含 __proto__ 指向他的原型对象

使用数组原型对象的 concat 方法完成连接操作

下例使用的就是数组原型对象的 concat 方法完成的连接操作。

let hd = ["a"];
console.log(hd.concat("b"));
console.log(hd);

JS基础 原型与继承_前端

默认情况下创建的对象都有原型。

let hd = { name: "wgchen" };
console.log(hd);

JS基础 原型与继承_前端_02

以下 x、y 的原型都为元对象 Object,即JS中的根对象

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);

JS基础 原型与继承_构造函数_03

函数拥有多个原型,prototype 用于实例对象使用,__proto__ 用于函数对象使用

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__);

JS基础 原型与继承_构造函数_04

原型关系分析,与方法继承的示例

JS基础 原型与继承_User_05

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();

JS基础 原型与继承_原型模式_06

使用构造函数创建对象的原型体现

  1. 构造函数拥有原型
  2. 创建对象时构造函数把原型赋予对象

JS基础 原型与继承_User_07

function User() {}
let xj = new User();
console.log(xj.__proto__ == User.prototype); // true

下面使用数组会产生多级继承即原型链

JS基础 原型与继承_User_08

let hd = [];
console.log(hd);
console.log(hd.__proto__ == Array.prototype);

let str = "";
console.log(str.__proto__ == String.prototype);

下面使用 setPrototypeOfgetPrototypeOf 获取与设置原型

let hd = {};
let parent = { name: "parent" };
Object.setPrototypeOf(hd, parent);

console.log(hd);
console.log(Object.getPrototypeOf(hd));

JS基础 原型与继承_javascript_09


使用自定义构造函数创建的对象的原型体现

JS基础 原型与继承_原型模式_10

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);

JS基础 原型与继承_原型模式_11

原型链

通过引用类型的原型,继承另一个引用类型的属性与方法,这就是实现继承的步骤。

JS基础 原型与继承_构造函数_12


使用 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);
}

JS基础 原型与继承_javascript_13


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
  }
}

借用原型 *

使用 callapply 可以借用其他原型方法完成功能。

下面的 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>

JS基础 原型与继承_原型模式_14

this 不受原型继承影响

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__ 不是对象属性,理解为 prototypeget/set 实现,他是一个非标准定义。
  • __proto__ 内部使用 get/set 控制值,所以只允许对象或 null
  • 建议使用 Object.setPrototypeOfObject.getProttoeypOf 替代 __proto__

下面修改对象的 __proto__ 是不会成功的,因为 _proto__ 内部使用 get/set 控制值,所以只允许对象或 null

let xj = {};
xj.__proto__ = "willem";
console.log(xj);

JS基础 原型与继承_前端_15


下面定义的 __proto__ 就会成功,因为这是一个极简对象,没有原型对象所以不会影响__proto__ 赋值。

let hd = Object.create(null);
hd.__proto__ = "wgchen";
console.log(hd);

JS基础 原型与继承_原型模式_16

下面通过改变对象的 __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);

JS基础 原型与继承_构造函数_17


构造函数中的 __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);

JS基础 原型与继承_javascript_18


可以使用 __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

使用建议

通过前介绍我们知道可以使用多种方式设置原型,下面是按时间顺序的排列

  1. prototype 构造函数的原型属性
  2. Object.create 创建对象时指定原型
  3. __proto__ 声明自定义的非标准属性设置原型,解决之前通过 Object.create 定义原型,而没提供获取方法
  4. Object.setPrototypeOf 设置对象原型

这几种方式都可以管理原型,一般以我个人情况来讲使用 prototype 更改构造函数原型,使用 Object.setPrototypeOfObject.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); //willem

constructor

构造函数的原型中包含属性 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'}

JS基础 原型与继承_User_19


正确的做法是要保证原型中的 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);

JS基础 原型与继承_User_20

使用优化

使用构造函数会产生函数复制造成内存占用,及函数不能共享的问题。

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__)

JS基础 原型与继承_构造函数_21

体验继承

下面为 Stu 更改了原型为 User 的实例对象,lisi 是通过构造函数 Stu 创建的实例对象

  1. lisi 在执行 getName 方法时会从自身并向上查找原型,这就是原型链特性
  2. 当然如果把 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_22

继承与多态

当对象中没使用的属性时,JS会从原型上获取这就是继承在JavaScript中的实现。

继承实现

下面使用 Object.create 创建对象,做为Admin、Member的原型对象来实现继承。

JS基础 原型与继承_前端_23

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());

JS基础 原型与继承_User_24


不能使用以下方式操作,因为这样会改变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);

JS基础 原型与继承_原型模式_25


因为有时根据得到的对象获取构造函数,然后再创建新对象所以需要保证构造函数存在,但如果直接设置了 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();
}

JS基础 原型与继承_前端_26

深挖继承

继承是为了复用代码,继承的本质是将原型指向到另一个对象。

构造函数

我们希望调用父类构造函数完成对象的属性初始化,但像下面这样使用是不会成功的。因为此时 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

JS基础 原型与继承_构造函数_27


解决上面的问题是使用 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

JS基础 原型与继承_原型模式_28

原型工厂

原型工厂是将继承的过程封装,使用继承业务简单化。

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

JS基础 原型与继承_javascript_29


JS基础 原型与继承_javascript_30

对象工厂 *

在原型继承基础上,将对象的生成使用函数完成,并在函数内部为对象添加属性或方法。

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基础 原型与继承_构造函数_31

Mixin模式

JS不能实现多继承,如果要使用多个类的方法时可以使用 mixin 混合模式来完成。

  1. mixin 类是一个包含许多供其它类使用的方法的类
  2. 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 制作选项卡

JS基础 原型与继承_原型模式_32

<!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>


标签:function,console,name,继承,JS,原型,User,prototype,log
From: https://blog.51cto.com/u_13571520/6147118

相关文章

  • requireJS 源码(二) data-main 的加载实现
    requireJS源码(二)data-main的加载实现(一)requireJs的整体结构:requireJS源码前192行,是一些变量的声明,工具函数的实现以及对三个全局变量(requirejs,require,def......
  • requireJS 源码(一) require() 为何可以全局使用
    requireJS源码(一)require()为何可以全局使用requireJS源码加注释总共不到2100行。我看的requireJs版本是2.19。 总体结构如下。......
  • requireJS 源码(三) data-main 的加载实现
    requireJS源码(三)data-main的加载实现(一)入口通过data-main去加载JS模块,是通过  req(cfg) 入口去进行处理的。为了跟踪,你可以在此加断点进行调试跟......
  • Vue.js 路由简介
    路由理解:一个路由(route)就是一组映射关系(key-value),多个路由需要路由器(router)进行管理。前端路由:key是路径,value是组件。......
  • js保存文件到本地
    使用原生方法保存文件到本地基本流程确定要保存的文本、保存格式及保存文件类型;根据保存格式生成url链接,设置文件的下载地址;创建一个a标签(即a标签指向的就是我们要......
  • 前端js RSA jsrsasign加密、解密、加签、验签
     jsrsasign(RSA-SignJavaScript库)是一个免费的开源加密库,支持RSA/RSAPSS/ECDSA/DSA签名/验证,ASN.1,PKCS#1/5/8私钥/公钥,X.509证书,纯JavaScript中的CRL,OCSP,CMSSigned......
  • fabricjs如何导入echarts
    Fabric.js是一个强大的HTML5canvas库,而ECharts是一个基于JavaScript的图表库。要将ECharts导入到Fabric.js中,您需要先将ECharts渲染到一个离屏canvas,然后将......
  • js 替换逻辑
     letstr1=JSON.stringify(data).replace(/{/g,"[")   letstr2=str1.replace(/}/g,"]")   letstr3=str2.replace(/"/g,"")   letstr4......
  • 使用html2canvas+jspdf将页面转为pdf并下载
    1、安装html2canvas和jspdfnpminstallhtml2canvasnpminstalljspdf2、新建文件htmlToPdf.ts//导出页面为PDF格式importhtml2Canvasfrom'html2canvas'impor......
  • Vue.js Vuex实现求和案例
    视频Vuex版本componentsCount.vue<template> <div> <!--模板里能看见vc上所有东西--> <h1>当前求和为:{{$store.state.sum}}</h1> <selectv-model.number="n......