zoukankan      html  css  js  c++  java
  • JavaScript原型链及继承

    在JavaScript中,所有的东西都是对象,但是JavaScript中的面向对象并不是面向类,而是面向原型的,这是与C++、Java等面向对象语言的区别,比较容易混淆,因此把我自己学习的过程记录下来。

    首先说,原型链有什么用?在Java中,继承都是基于类的,在JavaScript中继承都是基于原型链的。也就是说在JavaScript中,原型链是实现继承的基础,想要掌握好JavaScript中的面向对象编程,必须对原型链有一定的了解。

    要理解原型链,必须先了解两个对象,一个是 prototype ,另一个是 __proto__ 。当前只需要记住名字,下面会仔细说明。

    首先是 prototype : prototype ,或者叫原型对象,是函数特有的一个属性,其类型是 Object ,因此也常常被称作函数的原型对象。虽然每个函数都拥有自己的原型对象,但只有用作构造函数时,这个属性才会发挥作用,关于构造函数的知识这里不说。原型对象其实很简单,他就是一个普通的 Object ,当其作为构造函数时默认有一个 constructor 。

    1 // 举个简单的例子
    2 function fn() { }
    3 
    4 console.log(fn.prototype);  // {}

    我们可以给原型对象添加一些方法或属性,就可以被其子类继承:

     1 // 后面会讲怎么继承
     2 function fn() { }
     3 
     4 // 添加一个方法
     5 fn.prototype.sayHello = function() {
     6     alert('hello');
     7 };
     8 
     9 // 添加一个属性
    10 fn.prototype.name = 'my_fn';

     上面就完成了对原型对象的介绍,接下来是 __proto__ ,这是一个所有对象都拥有的属性。其实 __proto__ 与原型对象密不可分,因为一个对象的 __proto__ 就是指向其构造函数的原型对象。需要注意的是 __proto__ 并不是JavaScript的规范,只是大多数浏览器都实现了,从ECMAScript 6开始,应该用Object.getPrototypeOf()和Object.setPrototypeOf()来访问这个属性。看一个例子:

    1 function fn() { }
    2 
    3 var f = new fn();
    4 console.log(f.__proto__ === fn.prototype);  // true

     从上面的代码,应该就可以明白这二者的关系了,如果能理解这一点,接下来就可以开始分析继承的实现原理了。

    开头说过,继承是基于原型链实现的,那么什么是原型链呢?首先我们看几个例子:

     1 function fn() { }
     2 
     3 // 首先记住,一个对象的__proto__指向它的构造函数的原型对象
     4 var f = new fn();
     5 console.log(f instanceof Object);   // true,说明此时f是一个Object
     6 console.log(f.__proto__ === fn.prototype);   // true,没毛病,因为f的构造函数就是fn
     7 var obj = fn.prototype; // 我们看看fn的原型对象是什么类型?肯定是对象!
     8 console.log(obj.__proto__ === Object.prototype);    // true,那对象就是Object,它的构造函数就是Object
     9 obj = Object.prototype; // 那Object的原型对象应该也是个对象吧
    10 console.log(obj.__proto__ === null);    // true,为什么是null?这是JavaScript设计的,因为如果不是null,就会无限循环

    以上的例子说明了f的构造函数的原型,f的构造函数的原型的原型,f的构造函数的原型的原型的原型,用图形表示就是:

    f.__proto__ ---> f.__proto__.__proto__ ---> f.__proto__.__proto__.__proto__

    那么上面这条“链”就是我们所说的原型链了!这个过程理解了可以继续往下。

    那么原型链是如何实现继承的?在JavaScript中,你对一个对象调用一个方法或者获取一个属性,它就会自动的在原型链上面寻找,一直到找到或者原型对象为null。

     1 // 再举个例子
     2 function fn() {}
     3 
     4 var f = new fn();
     5 
     6 // 此时fn并没有方法toString
     7 console.log(f.toString()); // 输出[object Object]
     8 // 为什么?
     9 // 按照刚刚分析的原型链,它会现在fn.prototype中寻找
    10 console.log(fn.prototype);  // fn {},没有
    11 // 再在fn.prototype.__proto__(Object.prototype)中寻找
    12 console.log(fn.prototype.__proto__);

    上面这个例子说明,f的 toString 方法其实是从 Object.prototype 继承而来的。

    如果上面这些都能明白,那我们就可以自己实现继承了。

     1 // 回到前面的例子
     2 function SuperClass() {
     3     this.time = new Date().toLocaleString();
     4 }
     5 
     6 // // 添加一个方法
     7 SuperClass.prototype.sayHello = function() {
     8     console.log('hello');
     9 };
    10 
    11 // // 添加一个属性
    12 SuperClass.prototype.name = 'super';
    13 
    14 // 定义一个子类继承SuperClass
    15 function SubClass() {
    16     // 在子类的构造函数中调用父类的构造函数
    17     SuperClass.call(this);
    18 }
    19 
    20 SubClass.prototype = Object.create(SuperClass.prototype);  // 继承父类的属性和方法
    21 // SubClass.prototype = SuperClass.prototype;  // 不能直接赋值!因为JavaScript中的对象赋值都是浅复制,有副作用,也就是说修改子类的原型也会修改父类
    22 // SubClass.prototype = new SuperClass();   // 也不要这样做,因为这样会实例化一个SuperClass,假如SuperClass的构造函数中定义了一个很大的对象,就会造成内存浪费!
    23 SubClass.constructor = SubClass;    // 上一行代码会把子类的构造函数给覆盖了,这里把它恢复了。注意:constructor会影响instanceof运算符的结果
    24 
    25 SubClass.prototype.subfn = function() {
    26     console.log('this is a sub func');
    27 }
    28 
    29 var sc = new SubClass();
    30 console.log(sc.time);   // 2018-12-18 22:02:09
    31 sc.sayHello();  // hello
    32 sc.subfn(); // this is a sub func

     上面就是一个简单的继承,要实现多重继承也是类似的:

     1 // 在上面的代码修改
     2 // 另一个父类
     3 function SuperClassB() {}
     4 SuperClassB.prototype.anotherfn = function() {
     5     console.log('another super');
     6 }
     7 
     8 // ...
     9 
    10 // 定义一个子类继承SuperClass
    11 function SubClass() {
    12     // 在子类的构造函数中调用父类的构造函数
    13     SuperClass.call(this);
    14     SuperClassB.call(this); // 添加
    15 }
    16 
    17 var prototype = Object.create(SuperClass.prototype);  // 继承父类的属性和方法
    18 prototype = Object.assign(prototype, SuperClassB.prototype);  // 合并两个父类的原型对象
    19 SubClass.prototype = prototype  // 继承父类的属性和方法
    20 
    21 // ...
    22 
    23 sc.anotherfn(); // another super
  • 相关阅读:
    单链表相关笔试题:单链表逆转,约瑟夫环等
    cURL安装和使用笔记
    WSDL中文版——详解
    [c++语法]类
    spark源码解析之基本概念
    apache-spark导入eclipse环境
    zookeeper应用实例
    spring容器加载完毕做一件事情(利用ContextRefreshedEvent事件)
    Let's do our own full blown HTTP server with Netty--转载
    1号店11.11:从应用架构落地点谈高可用高并发高性能--转载
  • 原文地址:https://www.cnblogs.com/zhuangshq/p/10140507.html
Copyright © 2011-2022 走看看