理解JavaScript中的原型与原型链

理解JavaScript中的原型与原型链

原型链是一种机制,指的是JavaScript中每个内置的对象都有一个内置的__proto__属性指向创建它的构造函数的prototype(原型)属性。原型链的作用是为了实现对象的继承,要理解原型链,我们就要先理解对象constructornewprototype__proto__这五个概念。
对象
JavaScript 中,一切(引用类型)都是对象,(对象是属性的集合)但对象也是有区别的。分为普通对象和函数对象:

普通对象:
var o1 = {};
var o2 = new Object();
函数对象:
function f1(){};
var f2 = function(){};
var f3 = new Function('str','console.log(str)');

简单的说,凡是使用 function 关键字或 Function 构造函数创建的对象都是函数对象。而且,只有函数对象才拥有 prototype (原型)属性。


new操作符
要创建 Person 的新实例,必须使用 new 操作符。
以这种方式调用构造函数实际上会经历以下4个步骤:

  1. 创建一个新对象;
  2. 将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象);
  3. 执行构造函数中的代码(为这个新对象添加属性);
  4. 返回新对象。

constructor构造函数
构造函数就是一个普通的函数,创建方式上构造函数习惯上首字母大写。另外就是调用方式的不同,普通函数是直接调用,而构造函数需要使用new关键字来调用。

    function Person(name, age, gender) {
        this.name = name
        this.age = age
        this.gender = gender
        this.sayName = function () {
            alert(this.name);
        }
    }
    var per1 = new Person("旺财", 18, "男");
    var per2 = new Person("小强", 11, "女");
    console.log(per1);
    console.log(per2);
 console.log(per1.sayName===per2.sayName);//false

但是构造函数有个问题,如上面这一段代码,每创建一个Person构造函数,在Person构造函数中,为每一个对象都添加了一个sayName方法,也就是说构造函数每执行一次就会创建一个新的sayName方法。这样就导致了构造函数执行一次就会创建一个新的方法,而且方法都是一摸一样的,为什么不把这个方法单独放到一个地方,并让所有的实例都可以访问到呢?这就需要原型。


prototype原型
每个函数function都有一个prototype属性,而这个prototype的属性值是一个对象。
在这里插入图片描述

原型对象就相当于一个公共的区域,所有同一个类的实例都可以访问到这个原型对象,我们可以将对象中共有的内容,统一设置到原型对象中。
上面的代码我们一般这样写。

    function Person(name, age, gender) {
        this.name = name
        this.age = age
        this.gender = gender
       
    }
    Person.prototype.sayName = function () {
            alert(this.name);
        }
    var per1 = new Person("旺财", 18, "男");
    var per2 = new Person("小强", 11, "女");
    console.log(per1);
    console.log(per2);
 console.log(per1.sayName===per2.sayName);//true

__proto__隐式原型
每个对象都有一个__proto__
为什么在构造函数的 prototype 中定义了属性和方法,它的实例中就能访问呢?
那是因为当调用构造函数创建一个新实例后,该实例的内部将包含一个指针 __ proto __,指向构造函数的原型prototype。

function Person(){}
var person = new Person();
console.log(person.__proto__ === Person.prototype); // true
function Person(){}

// var person = new Person(); 
// 上一行代码等同于以下过程 ==> 
var person = {};
person.__proto__ = Person.prototype;
Person.call(person);

这个例子中,我先创建了一个空对象 person,然后把 person.__ proto__ 指向了 Person 的原型对象,便继承了 Person 原型对象中的所有属性和方法,最后又以 person 为作用域执行了 Person 函数,person 便就拥有了 Person 的所有属性和方法。这个过程和 var person = new Person(); 完全一样。
简单来说,当我们访问一个对象的属性时,如果这个属性不存在,那么就会去 __ proto __ 里找,这个 __ proto __ 又会有自己的 __ proto ,于是就这样一直找下去,直到找到为止。在找不到的情况下,搜索过程总是要一环一环地前行到原型链末端才会停下来。自定义函数的prototype。自定义函数的prototype是被Object创建,所以它的 proto__指向的就是Object.prototype。但是Object.prototype确实一个特例——它的__ proto__指向的是null。


原型链
JavaScript 中描述了原型链的概念,并将原型链作为实现继承的主要方法。其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。

function Father(){
    this.value = true;
}
Father.prototype.getValue = function(){
    return this.value;
};

function Son(){
    this.value2 = false;
}

// 继承了 Father
// Son.prototype = new Father(); ==>
Son.prototype = {};
Son.prototype.__proto__ = Father.prototype;
Father.call(Son.prototype);

Son.prototype.getValue2 = function (){
    return this.value2;
};

// var son = new Son(); ==>
var son = {};
son.__proto__ = Son.prototype;
Son.call(son);

console.log(son.getValue()); // true
console.log(son.getValue === son.__proto__.__proto__.getValue); // true

从以上代码可以看出,实例 son 调用 getValue() 方法,实际是经过了 son.proto.proto.getValue 的过程的,其中 son.proto 等于 Son.prototype,而 Son.prototype.proto 又等于 Father.prototype,所以 son.proto.proto.getValue 其实就是 Father.prototype.getValue。
事实上,前面例子中展示的原型链还少一环。我们知道,所有引用类型默然都继承了 Obeject,而这个继承也是通过原型链实现的。大家要记住,所有函数的默认原型都是 Object 的实例,因此默认原型都会包含一个内部指针 proto,指向 Object.prototype。这也正是所有自定义类型都会继承 toString()、valueOf() 等默认方法的根本原因。


可以阅读博客园王福彭对JavaScript基础的全面讲解

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值