↓↓↓来自【路遥_流年 】的思维导图:
「这是我参与2022首次更文挑战的第4天,活动详情查看:2022首次更文挑战」。
原型是什么?原型链是什么?
// 定义Person大类,有name属性和drink方法
class Person {
constructor(name){
this.name = name;
}
drink(){
console.log('i can drink!');
}
}
// 定义Student类,并继承Person属性和方法
class Student extends Person {
constructor(name,score){
super(name);
this.score = score;
}
getScore(){
console.log(`我是${this.name}得了${this.score}分`);
}
}
const stu = new Student('jessi',100)
console.log(stu.name);
stu.getScore()
stu.drink()
/**
* 1、 Student.prototype === stu.__proto__ //true 显示原型和隐式原型都指向同一个对象
* 2、 当访问一个属性或方法时候,首先在自己本身里寻找,如果没有再去访问它的原型,或原型的原型,这样一层层的的关系就是原型链
*/
hasOwnProperty 检测属性或方法是否属于本身
拓展:类型判断
const obj = {}
const arr = []
console.log(typeof(obj)); // object
console.log(typeof(arr)); // object
// typeOf无法判断数组和对象
console.log(obj instanceof(Object)); // true
console.log(arr instanceof(Array)); // true
继承方法
原型链继承
优点:继承父类的属性和方法,也继承父类原型上的方法和属性
缺点:(1) 原型属性在所有实例中是共享的,其中一个实例改变,其他实例也会跟着改变 (2)没办法给父类传参
// 父类
function Father(){
this.name = 'baba';
this.colors = ["red", "blue", "green"];
}
Father.prototype.getName = function(){
return `my name is ${this.name}`
}
// 子类
function Son(age){
this.age = age
}
// 原型链继承父类
Son.prototype = new Father(); //继承了SuperType的所有属性和方法
// 子类自己添加新方法
Son.prototype.getAge = function (){
return `my age is ${this.age}`
}
// 实例化对象
var s = new Son(18);
var s2 = new Son(28);
console.log(s.name); //baba
console.log(s2.name); //baba
console.log(s.age) //18
console.log(s2.age) //28
console.log(s.getName()) //my name is baba
console.log(s.getAge()) //my age is 18
console.log(s.colors) //[ 'red', 'blue', 'green' ]
s.colors.push('yellow')
console.log(s.colors) //[ 'red', 'blue', 'green', 'yellow' ]
console.log(s2.colors) //[ 'red', 'blue', 'green', 'yellow' ]
借助构造函数继承
方法:在子类型构造函数的内部通过使用 apply()和 call()等方法调用超类型构造函数。
优点: (1) 每个实例不会共享属性,其中一个实例改变属性另一个实例不受影响; (2) 子类可以向父类传递参数。
缺点:不能继承父类原型上的属性和方法
// 父类
function Father(name){
this.name = name;
this.colors = ["red", "blue", "green"];
this.say = function (){
console.log('gua~gua~gua')
}
}
Father.prototype.getName = function(){
return `my name is ${this.name}`
}
// 子类
function Son(name,age){
Father.call(this,name) //借助构造函数继承
this.age = age
}
// 子类自己添加新方法
Son.prototype.getAge = function (){
return `my age is ${this.age}`
}
// 实例化对象
var s1 = new Son('lily',18);
var s2 = new Son('tom',28);
console.log(s1.name) //lily
console.log(s1.age) //18
s1.say() //gua~gua~gua
console.log(s1.getAge()) //my age is 18
// console.log(s1.getName()) //不能访问父类原型上的属性和方法 s1.getName is not a function
s1.colors.push('yellow')
console.log(s1.colors) //[ 'red', 'blue', 'green', 'yellow' ]
console.log(s2.colors) //[ 'red', 'blue', 'green' ]
组合式继承
方法:原型链继承 + 借助构造函数继承 结合 优点:既可以传参又可以复用(属性方法不被共享) 缺点:耗内存(子类构造函数被多次调用)
// 父类
function Father(name){
this.name = name;
this.colors = ["red", "blue", "green"];
this.say = function (){
console.log('gua~gua~gua')
}
}
Father.prototype.getName = function(){
return `my name is ${this.name}`
}
// 子类
function Son(name,age){
Father.call(this,name) //借助构造函数继承
this.age = age
}
// 原型链继承
Son.prototype = new Father();
Son.prototype.constructor = Son; //将 constructor指回 Son
// 子类自己添加新方法
Son.prototype.getAge = function (){
return `my age is ${this.age}`
}
// 实例化对象
var s1 = new Son('lily',18);
var s2 = new Son('tom',28);
s1.colors.push('black')
console.log(s1.colors) //[ 'red', 'blue', 'green', 'black' ]
console.log(s2.colors) //[ 'red', 'blue', 'green' ]
console.log(s1.getName()) //my name is lily
console.log(s2.getName()) //my name is tom
原型式继承
本质是浅拷贝,在es5中,新增了一个函数Object.create()实现了原型式继承
优点: 兼容性好,最简单的对象继;
缺点: ① 多个实例共享属性; ② 无法传参
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"],
sayname: function () {
console.log(this.name);
}
};
var p1 = Object.create(person);
var p2 = Object.create(person, {
name: {
value: "Greg"
}
});
//相当于:
//var anotherPerson = Object.create(person);
//anotherPerson.name = "Greg";
p2.friends.push("Rob");
p1.friends.push("Barbie");
console.log(person.friends); //[ 'Shelby', 'Court', 'Van', 'Rob', 'Barbie' ]
p2.sayname(); //"Greg"
寄生式继承
把原型式继承再次封装,然后在对象上扩展新的方法,再把新对象返回 实例对象不仅具有 person 的所有属性和方法,而且还有自己的 sayName()方法。
缺点: (1) 在createAnother()中为对象添加函数,不能做到函数复用 (2) 由于是浅复制person对象,pp的改变会影响person对象
//封装继承函数
function createsunny(obj) {
var clone = Object(obj); //通过Object()创建一个新对象
clone.sayName = function() {
//添加自己的方法
console.log(this.name);
};
return clone; //返回这个对象
}
var person = {
name: "lily",
friends: ["Shel", "Court", "Van"]
};
var pp = createsunny(person);
pp.name='sunny';
pp.friends.push('Barbie');
console.log(person.name); //sunny
console.log(person.friends); // ["Shel", "Court", "Van", "Barbie"]
console.log(pp.name); //sunny
console.log(pp.friends); // ["Shel", "Court", "Van", "Barbie"]
pp.sayName(); //sunny
寄生组合式继承
通过借用构造函数来继承属性,通过“ 原型链 ”来继承方法,不同的是,这里的“ 原型链 ”本质上是使用寄生式继承来继承超类型的原型。
优点: (1) 既能具有组合继承的优点,又可以不必两次调用超类型的构造函数
(2) 避免了在 SubType.prototype 上面创建不必要的、多余的属性(在原型链继承时,SubType.prototype被重写为SuperType的实例,因此具有了他的实例属性)
function inheritPrototype(subType, superType) {
var copy= Object(superType.prototype); //创建新对象
copy.constructor = subType; //增强对象
subType.prototype = copy; //指定对象
}
① 通过浅复制创建超类型原型的一个副本(copy) ② 将副本的constructor 属性重新指向subType(在原型链继承中,constructor指向superType) ③ 把副本赋值给subType的原型
// 父类
function Father(name) {
this.name = name;
this.colors = ["red", "blue", "green"];
this.say = function () {
console.log('gua~gua~gua')
}
}
Father.prototype.getName = function () {
console.log(`father's name is ${this.name}`)
}
// 子类
function Son(name, age) {
Father.call(this, name) //借助构造函数继承
this.age = age
}
inheritPrototype(Son, Father); //调用函数代替原型链继承
// 子类自己的方法
Son.prototype.sayAge = function () {
console.log(this.age);
};
var sub1 = new Son("tom", 29);
var sub2 = new Son("jerry", 27);
sub1.colors.push("black");
console.log(sub1.colors); //[ 'red', 'blue', 'green', 'black' ]
sub1.getName(); //father's name is tom
sub1.sayAge(); //29
console.log(sub2.colors); //[ 'red', 'blue', 'green' ]
sub2.getName(); //father's name is jerry
sub2.sayAge(); //27
扩展:call(),apply(),bind()区别
相同:调用一个对象的一个方法,用另一个对象替换当前对象,call、apply、bind中的this被强绑定在指定的那个对象上;
B.apply(A, arguments);即A对象应用B对象的方法。
B.call(A, args1,args2);即A对象调用B对象的方法。
B.bind(A, args1,args2)();
区别: 当传入多个参数时候,apply方法需要以数组方式传入;bind需要调用
var name = 'lily', age = 18;
var obj = {
name: 'kiki',
objage: this.age,
myFun: function () {
console.log(this.name + '~~' + this.age)
}
};
var pp = {
name: 'grace',
age: 22
};
obj.myFun(); //kiki~~undefined
obj.myFun.call(pp); //grace~~22
obj.myFun.apply(pp); //grace~~22
obj.myFun.bind(pp)(); //grace~~22 bind的区别就是返回一个函数,需要调用才可
当传入多个参数时