1.构造函数
在介绍原型前先了解下什么是构造函数?
构造函数也是函数,在表现形式上与函数没有什么区别,但在使用时构造函数需要用new关键字调用。另外,约定俗成构造函数首字母大写(不是必须)
new关键字调用构造函数内部操作
当使用new关键字调用构造函数时,流程如下
- 创建新对象,该对象为空对象
- 新对象的原型
__proto__指向构造函数的原型prototype this指向这个新对象- 指向构造函数内部代码
- 如果构造函数没有指定返回
复杂类型数据则返回this否则返回指定数据。
2.原型 [[prototype]]
什么是原型
每个对象(函数也算对象)中都会有原型,原型可以理解为对象之间的连接,是用于实现继承和属性共享的机制。
对象的原型指的是__proto__,函数的原型指的是prototype,而函数也算是对象所以也会有__proto__。
- 显式原型:可以使用
函数.prototype的方式来获取函数的原型。 - 隐式原型:用来获取对象的原型,在写测试代码时候可以使用
对象.__proto__的方式来使用。
注:在JavaScript中,
__proto__是一个非标准的属性,它用于访问对象的原型。它是一种在早期JavaScript实现中存在的属性,但目前它已经被ECMAScript标准所废弃, 可以使用Object.getPrototypeOf()方法来获取对象原型
对象和函数对象的原型指向
对于原型指向总归一句话:对象原型找其构造函数
1.由构造函数创建出的对象,其原型
__proto__指向构造函数的原型prototype,其它对象形式指向Object.prototype,除了Object.prototype.__proto__;
2.函数对象的原型prototype默认指向一个包含constructor的对象
- 创建对象方式可以由
字面量创建和new构造函数来创建。
由于字面量创建对象是new Object()的简写形式,所以字面量创建对象的原型指向为构造函数Object的原型prototype。 - 函数对象的显式原型
prototype是一个对象,该对象存在一个不可枚举属性constructor,该属性的值为函数对象本身。函数对象的隐式原型__proto__指向的是构造函数Fucntion的prototype
function Person(){}
const p1 = new Person()
对于上方代码,原型指向情况如下
对象属性or方法的getter查找规则
当获取对象上的属性or方法时会现在对象自身寻找,如果找寻不到则会在对象的原型上寻找。
给构造函数的原型添加属性or方法
为何要给构造函数的原型添加属性or方法
我们观察如下代码
function Person(name, age) {
this.name = name;
this.age = age;
this.eating = function () {
console.log(`${this.name} 在吃饭`);
};
this.runing = function () {
console.log(`${this.name} 在跑步`);
};
}
const p1 = new Person("p1", 18);
const p2 = new Person("p2", 18);
const p3 = new Person("p3", 18);
每次通过new来调用Person构造函数时,都会创建方法eating和runing,这样会浪费内存空间,完全没必要。
而我们在上方说过对象属性or方法的getter查找规则,根据这个规则我们可以直接构造函数的原型添加属性or方法,这意味着类的实例之间共享相同的方法代码,而不是复制多份。
那么如何给构造函数的原型添加属性or方法?
如何给构造函数的原型添加属性or方法
我们可以使用构造函数.prototype.xxx = xx这种方式一个个来给原型上添加属性or方法,但确定是每次都需要通过构造函数.prototype.xxx来添加,这样就比较麻烦,那么我们就可以使用构造函数.prototype = {}来整体赋值,但需要注意,原本构造函数.prototype是存在constructor属性的,并且是不可枚举的。那么我们需要单独来对constructor定义。代码如下:
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype = {
eating: function () {
console.log(`${this.name} 在吃饭`);
},
runing: function () {
console.log(`${this.name} 在跑步`);
},
};
Object.defineProperty(Person, "constructor", {
configurable: true,
writable: true,
enumerable: false,
value: Person,
});
const p1 = new Person("p1", 18); //p1 在跑步
原型指向图解
特殊的几个原型指向
console.log(Object.__proto__ == Function.prototype); //true
//因为Object也是一个函数对象,是new Fucntion()简写形式,其值为{construtor:Function,__proto__:object.prototype}
console.log(Function.prototype.__proto__ == Object.prototype);//true
console.log(Object.prototype.__proto__ == null);//true
console.log(Function.__proto__ == Function.prototype);//true 因为Function也是一个函数对象,是new Fucntion()简写形式,
3.原型链[[prototype chain]]
首先要知道原型也是一个对象,原型也会有原型。
什么是原型链
原型链是js实现继承的一种机制,其由一个接一个原型组成,如:(原型->原型的原型->原型的原型的原型-> ···),原型链最顶层是Object.prototype(还有另一种说法是null)。
当我们在对象上调用一个方法or使用一个属性时,如果对象自身没有会在原型链上寻找,找到则返回,直到最顶层还没有找到的话则返回undefined
4.继承
什么是继承
继承就是重复利用另一个对象的属性or方法,如下代码
function Student(name, age) {
this.name = name;
this.age = age;
}
Student.prototype.eating = function () {
console.log("可以吃");
};
Student.prototype.running = function () {
console.log("可以跑");
};
Student.prototype.learning = function () {
console.log("可以学习");
};
function Teacher(name, age) {
this.name = name;
this.age = age;
}
Teacher.prototype.eating = function () {
console.log("可以吃");
};
Teacher.prototype.running = function () {
console.log("可以跑");
};
Teacher.prototype.teaching = function () {
console.log("可以教学");
};
我们看上面的两个构造函数Student和Teacher,这两个构造函数都有属性name和age及方法eating和runing,如果再有其它构造函数如工人、司机等岂不是还要再重新定义一遍?那我们有没有办法来避免代码的重复?有,那就是继承。我们可以定义个构造函数为Person,这个构造函数具有共有属性,其它构造函数来继承Person就可以了。
如何实现继承
1.原型链继承
原理:利用在实例上寻找不到会在其原型上寻找的方式
优点:实现了方法继承
缺点:
-
只实现了方法继承,没有实现属性继承
-
给
Student的原型添加方法会同时添加到Person的原型上 -
类型会为
Person
function Person() {}
Person.prototype.eating = function () {
console.log("吃饭");
};
Person.prototype.running = function () {
console.log("跑步");
};
function Student() {
this.name = name
}
// 该行代码实现了原型链继承
Student.prototype = new Person()
Student.prototype.learning = function () {
console.log("学习");
};
const stu1 = new Student('stu1')
2.借用构造函数式继承
原理:使用call来更改this绑定的方式来属性继承
优点:实现了属性继承
缺点:不能继承方法
function Person(name, age) {
this.name = name;
this.age = age;
}
function Student(name, age, sno) {
// 该行实行了借用构造函数式继承
Person.call(this, name, age);
this.sno = sno;
}
let stu = new Student("stu", 18, "18452");
3.组合式继承
原理:原型式继承和借用构造函数式继承的组合形式
优点:实现了属性继承和方法继承
缺点:
- 会调用两次
Person构造函数 - 给
Student原型添加的方法会出现在Person的原型上 - 在
stu对象实例上和原型同时出现name和age属性
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.eating = function () {
console.log("吃饭");
};
Person.prototype.running = function () {
console.log("跑步");
};
function Student(name, age, sno) {
// 调用构造函数式继承
Person.call(this, name, age);
this.sno = sno;
}
// 原型链继承
Student.prototype = new Person();
Student.prototype.learning = function () {
console.log("学习");
};
let stu = new Student("stu", 18, "18452");
4.原型式继承
原理:使用一个空对象作为中介,将某个对象赋值给空对象的构造函数的原型
优点:实现了属性继承和方法继承
缺点:
- 只能对象方式继承,而不是构造函数式继承
- 不能传参,只能通过
stu1.name的方式去修改,并且修改完成之后只是给stu1实例上的name修改,原型上还存在name属性并且值为person - 如果是引用类型,会多个实例的引用类型属性指向相同,存在篡改的可能
// create函数实现原型式继承
function createObject(obj) {
function foo() {}
foo.prototype = obj;
return new foo();
}
const person = {
name: "person",
friends: ["a", "b"],
running: function () {
console.log("跑步");
},
};
// stu1继承自person
const stu1 = createObject(person);
stu1.name = "stu1";
stu1.friends.push("c");
// stu2继承自person
const stu2 = createObject(person);
stu2.name = "stu2";
console.log(stu2.friends); //Array(3) [ "a", "b", "c" ]
stu1:
stu2:
5.寄生式继承
原理:原型式继承和工厂函数结合
优点:实现了属性继承和方法继承,并且在原型式基础上可以传递参数
缺点:
- 依旧只是对象上继承
- 依旧如果是引用类型,会多个实例的引用类型属性指向相同,存在篡改的可能
function createObject(obj, name, age) {
function foo() {}
foo.prototype = obj;
foo.name = name;
foo.age = age;
foo.friends = ["a", "b", "c"];
return new foo();
}
const person = {
name: "person",
friends: ["a", "b"],
running: function () {
console.log("跑步");
},
};
// stu1继承自person
const stu1 = createObject(person, "stu1", "18");
stu1.name = "stu1";
stu1.friends.push("c");
// stu2继承自person
const stu2 = createObject(person, "stu2", "20");
stu2.name = "stu2";
console.log(stu2.friends); //Array(3) [ "a", "b", "c" ]
stu1:
stu2:
6.寄生式组合继承 !!!最终方案
原理:原型式继承和调用构造函数式继承的结合,使用Object.create方法来代替原型式继承的createObject方法
function Person(name, age, friends) {
this.name = name;
this.age = age;
this.friends = friends;
}
Person.prototype.eating = function () {
console.log(this.name + `在吃饭`);
};
Person.prototype.running = function () {
console.log(this.name + `在跑步`);
};
function Student(name, age, friends, sno) {
// 实现了属性继承
Person.call(this, name, age, friends);
this.sno = sno;
}
Student.prototype = Object.create(Person.prototype, {
//修改construtor指向类型
constructor: {
writable: true,
configrable: true,
value: Student,
},
});
// 上方的Object.create相当于下方伪代码,这样实现了方法继承
//Student.prototype = { constructor: Student, __proto__: Person.prototype };
Student.prototype.learning = function () {
console.log(this.name + `在学习`);
};
let stu = new Student("stu1", "18", [{ one: "bob" }], "1106");