前端开发之原型与原型链
原型和原型链估计做前端开发的人都听说过,而且这个是一道很常见的面试题,但是想要真正弄清楚“什么是原型”,”什么是原型链“”,”他们又有什么用“”,“适合在哪些实际场景中使用”这些问题还真不是一件很容易的事儿。今天我就写下自己的理解,一方面加深理解,一方面作为备忘。
-
什么是原型和原型链
1、原型分为显式原型和隐式原型。显示原型对应的是prototype属性,隐式原型对应的是__proto__属性。
2、所有对象(万物皆对象)都有__proto__属性,包括函数、示例等,只有函数才有prototype属性。
3、prototype属性值其实也是一个对象,类型为Object。它有一个自带属性constructor,这个constructor是一个指针,指向函数本身。比如 function Animal(){}。 Animal是一个函数指针,可以叫函数对象。Animal.prototype是一个object对象。Animal.prototype.constructor == Animal。
4、一般情况下,一个实例的__proto__属性等于实例的类型的原型。这句话比较抽象。举个例子,var animal = new Animal(),这里 animal.__proto__ = Animal.prototype。通俗的说法可以为,animal的原型为Animal.prototype,但是这里其实涉及到显式和隐式原型的概念,很容易用混。
5、如果访问一个对象的属性,其查找顺序是,先查找对象本身的属性(可以理解为给this添加的属性),然后查找实例的__proto__(即原型)中的属性,在然后查找原型的__proto__里的属性,就这这样一直查找下去,直到找到。最终找到的是Object.prototype.__proto__ ,它等于 null。 其实这个过程就是原型链。通俗点就是以__proto__属性为媒介,把对象相关的原型以链式方式串联起来,以方便属性和方法的访问。prototype就是要串的原型。可以把__proto__理解为线,而prototype是珠子。
6、constructor属性指向原型的构造方法。其实这个属性在示例new的过程中是没有作用的。在实例的instance of 方法也不是以它为依据的。后来我想到这个属性有一个作用。比如,我们有一个实例,但是无法获取到实例的类(匿名自执行函数),可以通过实例的constructor获取到这个类,然后给类添加方法或属性。不过这种使用方法好像也没有什么必要。只是为了加深理解。
//constructor的作用,匿名自执行函数场景。
var bydCar,ytCar ;
(function () {
function Car() {
}
bydCar = new Car();
ytCar= new Car();
})();
bydCar.constructor.prototype.name = 'BYD';
console.log("bydCar车的品牌:" + bydCar.name);
console.log("ytCar车的品牌:" + bydCar.name);
7、new方法都做了哪些事情。这个还是以var animal = new Animal();为例。
首先是 创建一个空对象。
然后把对象的__proto__属性指向原型。
接下来,设置函数的this指向上边的对象,并执行函数。
//new的过程。
var obj = {};
obj.__proto__ = Animal.prototype;
Animal.call(obj);//如果Animal中有return 就是把return结果作为new的对象,否则就是上边创建的obj对象。
-
原型链和原型有什么用,哪些场景用
理解了上边的内容之后,你有没有感觉原型链的方式很像面相对象编程里边用到得继承的思想呢。下面就是把我用原型链实现的继承示例。使用面相对象的思想编写js代码,会使得代码的复用性更好,抽象程度更深,逻辑也更清晰。
/**原型链继承,修改子类的原型为父类的实例,那么cat.__proto__ == Cat.prototype == animal,原型链查找
* 就是cat----->cat.__proto__-------->animal------>animal__proto__---->Animal.prototype--->Animal.prototype.__proto__------>Object.prototype----->Object.prototype.__proto__------->null*/
function Animal(name) {
this.name = name;
this.sleep = function () {
console.log(name + "正在睡觉");
}
this.getName = function () {
return this.name;
}
this.setName = function (newName) {
this.name = newName;
}
}
Animal.prototype.eat = function () {
console.log(this.name + "正在吃");
}
/*var animal = new Animal('小羊');
animal.__proto__
animal.eat();
animal.sleep();*/
//原型链继承
//缺点1、无法实现多继承。2、所有实例共享同一个父实例。 3、创建子类时无法给父类传递参数。
function Cat(catName) {
this.catName = catName;
}
Cat.prototype = new Animal('动物');
var cat = new Cat('小猫');
var cat1 = new Cat('小猫1');
cat.setName('1111');
var aaa = cat.getName();
//构造继承 1、解决创建时无法给子类传递参数的问题。2、解决多继承问题。
//缺点:1、实例并不是父实例 2、不能继承父亲的原型方法。3、每个子类中都有父类函数的副本。
function Dog(dogName) {
Animal.call(this);
this.dogName = dogName;
}
var dog = new Dog("小狗");
dog.sleep();
//dog.eat();//没有此方法。
//实例继承 此种方式个人觉得没有任何意思,new出来的直接是父实例。只不过在构造方法中可以定义一些属性和方法。
function Cow(cowName) {
var cow = new Animal(cowName);
cow.cowName = cowName;
cow.cowF = function () {
console.log('cowF');
}
return cow;
}
var cow = new Cow('牛');
if(cow instanceof Animal){
console.log('cow 其实不是Cow,而是一个animal');
}
cow.cowName;
//拷贝继承 1支持多继承 2 效率低
function Fish(name) {
var animal = new Animal(name);
for(var p in animal){
Fish.prototype[p] = animal[p];
}
}
var fish = new Fish("鱼");
fish.eat();
fish.name;
//组合继承 推荐,仅仅是调用了两次父类的构造方法而已。
function Tiger(name) {
Animal.call(this);
}
Tiger.prototype = new Animal();
//Tiger.prototype.constructor = Tiger;
Tiger.prototype.tigerName = "老虎";
var tiger = new Tiger();
if(tiger instanceof Animal){
}
if(tiger instanceof Tiger){
}
//寄生组合继承
function Shark(name) {
Animal.call(this,name)
}
(function () {
var Super = function () {
}
Super.prototype = Animal.prototype;
Shark.prototype = new Super();
})();
var shark = new Shark("鲨鱼");
Shark.prototype.constructor = Shark;
Shark.prototype.bite = function () {
console.log('鲨鱼咬合');
}
if(shark instanceof Shark){
debugger;
}
if(shark instanceof Animal){
debugger;
}
var aaa = typeof(shark);
debugger;
shark.bite();
//https://www.cnblogs.com/luvx/p/5833844.html
//https://www.cnblogs.com/humin/p/4556820.html