编程技术文章分享与教程

网站首页 > 技术文章 正文

「前端知识体系-JS相关」深入理解JavaScript原型和原型链

hmc789 2024-11-10 10:33:53 技术文章 2 ℃

1.Javascript继承

1.1 原型链继承

 function Parent() {
 this.name = 'zhangsan';
 this.children = ['A', 'B', 'C'];
 }
 Parent.prototype.getName = function() {
 console.log(this.name);
 }
 
 function Child() {
 
 }
 Child.prototype = new Parent();
 var child = new Child();
 console.log(child.getName());

[!NOTE]
主要问题:

  1. 引用类型的属性被所有实例共享(this.children.push(‘name’))
  2. 在创建Child的实例的时候,不能向Parent传参

1.2 借用构造函数(经典继承)

 function Parent(age) {
 this.names = ['zhangsan', 'lisi'];
 this.age = age;
 
 this.getName = function() {
 return this.names;
 }
 
 this.getAge = function() {
 return this.age;
 }
 }
 
 function Child(age) {
 Parent.call(this, age);
 }
 var child = new Child(18);
 child.names.push('haha');
 console.log(child.names);
 
 var child2 = new Child(20);
 child2.names.push('yaya');
 console.log(child2.names);

[!NOTE]
优点:

  1. 避免了引用类型的属性被所有实例共享
  2. 可以直接在Child中向Parent传参

[!DANGER]
缺点:

  • 方法都在构造函数中定义了,每次创建实例都会创建一遍方法

1.3 组合继承(原型链继承和经典继承双剑合璧)

 /**
 * 父类构造函数
 * @param name
 * @constructor
 */
 function Parent(name) {
 this.name = name;
 this.colors = ['red', 'green', 'blue'];
 }
 
 Parent.prototype.getName = function() {
 console.log(this.name);
 }
 
 // child
 function Child(name, age) {
 Parent.call(this, name);
 this.age = age;
 }
 
 Child.prototype = new Parent();
 // 校正child的构造函数
 Child.prototype.constructor = Child;
 
 // 创建实例
 var child1 = new Child('zhangsan', 18);
 child1.colors.push('orange');
 console.log(child1.name, child1.age, child1.colors); // zhangsan 18 (4) ["red", "green", "blue", "orange"]
 
 var child2 = new Child('lisi', 28);
 console.log(child2.name, child2.age, child2.colors); // lisi 28 (3) ["red", "green", "blue"]

[!NOTE]
优点: 融合了原型链继承和构造函数的优点,是Javascript中最常用的继承模式

2. 多种方式实现继承及优缺点总结

2.1 原型式继承

 function createObj(o) {
 function F(){};
 // 关键:将传入的对象作为创建对象的原型
 F.prototype = o;
 return new F();
 }
 
 // test
 var person = {
 name: 'zhangsan',
 friends: ['lisi', 'wangwu']
 }
 var person1 = createObj(person);
 var person2 = createObj(person);
 
 person1.name = 'wangdachui';
 console.log(person1.name, person2.name); // wangdachui, zhangsan
 
 person1.friends.push('songxiaobao');
 console.log(person2.friends); // lisi wangwu songxiaobao

[!DANGER]
缺点:

  • 对于引用类型的属性值始终都会共享相应的值,和原型链继承一样

2.2 寄生式继承

 // 创建一个用于封装继承过程的函数,这个函数在内部以某种形式来增强对象
 function createObj(o) {
 var clone = Object.create(o);
 clone.sayName = function() {
 console.log('say HelloWorld');
 }
 return clone;
 }

[!DANGER]
缺点:与借用构造函数模式一样,每次创建对象都会创建一遍方法

2.3 寄生组合式继承

