原型和原型链也是一个老生常谈的问题,很多初学者对于__proto__和prototype搞不清楚。其实我刚开始学习的时候也搞不清楚,因此写这篇文章对于这方面的知识进行一个巩固。
作者:一木
链接:http://www.lofter.com/lpost/1f0227be_1106b61c
来源:LOFTER
在说原型和原型链之前,我们先来看看如下知识点:
一、私有变量和私有函数
在函数内部定义的变量和函数,如果不对外提供接口,那么外部是无法访问到的。这个被定义的变量和函数就叫做该函数的私有变量和私有函数。
function Foo() {
var name = "yiMu"; //私有变量
var fn = function() { //私有函数
console.log("hello word");
};
}
var bar = new Foo();
console.log(bar.name); //undefined
console.log(bar.fn); //undefined
二、静态变量和静态函数
当定义一个函数后通过"."的方式为其添加属性和函数,通过对象本身可以访问到,但是其实例却无法访问到,这样的变量和函数叫做静态变量和静态函数。
function Foo(){}
Foo.num = 10; //静态变量
Foo.fn = function() { //静态函数
console.log("hello word");
};
console.log(Foo.num); //10
console.log(typeof Foo.fn); //function
var bar = new Foo();
console.log(bar.num); //undefined
console.log(typeof bar.fn); //undefined
三、实例属性和实例方法
在面向对象编程中除了一些库函数,我们还是希望在定义一个对象的时候同时定义一些属性和方法并在实例化后能够访问,这些添加的属性和方法就叫做实例属性和实例方法。
function Foo() {
this.num = []; //实例属性
this.fn = function() { //实例方法
console.log("hello word");
};
}
console.log(Foo.num); //undefined
console.log(typeof Foo.fn); //undefined
var bar = new Foo();
console.log(bar.num); //[]
console.log(typeof bar.fn); //function
我们也可以为实例属性和实例方法添加属性和方法:
function Foo() {
this.num = []; //实例属性
this.fn = function() { //实例方法
console.log("hello word");
}
}
var oneBar = new Foo();
oneBar.num.push(1);
oneBar.fn = {};
console.log(oneBar.num); //[1]
console.log(typeof oneBar.fn); //Object
var twoBar = new Foo();
console.log(twoBar.num); //[]
console.log(typeof twoBar.fn); //function
从上面的代码可以看到,当我们在oneBar中修改了num的值和fn的类型,但是在twoBar中却没有发生改变,这是由于数组和函数都是对象,属于引用类型。oneBar和twoBar中的属性和方法名称虽然相同但是却不是同一个引用,它们只是对Foo对象定义的属性和方法的一个复制。
如果一个构造函数对象有上千的实例方法,那么它的每个实例都要对这个构造函数的上千个实例方法进行复制,这显然是不科学的,那么这种情况下我们就必须使用prototype了。
基本概念
我们创建的每一个函数都有一个prototype属性,这个属性是一个指针,指向一个对象,这个对象的用途是可以由特定类型的所有实例共享的属性和方法,那么prototype就是通过调用构造函数而创建的那个对象实例的原型对象。
使用原型的好处就是可以让对象实例共享它所包含的属性和方法。
在JS中,一共有两种类型的值——原始值和对象值。每个对象都有一个内部属性prototype,我们称之为原型。原型的值可以是一个对象,也可以是null。如果它的值是一个对象,那么这个对象一定也有自己的原型,这样就形成了一条线性的链,我们称之为原型链。
prototype和__proto__的区别
函数可以作为构造函数来使用,只有函数的prototype属性才可以访问到,但是对象实例不具有该属性,只有一个内部的不可访问的__proto__属性,如下图:
图片来自于水乙博客
var a = {};
console.log(a.prototype); //undefined
console.log(a.__proto__); //Object{}
var b = function(){};
console.log(b.prototype); //Object{constructor: function, __proto__: Object}
console.log(b.__proto__); //function(){}
图片来自于水乙博客
//字面量方式
var a = {};
console.log(a.__proto__); //Object{}
console.log(a.__proto__ === a.constructor.prototype); //true
//构造器方式
var C = function(){};
var d = new C();
console.log(d.__proto__); //C{}
console.log(d.__proto__ === C.prototype); //true
console.log(d.__proto__ === d.constructor.prototype); //true
//Object.create方式
var e = {name: "qtb"};
var f = Object.create(e);
console.log(f.__proto__); //Object{name: "qtb"}
console.log(f.__proto__ === f.constructor.prototype); //false(此处即为图1中例外情况)
图片来自于水乙博客
var A = function(){};
var a = new A();
console.log(a.__proto__); //A{}(即构造器function A 的原型对象)
console.log(a.__proto__.__proto__); //Object{}(即构造器function Object 的原型对象)
console.log(a.__proto__.__proto__.__proto__); //null
构造函数、实例和原型对象的区别
实例就是通过构造函数创建的。实例一创建出来就具有constructor属性(指向构造函数)和__proto__属性(指向原型对象)。构造函数中有一个prototype属性,这个属性是一个指针,指向它的原型对象。
原型对象内部也有一个指针(constructor属性),指向构造函数Person.prototype.constructor = Person。
实例可以访问原型对象上定义的属性和方法。
如果你在本文中发现错误或者有异议的地方,可以在评论区留言,谢谢!