要实现继承我们先要理解原型和原型链,那么我们就先来看看他们的概念吧
一.原型
1.普通对象的原型
JavaScript中每个对象中都有一个特有的内置属性[[prototype]],这个特殊的对象可以指向另一个对象。
简单来说就是对象中有个属性指向另一个对象,来看看代码
obj = {name:'sss'}
console.log(obj);
// 获取对象的原型
console.log(obj.__proto__);
console.log(Object.getPrototypeOf(obj)); //用来获取原型的方法
console.log(obj.__proto__ ===Object.getPrototypeOf(obj));
来看看结果
obj中果然有个[[prototype]]属性,那么这个原型又有什么用呢?当我们查找一个属性或者函数时 ,它会优先在自己的对象中查找,如果没找的话就会去它的原型中查找
console.log(obj.name); //sss
obj.__proto__.message = 'zzz'
console.log(obj.message); //zzz
2.函数的原型
1.所有的函数都有一个特别的prototype属性(显式原型)
function foo(){}
// 当把函数看成一个普通的对象时有个隐式原型(__proto__)
console.log(bar.__proto__);
console.log(foo.__proto__);
// 当把函数看成一个函数时有个显示原型(prototype)
console.log(foo.prototype);
console.log(bar.prototype); // 没有原型
2. 再来回忆下new操作符所做的操作
1.创建一个空对象
2.将这个空对象赋值给this
3.将函数的显示原型作为这个对象隐式原型
4.将这个对象返回
所以我们可以知道foo函数创建的__proto__等同于foo函数的prototype
function foo(name,age){
this.name = name
this.age =age
}
f = new foo('ss',18)
console.log(f.__proto ==foo.prototype) //true
每次new一个对象时,会创建出许多属性,但是如果将方法一起创建,那么是不是每创建一个实例,就会将方法一起创建,那么当创建出许多对象时是不是显得很多余,这个时候原型的作用就体现出来了,我们可以将方法放在原型上,就不用多次创建这个方法在对象中了
function foo(){
name:'ss'
}
foo.prototype.run = function(){
console.log('我会跑步');
}
let f1 = new foo()
f1.run() // 我会跑步
let f2 =new foo()
console.log(f1.run === f2.run) //true
而且很容易发现f1和f2调用的都是同一个方法,此时根据原型查找关系和this的隐式绑定将方法绑定在调用的对象身上,就可以很容易的使用
3.constructor属性
我们来打印一下foo的原型就可以很容易发现它身上有一个constructor属性
咦,好像就是foo本身,也就是说foo的prototype指向foo的原型,原型的constructor又指向foo,是个循环引用
function foo(){
}
console.log(foo.prototype.constructor === foo); // true
console.log(foo.prototype.constructor.name); //foo
let f = new foo()
console.log(f.__proto__.constructor === foo); //true
4.重写对象原型
对象的原型是可以绑定别的对象的
function Foo(){}
Foo.prototype = {
name:'sss',
age:18,
run:function(){
console.log('跑步');
}
}
console.log(Foo.prototype); //但是此时我们会发现少了个constructor
Object.defineProperty(Foo.prototype,'constructor',{
configurable:true,
writable:true,
enumerable:false,
value:Foo
}) //添加constructor并且不可枚举
5.原型链
理解原型后原型链就很简单了,就是我们寻找一个属性时优先在原型上查找,没找到继续接着原型找,而这个链条就是原型链
二.继承的实现
面向对象有三大特性:
1.封装:将属性和方法封装到一个类中
2.继承:为了减少重复的代码,也是多态的前提
3.多态:不同的对象执行时有不同的表现
实现继承的方式
1.寄生式继承方案(最终)
function Person(name,age){
this.name = name
this.age = age
}
Person.prototype.run = function(){
console.log('跑');
}
Person.prototype.sex = function(){
console.log('涩涩');
}
function Student(name,age,grade){
//借用构造函数
Person.call(this,name,age)
this.grade = grade
}
function createObject(obj){
function F(){}
F.prototype = obj.prototype
return new F()
}
function inherit(Subtype,Supertype){
Subtype.prototype = createObject(Supertype)
Object.defineProperty(Subtype.prototype,'constructor',{
enumerable:false,
configurable:true,
writable:true,
value:Subtype
})
}
inherit(Student,Person)
let s =new Student('sss',18,100)
s.run() // 跑
s.sex() // 涩涩
console.log(s.name); //sss
2.Object是其他类的父类
3.对象的几个判断方法
hasOwnProperty:对象是否有某一个属于自己的属性
in/for in:判断某个属性思否在对象上或者对象的原型上
instanceof:用于检测构造函数的prototype是否在某个实例对象的原型链上
inPrototypeOf:用于检测某个对象是否在某个实例对象的原型上