js原型与继承
原型与原型链
每一个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含指向原型对象的内部指针。
person.__proto__ === Person.prototype
每个原型都有一个 constructor 属性指向关联的构造函数
Person.prototype.constructor === Person
es5获取对象的原型
Object.getPrototypeOf(person) === Person.prototype
继承
原型链
- 基本思想:利用原型让一个引用类型继承另一个引用类型的数组和方法。
- 缺点:优点也是原型链继承的缺点,引用类的原型属性会被所有的实例共享,且不能向父类型传递参数,请注意是引用类型的值
function Animal(){
this.species = ['哈士奇','柯基']
}
function Dog(){
}
Dog.prototype = new Animal(); // 继承了Animal的属性和方法
var dog1 = new Dog();
dog1.species.push('萨摩耶')
var dog2 = new Dog();
console.log(dog1.species); // ["哈士奇", "柯基", "萨摩耶"]
console.log(dog2.species); // ["哈士奇", "柯基", "萨摩耶"]
借用构造函数继承
- 基本思想:在子类型构造函数的内部调用超类型的构造函数
- 优点:相比原型链而言,借用构造函数有一个很大的优势,就是子类型函数构造函数可以向超类型构造函数传递参数
- 缺点:方法都在构造函数中定义,因此函数的复用性就无从谈起了(不明白)
function Animal(){
this.species = ['哈士奇','柯基']
}
function Dog(){
Animal.call(this)
}
var dog1 = new Dog();
dog1.species.push('萨摩耶')
var dog2 = new Dog();
console.log(dog1.species); // ["哈士奇", "柯基", "萨摩耶"]
console.log(dog2.species); // ["哈士奇", "柯基"]
传递参数
function Animal(speices){
this.speices = speices;
}
function Dog(speices){
Animal.call(this,speices);
}
var dog1 = new Dog('中华田园犬');
var dog2 = new Dog();
console.log(dog1.speices); // 中华田园犬
console.log(dog2.speices); // 中华田园犬
组合继承
- 基本思想: 原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承
- 优点: 在原型上定义方法是实现了函数复用,又能偶保证每个实例都有它自己的属性
- 缺点: 无论在什么情况下,都会两次调用超类型构造函数(具体什么原因,后面会讲)
function Animal(speices){
this.speices = speices;
this.skills = ["jump","climb","catch","run"]
}
Animal.prototype.getSpeices = function(){
console.log(this.speices)
}
function Dog(speices,color){
// 借用构造函数继承,继承父类的属性
Animal.call(this,speices); // 第二次
this.color = color;
}
// 原型继承,继承父类的方法
Dog.prototype = new Animal(); // 第一次
Dog.prototype.getColors = function(){
console.log(this.colors);
}
var dog1 = new Dog('博美','white'); // 在这里用的时候
dog1.skills.push('acting');
console.log(dog.skills); // ["jump","climb","catch","run","acting"]
dog1.getSpeices(); // 博美
var dog2 = new Dog('柯基','brown');
console.log(dog2.skills); // ["jump","climb","catch","run"]
dog2.getSpeices(); // 柯基
关于两次调用父类的构造函数,先画个图
在第一次调用Animal构造函数时,Dog.prototype会得到两个属性speices和skills,位于Dog的原型中。当调用Dog构造函数时,又会调用一次Animal构造函数,这一次位于新的实例对象上。在新的对象上创建了实例属性speices和skills,这两个属性会屏蔽原型中的两个同名的属性
原型式继承
- 基本思想:没有使用构造函数,借助原型可以基于已有的对象创建新对象,同时还不必创建自定义类型。就是
ES5 Object.create
的模拟实现,将传入的对象作为创建的对象的原型。 - 优点: 想让一个对象与另一个对象保持类似,就不用创建构造函数了
- 缺点: 包含引用类型的值始终会共享,这跟原型链继承的缺点一样
function createObj(o){
function F(){}
F.prototype = o;
return new F();
}
使用
var dog = {
species: '比熊犬',
color: 'gold',
skills: ["jump"]
}
var dog1 = createObj(dog); // dog 对象作为dog1 对象的基础,在ES5当中,这里可以写成
dog1.species = ' 泰迪';
dog1.color = 'brown';
dog1.skills.push('acting');
var dog2 = createObj(dog);
dog2.species = ' 吉娃娃';
dog2.color = 'grey';
dog2.skills.push('show');
console.log(dog1.skills) // ["jump","acting","show"]
console.log(dog2.skills) // ["jump","acting","show"]
原型式继承就是Objecte.create
ES5的写法,他还有第二个参数,就是与Object.defineProperties()
方法的第二个参数格式相同,比如
var person = {
name: "南蓝",
age: 24,
hobby: ['acting','codeing;]
}
Object.create(person,{
})
var o
o = {};
// 以字面量方式创建的空对象就相当于:
o = Object.create(Object.prototype);
寄生式继承
- 基本思路: 与原型式继承紧密相关,创建一个用于封装继承过程的函数
- 优点: 在考虑对象不是自定义类型和构造函数的情况下,寄生式继承也是一种很有效的方式
- 缺点:使用寄生式继承式为对象添加函数,不能够做到函数复用
function createDog(obj){
var clone = Object.create(obj);
clone.getColor = function(){
console.log(clone.color)
}
return clone
}
var dog = {
species: '贵宾犬',
color: 'yellow'
}
var dog1 = createDog(dog);
dog1.getColor(); // yellow
寄生组合式继承
基本思想:利用借用构造函数继承属性,原型链的混成形式来继承方法。和组合继承有点类似,不过它解决组合继承调用两次父类的构造函数,就是在子类的原型调用父类的构造函数时做下改变,用寄生式继承来做这步操作,我们想要无非不就是父类原型的一个副本而已
优点: 解决了组合继承两次调用父类的构造函数,普遍人员认为这是最理想的一种方式
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
function inheritPrototype(child,parent){
var prototype = object(parent.prototype); // 创建对象,此处可以用 Object.create(parent.prototype)
prototype.constructor = child; // 增强对像
child.prototype = prototype; // 指定对象
}
function Animal(speices){
this.speices = speices;
this.skills = ["jump","climb","catch","run"];
}
Animal.prototype.getSpeices = function(){
console.log(this.speices)
}
function Dog(species,color){
Animal.call(this,species);
this.color = color;
}
inheritPrototype(Dog,Animal);
// 在组合继承里面,这句是 Dog.prototype = new Animal()
var dog1 = new Dog('牧羊犬','black');
dog1.getSpeices(); // 牧羊犬
涉及问题:仔细对比组合继承(借用构造函数继承+原型继承)与寄生组合继承(借用构造函数与原型链的混成方式)
class关键字
实例属性
class Person {
constructor(name) {
this.name = name; // 实例属性
}
sayHello() {
return 'hello, I am ' + this.name;
}
}
var kevin = new Person('Kevin');
kevin.sayHello()
新的写法
class Person {
name = "小菊"
sayHello() {
return 'hello, I am ' + name;
}
}
对应的es5代码
function Person(){
this.name = name
}
Person.prototype.sayHello = function() {
return 'hello, I am ' + this.name;
}
静态属性和方法
所有在类中定义的方法或方法,都会被实例继承。如果在一个方法前,加上 static 关键字,就表示该方法或属性不会被实例继承,而是直接通过类来调用,这就称为“静态方法或属性”。
class Person {
static name = "xiaoju"
static sayHello = function(){
return 'hello, I am ' + name;
}
}
console.log(Person.name)
Person.sayHello()
对应的es5代码
function Person() {}
Person.sayHello = function() {
return 'hello';
};
Person.sayHello(); // 'hello'
var kevin = new Person();
kevin.sayHello(); // TypeError: kevin.sayHello is not a function
class Person {
constructor(){
this.age = 24
}
static bar = 'bar';
static onSayHello = function(){
return this.bar
}
sayHello() {
return 'hello, I am ' + this.age;
}
}
对应的es5代码
function Person(){
this.age = 24
}
Person.prototype.sayHello = function(){
retur 'hello,I am' + this.age
}
Person.bar = "bar"
Person.onSayHello = function(){
return this.bar
}
编译后的代码
"use strict";
function _instanceof(left, right) { if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) { return !!right[Symbol.hasInstance](left); } else { return left instanceof right; } }
function _classCallCheck(instance, Constructor) { if (!_instanceof(instance, Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
var Person = /*#__PURE__*/function () {
function Person() {
_classCallCheck(this, Person);
this.age = 24;
}
_createClass(Person, [{
key: "sayHello",
value: function sayHello() {
return 'hello, I am ' + this.age;
}
}]);
return Person;
}();
_defineProperty(Person, "bar", 'bar');
_defineProperty(Person, "onSayHello", function () {
return this.bar;
});
class继承
ES6 通过 extend实现继承,在子类的constructor中必须通过super关键字调用父类的构造函数,不然子类无法实例,因为子类没有自己的this对象。super()相当于是Parent.call(this)
ES6的代码
class Parent {
constructor(name) {
this.name = name;
}
getName (){
return this.name
}
}
class Child extends Parent {
constructor(name, age) {
super(name); // 调用父类的 constructor(name)
this.age = age;
}
}
var child1 = new Child('kevin', '18');
console.log(child1);
子类的__proto__属性
console.log(Child.__proto__ === Parent)
console.log(Child.prototype.__proto__ === Prarent.prototype)
ES5的代码
function Parent (name) {
this.name = name;
}
Parent.prototype.getName = function () {
console.log(this.name)
}
function Child (name, age) {
Parent.call(this, name);
this.age = age;
}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child
var child1 = new Child('kevin', '18');
console.log(child1);
call的原理
Function.prototype.myCall = function (context) {
var context = context || window;
context.fn = this;
var args =[...arguments].slice(1);
var result = context.fn(...args)
delete context.fn
return result;
}
apply的原理
Function.prototype.myApply = function (context) {
context = context ? context : window
context.fn = this
let args = [...arguments][1]
if (!args) {
return context.fn()
}
let result = context.fn(...args)
delete context.fn;
return result
}
bind 实现
Function.prototype.bind2 = function (context) {
if (typeof this !== "function") {
throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
}
var self = this;
var args = Array.prototype.slice.call(arguments, 1);
var fNOP = function () {};
var fBound = function () {
var bindArgs = Array.prototype.slice.call(arguments);
return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
}
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
}
new的原理
function objectFactory() {
var obj = new Object(),
Constructor = [].shift.call(arguments);
obj.__proto__ = Constructor.prototype;
var ret = Constructor.apply(obj, arguments);
return typeof ret === 'object' ? ret : obj;
};
参考
github.com/mqyqingfeng… github.com/mqyqingfeng…
《js高级程序设计 第3版》
转载自:https://juejin.cn/post/6947642104092819464