原型与原型链
为什么会出现原型与原型链?
我们都知道面向过程编程、面向对象编程,JS原型出现的原因就是要实现面向对象。说到这里肯定还不明白,下面就来详细说明一下这张图:
首先把握两个点:对象一定有__proto__,函数一定有prototype.
这张图描述了以下几个意思:
1.JS通过构造函数(一般用首字母大写的函数)实现类,通过new一个构造函数来创建对象。
2.构造函数的显式原型prototype等于他所创建出来对象的隐式原型__proto__
3.Object构造函数也是Function创建的,因为它也是个函数吧,不然怎么创建对象,是函数那就肯定是Function new出来的。Function.prototype是一个对象吧,它的__proto__就是Object.prototype,注意:不是说对象的__proto__就一定是Object.prototype,String new 出来的对象__proto__是String.prototype
4.Function是一个构造函数,他可以new出来很多实例其中包括Array,String,以String为例:
(1)String这些具有双重身份,作为对象,它的__proto__指向Function.prototype。
(2)作为构造函数,可以new出示例,比如[1,2,3],因为万物皆对象 String.prototype是Object创建出来的对象,所以它的__proto__指向Object.prototype。
5.Object.prototype.proto==null,总得有个尽头吧,这就是原型链的尽头。
面向对象和原型链
根据上面的描述,原型链其实就定义了一套对象函数变量之间的规则,用于实现类与对象,实现面向对象。
手写new
function myNew() {
//拿到第一个值,构造函数
let content = Array.prototype.shift.call(arguments);
//1.shift会改变原数组,2.call可以借用方法
let args = Array.prototype.slice.call(arguments);
//typeof类型判断
if (typeof content !== "function") throw new Error("error");
//创建对象
let obj = Object.create(content.prototype); //这个里面的this已经维护好了
//调用函数,并维护创建出来的对象的this,在构造函数内部,this指向创建出来的实例
let res = content.apply(obj, args);
let flag =
res && (typeof res === "function" || typeof res === "object");
return flag ? res : obj;
}
//测试用例1,有返回值,new出来的对象只有返回的对象上的属性c
// let obj = { c: 1 };
// function foo(a, b) {
// this.a = a;
// this.b = b;
// return obj;
// }
// let o = myNew(foo, 1, 2);
// console.log(o.c);
//测试用例2,没有返回值,new出来的对象包含foo构造函数的属性a,b
let obj = { c: 1 };
function foo(a, b) {
this.a = a;
this.b = b;
}
let o = myNew(foo, 1, 2);
console.log(o.a);
继承
为什么需要继承?
继承是将不同类(Student,Teacher)之间的共有的部分提取出来,写到一个父类中(Person) 减少代码冗余
继承的方法
1.原型链继承:
直接将子类的prototype指向父类new出来的示例。
function Father() {
this.name = "zhangsan";
this.age = 12;
}
function Son() {
this.school = "123";
}
Son.prototype = new Father();
Son.prototype.sing = function () {
console.log("singing");
};
存在的问题:
1.Object.keys拿不出来,因为这个方法拿的是自身可枚举属性
2.要给子类加方法需要在继承语句的后面
2.借用构造函数继承
function Father(name, age) {
this.name = name;
this.age = 12;
}
Father.prototype.sing = function () {
console.log("singing");
};
function Son(name, age) {
Father.call(this, name, age);
this.school = "123"; //子类独有的属性
}
Son.prototype = new Father();
let son3 = new Son("zs", 18); //这个对象拥有它的类继承来的name和age
console.log(son3.name);
console.log(son3.school);
解释:
1.Father.call是把name,age传给父类的构造函数,让父类构造函数的逻辑在子类里面跑一下,然后使用call改变this的指向为子类,相当于给子类添加了两个属性name,age,实现继承。
2.为了继承方法,使用Son.prototype=new Father()
存在的问题
父类构造函数调用了两次,一次是call,一次是new。
3.寄生组合式继承
需要改变子类prototype的constructor指向
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.sing = function () {
console.log("singing");
};
Person.prototype.run = function () {
console.log("running");
};
function Student(name, age, score, course) {
Person.call(this, name, age);
this.score = score;
this.course = course;
}
inherit(Student, Person); //使用继承函数继承,改变construcotor 使用Object.create
let student = new Student("zs", 18, 98, ["Math", "English"]);
console.log(student.constructor); //这里的constructor指向person,需要修改
function inherit(subType, superType) {
subType.prototype = Object.create(superType.prototype);
Object.defineProperty(subType.prototype, "constructor", {
enumerable: false,
writable: true,
configurable: true,
value: subType,
});
}
4.ES6的继承
class Father {
constructor(age, name) {
this.age = age;
this.name = name;
}
singing() {
console.log("sing");
}
dance() {
console.log("dance");
}
method() {
console.log(1);
console.log(2);
console.log(3);
}
}
class Son extends Father {
constructor(age, name, score) {
super(age, name);
this.score = 100;
}
study() {
console.log("study");
}
method() {
super.method();
console.log(4);
console.log(5);
console.log(6);
}
}
let son = new Son(18, "zs,");
console.log(son.method());
1.使用extend+super关键字实现继承,可以继承父类的属性和方法。
super的两种用法 1.直接在constructor里面调用传入子类的参数,借用父类的逻辑实现继承。 2.在子类的某个方法里调用父类中方法的一些逻辑,实现逻辑的复用和方法的重写。
总结
继承的方法有:原型链继承,借用构造函数继承,寄生组合式继承,ES6继承(extends+super)