参考文献

每一个函数都有一个 prototype 属性,这个属性是指向一个对象的引用,称为 原型对象原型对象中的属性和方法,可以被函数 实例 所继承和共享。

一、私有变量、函数

JavaScript中没有命名空间的概念,同时JavaScript支持函数嵌套定义。但是,如果函数内部定义的变量或函数不对外提供接口,那么外部将无法访问

/**
 * obj中的变量和函数都是私有的,外部无法访问
 */
function obj(params) {
    var a = 0;  // 私有变量
    var fn = function(){};  // 私有方法
}

var o = new obj();
console.log(o.a);   // undefined
console.log(o.fn);   // undefined

二、静态变量和函数

通过 . 操作符,添加的函数或变量,可以通过对象本身访问到,但是其实例对象,仍然访问不到。(用过java的同学很好理解)

/**
 * 对象可以访问
 */
function obj(){}

obj.a = 0;  // 静态变量
obj.fn = function(){};  // 静态方法

console.log(a);     // 0
console.log(typeof obj.fn); // function

/**
 * 实例不能访问
 */
var vm = new obj();
console.log(vm.a);  // undefined
console.log(typeof vm.fn); // undefined

三、实例变量和函数

在面向对象编程的概念中,我们还是需要在对象在定义的时候能同时定义一些属性和方法,实例化之后,可以访问。

/**
 * 对象访问不到
 */
function obj (){
    this.a = [];    // 实例变量
    this.fn = function(){}; // 实例方法
}

console.log(typeof obj.a);  //undefined
console.log(typeof obj.fn);     // undefined

/**
 * 对象实例可以访问到
 */
var vm = new obj();

console.log(typeof vm.a); // object
console.log(typeof vm.fn);  // function

然而会遇到如下问题:

/**
 * 实例属性不一样
 */
function obj (){
    this.a = [];    // 实例变量
    this.fn = function(){};  // 实例方法
}

var vm1 = new obj();
var vm2 = new obj();

vm1.a.push(1);
vm1.fn = {};
console.log(vm1.a); // [1]
console.log(typeof vm1.fn); // object
console.log(vm2.a); // []
console.log(typeof vm2.fn); // function

从以上代码可以看出,虽然两个实例对象的属性和方法名称是一样的(继承自函数对象),但是是一个复制。对于属性来说我们可以接受。但是对于方法来说,没有必要(如果定义10000个实例,就有10000个copy)。

四、prototype

无论什么时候,只要创建了一个函数,就有根据一组特定的规则,为该函数创造一个prototype属性。

该属性指向一个对象,称为 原型对象。这个属性会包含一个constructor指针,指向函数原型。

/**
 * 函数原型-首字母大写
 */
function Person(){}
20220818221645-2022-08-18-22-16-45

通过图片可以更好的理解以上内容。

当调用构造函数创建一个实例时,实例内部会包含一个内部指针__proto__,称为隐式原型,它指向构造函数的prototype对象。

/**
 * 实例和函数原型
 */
function Person(name){
    this.name = name;   // 实例属性
}
Person.prototype.printName = function(){
    alert(this.name);
}

var person1 = new Person('Byron');
var person2 = new Person('Frank');
20220818222415-2022-08-18-22-24-15

通过图片可以看出,person1中包含了name,同时会有 __proto__ -> Persion.prototype (能看到printName),如下图所示

20220818222714-2022-08-18-22-27-14
/**
 * prototype内容共享测试
 */
function Person(name){
    this.name = name;
}

Person.prototype.share = [];
Person.prototype.printName = function(){ alert(this.name); };

var person1 = new Person('Byron');
var person2 = new Person('Frank');

person1.share.push(1);
person2.share.push(2);
console.log(person2.share); // [1,2]

当从实例中获取属性时,会先查找该实例是否包含。如果没有则查找__proto__ 指向的prototype 里面是否含有,没有就继续递归查找。直到 Object.prototype,仍然没有则报错。

prototype中的属性可以通过实例属性去覆盖

/**
 * 覆盖prototype中的属性
 */
function Person(name){
    this.name = name;
}
Person.prototype.share = [];

var person = new Person('Byron');
person.share = 0;
console.log(person.share); // 0 不是 []

五、总结

如果希望实例复用对象的属性或函数,则放到 prototype 中。 如果希望每个实例单独拥有的属性和方法,则定义到this中,通过构造函数传递实例化参数。

function Person(name){
    this.name = name;
}

Person.prototype.share = [];
Person.prototype.printName = function(){
    alert(this.name);
}

六、原型链示意图

假设有个Person

function Person(){}
new Person();

此时,会有如下关系:

  • 红色:function Person(){}
  • 蓝色:Person.prototype
  • 绿色: new Person()
  • prototype和__proto__(一般浏览器都叫这个)是自动生成

以下图片中线条

  • 蓝色:prototype
  • 绿色:new
  • 黑色:proto
20220819090953-2022-08-19-09-09-53

而其中Person函数,又是 new Function()生成的实例,所以关系图如下:

20220819092506-2022-08-19-09-25-06

Person.prototype = {} 他是个Object 实例,相当于 new Object();,所以又有如下关系图:

20220819094705-2022-08-19-09-47-05

同理 Function.prototype 也是如此,所以关系图如下:

20220819095320-2022-08-19-09-53-20

关系图中 Object.prototype 比较特殊,它的隐式原型为null
Object() 函数为new Function(),所以它是其实例,同时其隐式原型为Function.prototype
最后一点是Function()函数的隐式原型指向Function.prototype

所以最终关系图如下:

20220819101128-2022-08-19-10-11-28

当我们去用实例去调用属性或方法时

person1.toString();

查找顺序为(沿最左侧黑色箭头):person1自身属性 -> Person.prototype -> Object.prototype

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注