2.3.1 基础版本

 function Parent(name) {
 this.name = name;
 this.colors = ['red', 'green', 'blue'];
 }
 
 Parent.prototype.getName = function() {
 console.log(this, name);
 }
 
 function Child(name, age) {
 Parent.call(this, name);
 this.age = age;
 }
 
 // test1:
 // 1. 设置子类实例的时候会调用父类的构造函数
 Child.prototype = new Parent();
 // 2. 创建子类实例的时候也会调用父类的构造函数
 var child1 = new Child('zhangsan', 18); // Parent.call(this, name);
 
 
 // 思考:如何减少父类构造函数的调用次数呢?
 var F = function(){};
 F.prototype = Parent.prototype;
 Child.prototype = new F();
 
 // 思考:下面的这一句话可以吗?
 /* 分析:因为此时Child.prototype和Parent.prototype此时指向的是同一个对象,
 因此部分数据相当于此时是共享的(引用)。
 比如此时增加 Child.prototype.testProp = 1; 
 同时会影响 Parent.prototype 的属性的。
 如果不模拟,直接上 es5 的话应该是下面这样吧
 Child.prototype = Object.create(Parent.prototype);*/
 Child.prototype = Parent.prototype;
 
 // 上面的三句话可以简化为下面的一句话
 Child.prototype = Object.create(Parent.prototype);
 
 
 
 // test2:
 var child2 = new Child('lisi', 24);

2.3.2 优化版本

 // 自封装一个继承的方法
 function object(o) {
 // 下面的三句话实际上就是类似于:var o = Object.create(o.prototype)
 function F(){};
 F.prototype = o.prototype;
 return new F();
 }
 
 function prototype(child, parent) {
 var prototype = object(parent.prototype);
 // 维护原型对象prototype里面的constructor属性
 prototype.constructor = child;
 child.prototype = prototype;
 }
 
 // 调用的时候
 prototype(Child, Parent)

3. JS创建对象的方法

  • 字面量创建
  • 构造函数创建
  • Object.create()
var o1 = {name: 'value'};
var o2 = new Object({name: 'value'});

var M = function() {this.name = 'o3'};
var o3 = new M();

var P = {name: 'o4'};
var o4 = Object.create(P)

4. 原型和原型链

4.1 原型

  1. JavaScript 的所有对象中都包含了一个 __proto__ 内部属性,这个属性所对应的就是该对象的原型
  2. JavaScript 的函数对象,除了原型 __proto__ 之外,还预置了 prototype 属性
  3. 当函数对象作为构造函数创建实例时,该 prototype 属性值将被作为实例对象的原型 __proto__。

4.2 原型链

  1. 任何一个实例对象通过原型链可以找到它对应的原型对象,原型对象上面的实例和方法都是实例所共享的。
  2. 一个对象在查找以一个方法或属性时,他会先在自己的对象上去找,找不到时,他会沿着原型链依次向上查找。

[!NOTE]
注意: 函数才有prototype,实例对象只有有__proto__, 而函数有的__proto__是因为函数是Function的实例对象

4.3 instanceof原理

[!NOTE]
判断实例对象的__proto__属性与构造函数的prototype是不是用一个引用。如果不是,他会沿着对象的__proto__向上查找的,直到顶端Object。

4.4 判断对象是哪个类的直接实例

[!NOTE]
使用对象.construcor直接可判断

4.5 构造函数,new时发生了什么?

 var obj = {}; 
 obj.__proto__ = Base.prototype;
 Base.call(obj); 
  1. 创建一个新的对象 obj;
  2. 将这个空对象的__proto__成员指向了Base函数对象prototype成员对象
  3. Base函数对象的this指针替换成obj, 相当于执行了Base.call(obj);
  4. 如果构造函数显示的返回一个对象,那么则这个实例为这个返回的对象。 否则返回这个新创建的对象

4.6 类

// 普通写法
function Animal() {
 this.name = 'name'
}

// ES6
class Animal2 {
 constructor () {
 this.name = 'name';
 }
}

喜欢小编的可以点个赞关注小编哦,小编每天都会给大家分享文章。

我自己是一名从事了多年的前端老程序员,小编为大家准备了新出的前端编程学习资料,免费分享给大家!

如果你也想学习前端,那么帮忙转发一下然后再关注小编后私信【1】可以得到我整理的这些前端资料了(私信方法:点击我头像进我主页有个上面有个私信按钮)

标签列表
最新留言