对于JavaScript prototype的理解

对于事物的理解,往往需要涵盖三个方面:它是什么样子的?它为何会出现?它的外延是什么?理解了内涵与外延才算真正的掌握。

一、原型是什么

以对象a为例,a内部除了固有的属性外,还有一个隐藏的链接指向另一个对象b,这个b就叫作a的原型(也叫原型对象);同时b也有隐藏链接指向c,以此类推,最终指向的是Object.prototype,以上也解释了什么是原型链。可以通过Object.getPrototypeOf(a)来获取a对象的原型。

1.1 令人困惑的prototype

原型之所以使人困惑是因为人们往往把它和prototype属性混在一起,其实这是命名上的失败,对象的原型不是它的属性,而是它的特性。为了方便说明,我将对象的原型称为“原型”使之区别于prototype属性,但两者之间又是紧密相连的。(每个JavaScript对象都有个__proto__属性指向对象的原型,不过__proto__是在ECMAScript 2015中被新加入的属性,而该属性的使用也存在争议)。

1.2 prototype属性

每个JavaScript方法都有一个prototype属性;当你定义一个方法var A = function() {}时,实际上它等价于以下代码:

function A() {
}

// obj是一个新对象,且obj.constructor = A,obj的原型为Object.prototype
A.prototype = obj;

也就是说,当你创建了一个方法的同时,JavaScript会创建一个新对象赋值给方法的prototype属性,并将新对象的constructor指向方法A,如下图所示:

"方法定义过程"

1.2.1 Object.prototype

前面已经说过每个JavaScript方法都有一个prototype属性,所以Object也是一个方法,从使用角度看是一个构造方法。Object.prototype是所有JavaScript对象原型链的最末端(除特殊情况:人为的将原型设置为null)。此外需要说明的是:从JavaScript的语法角度看,并没有构造方法的定义,人们只是根据用途将某些方法称为构造方法,本质上构造方法与一般的方法并没有区别。

二、原型的应用

2.1 被人们忽视的原型

如果你一直从事轻前端重后端的web项目,一直在编写意大利面条式的代码,且这种代码还能满足开发与维护的需求,你确实不需要了解原型。当你发觉JavaScript的代码量在项目中急速增长,代码逐渐变的难以阅读、难以维护时,你开始寻找能避免代码重复的方案,首先你想到的应该是—继承。JavaScript通过原型可以达到这个目的。

2.2 原型的特性

原型最基本的作用就是用来检索对象的属性,当检索某个对象的属性时,首先会检索对象本身的属性,如果本身没有该属性,就去检索对象的原型是否存在该属性,如果仍旧没有找到,就去检索原型的原型,以此类推,沿着原型链一路检索,直到原型链的最后一环Object.prototype,如果最终还是没有找到,就会返回undefined。

2.3 创建对象

在JavaScript中创建对象有以下三种方式:
+ var a = {};
+ var a = Object.create(b);
+ var a = new A();

很显然只有第三种方式才符合OOP的思想,因为它体现了class(类)的概念。在JavaScript的语法层次是没有class的定义的,但是可以通过function来模拟class的行为,通过原型来实现继承。

2.4 JavaScript中的“class”

JavaScript通过function来模拟class,示例代码如下:

var A = function(name, age) {

    // init property 
    this.name = name;
    this.age = age;

    // default property 
    this.attr = "attr";

    // default function
    this.fun = function() {
    };
};

var a1 = new A("li", 20);
var a2 = new A("zhang", 21);

通过new操作符,对象a1、a2就拥有了A方法中this所拥有的属性和方法。

2.5 JavaScript中的继承

JavaScript是通过原型来实现继承的,示例代码如下:

// define Parent class  
var Parent = function() {
    this.pName = "li";
    this.pFun = function() {
        console.log("parent's fun");
    };
};

// define Child class
var Child = function() {
    this.cName = "xiaoli";
    this.cFun = function() {
        console.log("child's fun");
    };
};

// Child inherit Parent
Child.prototype = new Parent();

// create an object of Child
var c = new Child();

// print: parent's fun
c.pFun();

new操作的内存分析:

  1. 当执行var c = new Child();时,首先新建了一个空对象

    "内存分析-1"
  2. 将Child方法中的this指向新对象,通过this进行对象初始化

    "内存分析-2"
  3. 将Child的prototype属性(new Parent())作为新对象的原型

    "内存分析-3"

2.6 prototype-based(基于原型)的继承 VS class-based(基于类)的继承

class-based语言中,同一个类产生的多个对象之间相互独立、互不干扰;而prototype-based语言中同一个“类”产生的多个对象通过原型捆绑在一起,代码示例如下:

var A = function() {
};
A.prototype = {x : 1};

var a1 = new A();
var a2 = new A();

// change prototype object
A.prototype.x = 2;

// print: 2
console.log(a1.x);

// print: 2
console.log(a2.x);

因为a1、a2的原型为同一个对象,所以当改变原型对象时,a1、a2都受影响;这个例子同时展示了JavaScript作为动态语言的灵活性,在运行时改变“类”的结构,使通过该类产生的对象均受影响。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值