javascript 原型与原型链详细解析

JavaScript 原型与原型链详细解析

说明

JavaScript 是基于原型实现的面向对象,这篇文章整理了一下对象创建、原型、原型链、继承等的理解

文章内容

  1. 创建对象基础方法
  2. 原型与原型链详解
  3. 构造函数创建对象及实现继承的方法详解

创建对象基础方法

1. 对象字面量

创建对象最简单的方法

    var obj = {
        name: 'name',
        getName: function () {
            console.log(this.name);
        }
    }
2. 工厂方法

用于创建多个类型一致的对象

    function createObj(name) {
        return {
            name: name,
            getName: function () {
                console.log(this.name);
            }
        };
    }

    var obj1 = createObj('xiaobai');
    var obj2 = createObj('xiaolan');
    obj1.getName(); // xiaobai
    obj2.getName(); // xiaolan
3. 通过克隆其他对象

Object.create()

    var obj = {
        name: 'name',
        getName: function () {
            console.log(this.name);
        }
    }

    var o = Object.create(obj);
    o.name = 'name o';
    o.getName(); // name o
4. 构造函数方式

这里是比较原始的构造函数方式,后边会根据原型进行修改

    function Anm(name) {
        this.name = name;
        this.test = function () {
            console.log(this.name);
        }
    }

    var anm1 = new Anm('xiaobai');
    anm1.test(); // xiaobai

原型与原型链

javascript 中没有类,js 是基于原型来实现的面向对象

这里写图片描述

1. __proto__属性 与 prototype 属性

重点

  1. 每一个对象都由一个 __proto__的隐藏属性,这个属性指向了这个对象构造函数的原型对象
  2. 每一个函数都由一个 prototype的属性,这个属性指的就是这个函数的原型对象
  3. 原型对象也是一个对象,既然是对象就会有名为 __proto__ 的隐藏属性
  4. JavaScript 通过 __proto__ 链接形成了一条基于原型的链,原型链的尽头是 null
  5. javascript 中函数也是一个对象,既然是对象就会有 __proto__属性,函数的 __proto__属性指向他的构造函数的原型对象 (Function.prototype)
  6. ObjectFunction 也是构造函数,他们的原型对象有些特殊
2. 代码详解
1. 每一个对象都由一个 __proto__的隐藏属性,这个属性指向了这个对象构造函数的原型对象

检查一下是否有 __proto__ 属性(Chrome 开发者工具)

    // 字面量对象
    var o = {name: 'o'};
    console.log(o);

    // 构造函数创建的对象
    var Anm = function (name) {
        this.name = name;
    }

    var anm = new Anm('anm');

    console.log(anm); 

对象的__proto__属性

检查 __proto__ 是否指向构造函数的原型对象


    // 构造函数创建的对象
    var Anm = function (name) {
        this.name = name;
    }
    // 手动将构造函数 Anm 的原型对象至为一个对象
    Anm.prototype = {
        test: function () {}
    }

    var anm = new Anm('anm');

    console.log(anm.__proto__); // {test: function}
    // 查看是否指向 Anm.prototype
    console.log(anm.__proto__ === Anm.prototype); // true
2. 每一个函数都由一个 prototype的属性,这个属性指的就是这个函数的原型对象

通过控制台打印

    var Anm = function (name) {
        this.name = name;
    }

    Anm.prototype.test = function () {}
    var anm = new Anm('anm');
    console.log(Anm.prototype);

这里写图片描述

普通函数的 prototype

    function test() {
        console.log('test');
    }
    console.log(test.prototype);

这里写图片描述

可以看到 每个原型对象中都默认有一个属性 constructor,这个属性指向这个原型对象的构造函数

    console.log(Anm.prototype.constructor === Anm); // true
    console.log(test.prototype.constructor === test); // true
3. 原型对象也是一个对象,既然是对象就会有名为 __proto__ 的隐藏属性

接着上一个例子,我们在控制台打印原型对象时会看到原型对象除了 默认添加了 constructor 属性,同时还有 __proto__ 属性存在,前边讲过 __proto__指向创建当前对象的构造函数的 原型对象,下边具体测试一下。

    // 先找出构造 Anm 原型对象的构造函数
    console.log(Anm.prototype.__proto__.constructor); // function Object() { [native code] }
    // 从打印出的值来看 构造函数是 Object,下边测试一下
    console.log(Anm.prototype.__proto__ === Object.prototype); // true
4. JavaScript 通过 __proto__ 链接形成了一条基于原型的链,原型链的尽头是 null

当调用对象的某个属性或者方法时,如果这个对象没有这个属性或者方法,他会把调用请求委托给它自己的原型,也就是通过 __proto__将请求传递下去,如果它的原型有这个方法或者属性就会调用,否则继续向下传递

在 JavaScript 中一切皆对象,所以通过原型链传递,最后会传递到 Object.prototype 如果 Object.prototype 上页也没有需要的方法,则会最终传递给 Object.prototype.__proto__ === null

    // 创建父类
    var Anm = function (name) {
        this.name = name;
    }
    Anm.prototype.test = function () {
        console.log(this.color);
    }

    // 创建子类
    var Cat = function (color) {
        this.color = color;
    }
    // 子类的原型指向父类的一个实例;
    Cat.prototype = new Anm('cat');
    // 将子类原型的构造函数指向自己
    Cat.prototype.constructor = Cat;

    var cat = new Cat('red');
    console.log(cat);

    // 可以用 对象的 hasOwnProperty 方法判断一个对象是否含有某个属性
    // 调用 test 方法
    cat.test(); // red
    // 查看 cat 对象是否有 test 方法
    console.log(cat.hasOwnProperty('test')); // false 
    // 查看 cat 的原型对象是否有 test 方法
    console.log(cat.__proto__.hasOwnProperty('test')); // false 
    // 查看 cat 的原型对象 的原型对象是否有 test 方法
    console.log(cat.__proto__.__proto__.hasOwnProperty('test')); // true

    // 原型链的尽头
    console.log(Object.prototype.__proto__) // null

