• 深入理解:JavaScript原型与继承


    深入理解:JavaScript原型与继承

    看过不少书籍,不少文章,对于原型与继承的说明基本上让人不明觉厉,特别是对于习惯了面向对象编程的人来说更难理解,这里我就给大家说说我的理解。

    首先JavaScript是一门基于原型编程的语言,它遵守原型编程的基本原则:

    1. 所有的数据都是对象(javascript中除了字符串字面量、数字字面量、true、false、null、undefined之外,其他值都是对象!)
    2. 要得到一个对象,不是通过实例化类,而是找到一个对象作为原型并克隆它
    3. 对象会记住它的原型
    4. 如果对象无法响应某个请求,它会把该请求委托给它自己的原型

    这么说来,JavaScript一定有一个根对象,所有的对象的最终原型都将是它,它就是Object.prototypeObject.prototype也是一个对象,它是一个空的对象。(记住一点:所有的原型都是对象,但不是函数,虽然函数也是对象,Object对象其实就是一个函数)

    以下代码创建空对象:

    var obj1 = new Object();
    var obj2 = {};
    Object.getPrototypeOf(obj1) === Object.prototype; //true
    Object.getPrototypeOf(obj2) === Object.prototype; //true
    

    我们再来看下以下代码:

    function Book(name){
        this.name = name;
    }
    Book.prototype.getName = function(){
        return this.name;
    }
    Book.num = 5;
    var book1 = new Book('javascript权威指南');
    book1.getName(); //javascript权威指南
    Object.getPrototypeOf(book1) === Book.prototype; //true
    

    我们通常说,使用了new Book()来创建了Book的实例book1,但是JavaScript并没有类的概念,这里的Book本质上只是一个函数而已,如果用new则把它当着构造函数对待,那么var book1 = new Book('javascript权威指南')是怎么个执行过程呢?在这之前,我们来先看下Function与Object的关系

    这里有张图:
    图

    Function与Object的关系

    console.log(Function); //[Function: Function]
    console.log(Function.constructor); //[Function: Function]
    console.log(Function.__proto__); //[Function]
    console.log(Function.prototype); //[Function]
    console.log(Function.constructor.prototype); //[Function]
    
    console.log(Object.__proto__); //[Function]
    console.log(Object.prototype); //{}
    console.log(Object.constructor); //[Function: Function]
    console.log(Object.constructor.prototype); //[Function]
    

    在JavaScript中:Function和Object就像是女娲和伏羲,Object提供种子(Object.prototype),Function负责繁衍。Object是由Object.prototype提供的种子经过Function打造出来的。Object.prototype会被一直继承下去,并一代一代增强。

    1. 每一个函数都有一个原型对象(prototype)和隐藏的__proto__属性,函数的__proto__属性指向Function.prototype,而原型对象(prototype)是一个对象,符合以下第2点(也有构造函数constructor和隐藏的__proto__属性);
    2. 每一个非函数对象(实例对象)都有一个构造函数(constructor)和隐藏的__proto__属性,constructor自然指的是它的构造函数,而__proto__指向的是它的构造函数的原型对象prototype
    3. 通过__proto__属性,每个对象和函数都会记住它的原型,这样就形成了原型链;
    console.log(Book); //{ [Function: Book] num: 5 }
    console.log(Book.__proto__); //[Function]
    console.log(Book.prototype.__proto__); //{} 等于Object.prototype
    console.log(Book.prototype); //Book { getName: [Function] }
    console.log(Book.constructor); //[Function: Function]
    console.log(Book.prototype.constructor); //{ [Function: Book] num: 5 }
    console.log(Book.constructor.prototype); //[Function]
    console.log(Book.__proto__.__proto__); //{} 等于Object.prototype
    

    每一个函数都是通过Function构造出来的,函数的原型属性__proto__指向Function.prototype,而函数的原型对象prototype是代表着自身的函数对象,它的__proto__属性指向Object.prototype,它的constructor属性默认指向它自己构造函数(也可改为别的函数,如:Book.prototype.constructor = Person;)。

    console.log(book1); //Book { name: 'javascript权威指南' }
    console.log(book1.__proto__); //Book { getName: [Function] }
    console.log(book1.prototype); //undefined
    console.log(book1.constructor); //{ [Function: Book] num: 5 }
    console.log(book1.constructor.prototype); //Book { getName: [Function] }
    

    所以,我们通常说‘一个对象的原型’其实是不准确的,应该是‘一个对象的构造器的原型’,且对象是把它无法响应的请求委托给它的构造器的原型顺着原型链往上传递的。

    现在来讲解一下var book1 = new Book('javascript权威指南')的执行过程,是这样:new先从Object.prototype克隆一个空对象,先将空对象的构造函数指定为Book,然后将空对象的__proto__指向它的构造函数的原型Book.prototype,之后通过Book构造函数对这个空对象进行赋值操作,并将这个对象返回给变量book1。

    我们再看如下代码:

    var obj = {name: 'Sufu'};
    var Person = function(){};
    Person.prototype = obj;
    var a = new Person();
    console.log(a.name); //Sufu
    

    这段是这样执行的:

    1. 首先尝试查找对象a的name属性,找不到,执行第2步
    2. 将查找name属性的这个请求委托给a的构造器的原型,a.__proto__记录的是Person.prototype,Person.prototype为对象obj
    3. 在对象obj中查找name,找到并返回它的值给a,假如还找不到,它就通过obj.__proto__找到Object.prototype,找不到就返回undefined,因为Object.prototype的原型不存在了,为null

    好了,就介绍到这里了,以上是个人的理解,有不对的地方欢迎指出!

    参考文献:《javascript权威指南》

  • 相关阅读:
    css常用字体
    多行文本显示省略号,点击展开隐藏
    某个公司采用公用电话传递数据,数据是四位的整数,在传递过程中是加密的, 加密规则如下:每位数字都加上5,然后用除以10的余数代替该数字,再将第一位和第四位交换, 第二位和第三位交换,请编写一个函数,传入原文,输出密文
    编写一个函数,计算任意两个数字之间所能组成的奇数个数,数字必须是个位数。 比如:计算0~3之间能组成的奇数是: 01、03、13、21、23、31
    Redis(一) 数据结构与底层存储 & 事务 & 持久化 & lua
    IO多路复用之Reactor
    IO多路复用之select poll epoll
    五种IO模型
    kafka(五) 流式处理 kafka stream
    kafka(二) 高性能技术分析
  • 原文地址:https://www.cnblogs.com/susufufu/p/5815675.html
Copyright © 2020-2023  润新知