JS原型和原型链

如果单纯的使用构造函数(把方法放到函数的外面)会使全局变量增多,造成污染,而解决这个问题的办法就是使用原型。

原型prototype

每个构造函数都有一个prototype原型属性,这个属性是一个指针,指向函数的原型对象。构造函数创建的所有实例都共享原型所包含的属性和方法。

所有原型对象都会自动获得一个costructor属性,指向prototype属性所在函数的指针。constructor属性也是实例共享的,所以所有实例也可以访问

创建了构造函数后。它的原型对象默认只会取得constructor属性,其他方法都是从Object继承而来的

当创建一个新实例后,该实例的内部将包含一个指针(内部属性),指向构造函数的原型对象[[prototype]],每个对象都支持一个属性proto

1
2
3
4
5
6
7
8
function Person(name,age) {
this.name = name;
this.age = age;
this.sayName = function () {
console.log("我叫"+this.name);
}
}
var zs = new Person("张三",18);

访问原型对象

  1. 构造函数.prototype
  2. 实例. proto

给原型对象添加属性/方法

  1. 点语法
  2. 方括号[]

方法

  1. 原型对象.isPrototypeOf(对象):判断指定对象是否存在于另一个对象的原型链中
  2. Object.getPrototypeOf(对象):返回对象的原型
  3. 对象.hasOwnProperty(属性):检测属性是都在对象实例中,而不是原型对象上。返回值为Boolean类型

原型与in操作符

  1. in
1
属性 in 对象

该属性可以在原型上也可以在对象实例本身上,可以同时使用hasOwnProperty()和in确定属性是否存在对象中还是原型中

1
!对象.hasOwnProperty(属性) && (属性 in 对象)
  1. for-in:访问对象的可枚举属性,包括实例的属性和原型中的属性
  2. Object.keys(对象)):只返回对象实例的属性。返回一个包含所有可枚举属性的字符串数组
  3. Object.getOwnPropertyNames():但会所有实例属性,无论是否可以枚举
  4. 对象 instanceOf 构造函数
    :断该构造函数的原型对象是否在这个对象的原型链上
  5. propertyIsEnumerable:
    用来检测属性是否属于某个对象本身并且是否可以枚举
  6. object.valueOf():用于返回指定对象的原始值。该方法属于Object对象,由于所有的对象都”继承”了Object的对象实例,因此几乎所有的实例对象都可以使用该方法。

    在对象参与运算的时候,默认的会先去调用对象的valueOf方法,如果valueOf获取到的值,无法进行运算 ,就去调用toString方法最终做的就是字符串拼接的工作。

  7. 静态成员和实例成员
  • 静态成员:构造函数的属性和方法
  • 实例成员:实例的属性和方法

属性的搜索

访问对象的属性和方法时,首先在对象本身查找,如果找到了,则返回该属性的值;如果没有找到这个属性,则继续搜索对象实例的原型对象以及原型链,如果找到了,则返回该属性的值;如果原型中也没有的话属性会undefined,方法会报错。

不能通过对象实例重写原型中的值。
点语法赋值时,如果对象没有该属性,就会给对象新增该属性并赋值,会屏蔽原型中的同名属性。如果有该属性则是修改属性值,而不会修改原型中的东西。

原型对象的属性

当原型中的属性是引用类型的属性时,那么所有的对象共享该属性,并且一个对象修改了该引用类型属性的成员时,其他对象也会受到影响。(这里你需要知道引用类型和值类型的不同处)。

一般情况下不会将属性放到原型对象中,一般情况下原型中只会放需要共享的方法

直接替换原型对象

在替换原型之前构造函数创造的对象的原型和替换后构造函数创建的对象的原型不同

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Person(name,age){
this.name=name;
this.age=age;
}
Person.prototype.sayName=function(){
console.log("我叫"+this.name);
}
var p1=new Person("张三",18);
//直接替换原型对象
Person.prototype={
askName: function(){
console.log("我的名字是"+this.name);
}
}
var p2= new Person("张三",18);
// p2.sayName(); 替换后和之前的原型不一样 不能再使用
p2.askName();

在使用新的对象替换默认的原型对象时,原型对象中的constructor属性会变为Object,所有当需要为了保证 构造函数–原型对象–对象之间关系的合理性,应该在替换原型对象时,给新的对象添加constructor属性(构造函数.prototype.constructor=构造函数)

将属性写在原型里与将属性写在构造函数里有什么不同

把方法写在原型中比写在构造函数中消耗的内存更小,因为在内存中一个类的原型只有一个,写在原型中的行为可以被所有实例共享,实例化的时候并不会在实例的内存中再复制一份
而写在类中的方法,实例化的时候会在每个实例中再复制一份,所以消耗的内存更高
所以没有特殊原因,我们一般把属性写到类中,而方法写到原型中

构造函数中定义的属性和方法要比原型中定义的属性和方法的优先级高,如果定义了同名称的属性和方法,构造函数中的将会覆盖原型中的

原型链

每个对象都有一个构造函数,每个构造函数都有一个原型对象,而原型对象也是一个对象,所有这个原型对象又有它的构造函数,那么这个原型对象的构造函数也会有一个原型对象,从而形成一个链式结构,称作原型链

当引用一个对象属性时,这个对象内部不存在这个属性,那么就会在prototype中找这个属性,这个prototype又会有自己的prototype,直到为null位置

原型链

每个函数都有 prototype 属性,除了 Function.prototype.bind(),该属性指向原型。

每个对象都有 proto 属性,指向了创建该对象的构造函数的原型。其实这个属性指向了 [[prototype]],但是 [[prototype]] 是内部属性,我们并不能访问到,所以使用 proto 来访问。

对象可以通过 proto 来寻找不属于该对象的属性,proto 将对象连接起来组成了原型链。