这里写图片描述

5. javascript 中函数也是一个对象,既然是对象就会有 __proto__属性,函数的 __proto__属性指向他的构造函数的原型对象 (Function.prototype)

控制台打印普通函数的原型

    var test = function () {
        console.log('test');
    }

    console.log(test.__proto__); // function () { [native code] }

function () { [native code] } 这样不方便查看,所以我换了一种方式,对象中的方法也是一个函数,直接在控制台查看

    var o = {
        test: function () {
            console.log('test');
        }
    }

    console.log(o); 

这里写图片描述

可以看到 test 函数是有 __proto__属性的,它的 constructor 指向 Function,下边来证实一下

    console.log(o.test.__proto__.constructor === Function); // true
    console.log(o.test.__proto__ === Function.prototype); // true
6. ObjectFunction 也是构造函数,他们的原型对象有些特殊

文章的开始有一个原型链的表示图,在javascript 中无论是通过构造函数还是还是通过字面量方法创建的对象都遵守以上规则,但是 ObjectFunction 作为比较底层的,与其他对象或者函数有一些区别

    var f = function () {};
    // 函数的 __proto__ 指向 Function.prototype
    console.log(f.__proto__ === Function.prototype); // true
    // Function 的原型对象 指向他自己的 __proto__
    console.log(Function.prototype === Function.__proto__); // true
    // Function 原型的 __proto__ 指向 Object.prototype
    console.log(Function.prototype.__proto__ === Object.prototype); // true
    // Object 原型的 __proto__ 指向 null
    console.log(Object.prototype.__proto__ === null); // true
    // Object 也是构造函数所有他的 __proto__ 指向 Function.prototype
    console.log(Object.__proto__ === Function.prototype ); // true

这部分有点绕,这里有一张图说明Javascript的原型链图

构造函数创建对象及实现继承的方法详解

1. 组合式继承
    // 创建父类
    var Anm = function (type) {
        // 创建属性
        this.type = type;
    }
    // 原型上定义方法,这样子类、创建的对象就都可以使用这个方法
    Anm.prototype.getType = function () {
        console.log(this.type);
    }

    // 创建子类
    var Cat = function (name) {
        // 私有属性
        var catType = 'cat';

        // 通过改变this指针的指向获得使用父类函数中定义的属性
        Anm.call(this, catType);
        // Anm.apply(this, arguments);

        this.name = name;
    }

    // 将子类的原型转向一个父类的实例
    Cat.prototype = new Anm();

    // 将 Cat 原型的 constructor 构造函数重新指向自己
    Cat.prototype.constructor = Cat;

    // 定义子类的方法
    Cat.prototype.getName = function () {
        console.log(this.name);
    }

    var cat = new Cat('xiaobai');

    cat.getName(); // xiaobai
    cat.getType(); // cat
2. 其他的方式实现继承
    // 定义一个继承函数
    function extend (subClass, supClass) {
        // 定义一个临时函数
        var F = function () {};
        // 临时函数的原型指向父类的原型
        F.prototype = supClass.prototype;

        subClass.prototype = new F();

        subClass.prototype.constructor = subClass;

    }

    var Anm = function () {};
    Anm.prototype.getName = function () {
        console.log(this.name);
    }

    var Cat = function (name) {
        this.name = name;
    };

    extend(Cat, Anm);

    var cat = new Cat('xiaobai');

    cat.getName(); // xiaobai

其他

1. Object.create()创建对象的问题

Object.create() 复制的对象会将被复制的对象作为新对象的 proto

    var Anm = function () {};
    Anm.prototype.getName = function () {
        console.log(this.name);
    }

    var Cat = function (name) {
        this.name = name;
    };

    Cat.prototype = new Anm();
    Cat.prototype.constructor = Cat;

    var cat = new Cat('xiaobai');

    // 使用 Object.create() 复制对象
    var dog = Object.create(cat);

    dog.getName(); // xiaobai
    // 新对象的原型指向被复制的对象
    console.log(dog.__proto__ === cat); // true
    // 新对象的原型上没有 constructor 属性,这个属性是从被复制对象的原型上继承的
    console.log(dog.__proto__.constructor === Anm); // true
    console.log(dog.__proto__.hasOwnProperty('constructor')); //false
    console.log(dog.__proto__.__proto__.hasOwnProperty('constructor')); // true
2. ES6 Class

ECMAScript 6 带来了新的 Class 语法,这让javascript 看起来像是一门基于类的语言,但其背后仍是通过原型机制来创建对象

    class Anm {
        constructor(type) {
            this.type = type;
        }

        getType() {
            console.log(this.type);
        }
    }

    class Cat extends Anm {
        constructor(name) {
            const catType = 'cat';

            super(catType);

            this.name = name;
        }

        getName() {
            console.log(this.name);
        }
    }

    var cat = new Cat('xiaobai');

    console.log(cat); // {type: "cat", name: "xiaobai"}
    console.log(cat.__proto__ === Cat.prototype); // true
    console.log(cat.__proto__.constructor === Cat); // true
    console.log(cat.__proto__.__proto__ === Anm.prototype); // true
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值