原型与原型链
原型是一个从其他对象继承属性的对象。对象都是通过函数来创建的,而函数又是一种对象。
关于原型要记住三点:
(1)每个构造函数 Fn 都有一个显式原型对象 Fn.prototype, 每个原型对象都有一个指向构造函数 Fn 的指针 constructor 。
(2)由构造函数创建出来的每个实例都有一个指向原型对象的内部指针。
(3)原型对象上的属性和方法都能被它的实例访问到。
顺着原型找原型的原型,最后可以发现形成了一条有方向的线条,这就是原型链了。
原型链的最顶端为Object.prototype, 但 ({}).prototype是获取不到它的原型的,ECMA 5介绍了标准的访问方法是Object.getPrototypeOf({})。
Object.prototype.__proto__ = null
继承
继承:一个类型(子类)的实例能够访问另外一个类型(父类)的属性和方法。
1. 原型继承
核心:让一个类型(子类)的原型对象等于另一个类型(父类)的实例。
function Peron(){
}
Person.prototype.name = "john";
Person.prototype.age = 20;
Person.prototype.sayHello = function(){
console.log(this.name);
};
function Male(){
}
// // test1 : 直接赋值
// Male.prototype = Person.prototype; // 地址共用,修改子类原型,父类也被修改,x
// Male.prototype.sexy = "男";
// console.log(Person.prototype);
// // test2 :拷贝赋值
// for (var i in Person.prototype){ // 遍历
// Male.prototype[i] = Person.prototype[i]; // 拷贝赋值
// }
// Male.prototype.sexy = "男";
// console.log(Person.prototype); // 不会修改父类原型
// test 3 : 将父类的实例赋值给子类原型
Male.prototype = new Person();
Male.prototype.sexy = "男";
var male = new Male();
male.sayHello(); // john
var person = new Person();
console.log(person.sexy); // undefined
2. 构造函数继承
核心:使用call实现函数复用(call接受参数列表),并改变子类实例的this指向。
优点:可以传参,添加自己的属性和方法时不会修改父类
缺点:方法都在构造函数中定义,函数无法复用
function Person(name,age){
this.name = name;
this.age = age;
this.sayHello = function(){
console.log(this.name);
}
}
function Male(name,age){
Person.call(this,name,age);
this.sexy = "男";
}
function Female(name,age){
Person.call(this,name);
}
3. 组合继承
核心:在子类构造函数中通过parent.call(this,value)继承父类的属性,然后改变子类的原型为new Parent()来继承父类的函数。
优点:构造函数可以传参,不会与父类引用属性共享,可复用父类函数。
缺点:继承父类时调用了父类构造函数,导致子类原型上多了一些可能不需要的父类属性/方法。
function Person(name,age){
this.name = name;
this.age = age;
}
Person.prototype.sayHello = function(){
console.log(this.name);
}
function Male(name,age){
Person.call(this,name,age);
}
Male.prototype = new Person();
Male.prototype.sexy = "男";
var male = new Male("john",20);
male.sayHello();
console.log(Male.prototype,Person.prototype);
4. 寄生式组合继承
核心:Object.create(obj) : 创建一个新的对象并把它与()中的参数 obj 相关联。这样,新对象本身并没有得到指定对象的任何值的副本(不会共用地址),只是将它与指定对象关联,而obj会作为新创建出来的对象的原型对象。
(1)通过call实现实例属性的继承
(2)将父类原型赋值给子类,并设置子类原型上的指针指向子类的构造函数
优点:既解决了无用父类属性的问题,还能正确的找到子类的构造函数
// 这里是Object.create()的模拟实现
function cloneObject(obj){
function F(){}
F.prototype = obj; // 将被继承的对象作为空函数的prototype
return new F(); // 返回new期间创建的新对象,此对象的原型为被继承的对象, 通过原型链查找可以拿到被继承对象的属性
}
function Person(name,age){
this.name = name;
thia.age = age;
}
Person.prototype.sayHello = function(){
console.log(this.name);
}
function Male(name,age){
Person.call(this,name,age); // 实现实例属性的继承
}
console.log(Male.prototype); // Object {constructor: ƒ Male(name, age),__proto__: Object}
// 将父类原型赋值给子类,并设置子类原型上的指针指向子类的构造函数
Male.prototype = Object.create(Person.prototype);
console.log(Male.prototype); // Person {} {constructor: ƒ Male(name, age),__proto__: Object}
console.log(Male.prototype.constructor); // ƒ Person(name, age) {this.name = name;this.age = age;}
Male.prototype.constructor = Male;
var male = new Male('john', 21);
male.sayHello(); // john
console.log(male.__proto__.constructor); // ƒ Male(name, age) {Person.call(this, name, age);}
5. ES6继承
js中并不存在类,class的本质是函数。
核心:使用extends 表明继承自哪个父类,在子类构造函数中必须调用super,可以看成是Parent.call(this,arg1,arg2,…)
(1) 每个class内部都有一个constructor,为指向构造函数的指针。
(2) constructor内部使用super,创建并改变this对象,super指向父类的构造函数。
class Person{
constructor(name,age){
this.name = name;
this.age = age;
}
sayHello(){
console.log(this.name);
}
static foo(){ // 静态方法
console.log("aa");
}
}
class Male extends Person{
constructor(name,age){
super(name,age); // super指向父类的构造函数
this.sexy = "男"; // this指向实例对象
}
sayHi(){
super.sayHello(); // 指向父类的原型对象
}
static bar(){
super.foo(); // 指向父类
}
}
var male = new Male("john",20);
male.sayHi(); // john
Person.foo(); // aa
Male.bar(); // aa