原型 && 原型链
2020-03-25 本文已影响0人
晚月川
原型 && 原型链
- 每一个类(函数)都天生具备一个属性:prototype(原型),prototype的属性值是一个对象(浏览器默认会给其开辟一个堆内存)=>“原型对象上所存储的属性和方法,就是供当前类实例所调用的公有的属性和方法”
- 普通函数
- 类也是函数类型的值
- 在类的prototype原型对象中,默认存在一个内置的属性:constructor(构造函数),属性值就是当前类(函数)本身,所以我们也把类称为构造函数
- 每一个对象都天生具备一个属性:
__proto__
(原型链),属性值是当前实例(对象)所属类的prototype原型- 对象数据类型
- 普通对象
- 数组对象
- 正则对象
- ……
- 实例也是对象类型值(除基本值外)
- 类的prototype原型属性值也是对象
- 函数也具备对象的特征(它有一重身份就是对象类型)
- ……
=>所有的对象数据类型值,都是内置类Object的一个实例
- 对象数据类型
-
arr1.length
或者arr1[0]
=>获取当前实例的私有属性值 -
arr1.push()
- 首先找自己的私有属性,私有属性有,调取的就是私有属性;如果没有,默认基于
__proto__
原型链属性,找所属类prototype
原型上的公共属性和方法;如果还没有则基于原型prototype
中的__proto__
继续向上查找,一直找到Object.prototype
为止
- 首先找自己的私有属性,私有属性有,调取的就是私有属性;如果没有,默认基于
-
arr.__proto__.push
- 直接找所属类原型上的push方法,类似于Array.prototype.push这样找
arr1.__proto__.push === arr2.push === Array.prototype.push
- 直接找所属类原型上的push方法,类似于Array.prototype.push这样找
原型链查找机制:基于实例__proto__
找所属类的prototype
=>实例的私有属性方法
=>实例的公有属性和方法
push
是arr1实例的“公有属性方法” push
是Array.prototype
的私有属性
hasOwnProperty
是arr1实例的“公有属性方法”
对象的私有属性:存储在自己的堆中,无需基于__proto__
查找
对象的公有属性:自己堆中没有,需要基于proto找prototype上的
Array.prototype.hasOwnProperty('hasOwnProperty'); //=> false
Object.prototype.hasOwnProperty('hasOwnProperty'); //=> ture
所有对象都是Object的实例:只要原型链最后都能找到Object.prototype
,那么都是对象类的一个实例(也就是对象),例如:数组是Array
的一个实例,但是最后原型链也找到了Object.prototype,所以数组也是对象类的一个实例(也是一个对象)
document -> HTMLDocument.prototype -> Document.prototype -> Node.prototype -> EventTarget.prototype -> Object.prototype
每个元素对象 最后都能找到 Object.prototype,所以它们也是对象类的实例,也是一个对象
元素集合 -> HTMLCollection.prtotype -> Object.prototype
只要在当前实例原型链上的属性和方法 实例都可以调用
JS中的所有值,最后基于原型链,都能找到Object.protitype
原型,也就是都是对象类的实例,也就是都是对象 =>(万物皆对象)
- 给类的原型上扩展属性或者方法(供其实例调取使用的公有属性方法)
- Fn.prototype.xxx = xxx;
- Object.prototype.xxx = xxx; 内置类原型上扩展方法
- f1.proto.xxx = xxx; 这样也可以,因为基于实例的proto找到的就是所属类的原型,也相当于给原型上扩展属性方法(只不过这种方式我们基本不用,因为在IE浏览器中,为了防止原型链的恶意篡改,是禁止我们自己操作proto属性的;IE中不让用proto)
- 关于构造函数中相关的this问题
- 基于new执行,构造函数,函数体中的this是当前类的一个实例
- 给实力扩展的私有或者公有方法,这些方法中的this完全看前面是否有“点”来决定
/* 向类的原型上扩展属性方法
Fn.prototype.xxx = xxx; 向默认的堆内存中增加属性方法
=>如果需要设置很多属性方法,操作起来比较麻烦(小技巧:给Fn.prototype设置别名) */
let prop = Fn.prototype;
prot.A = 100;
prot.B = 200;
prot.C = 300;
// 这类方式的特点都是向默认开辟的堆内存中扩展属性和方法,默认开辟的堆内存中存在constructor这个属性
// 重定向Fn的原型指向
// =>自己开辟的堆内存中是没有constructor这个属性的,所以真实项目中,为了保证结构的严谨性,我们需要手动设置constructor
// =>如果在重定向之前,我们向默认开辟的堆内存中设置了一些属性方法,重定向后,之前设置的属性方法都丢失了(没用了)
Fn.prototype = {
constructor:Fn;
xxx:xxx
}
JS中有很多内置类,而且在内置类的原型上有很多内置的属性和方法
Array.prototype:数组作为Array的实例,就可以调取原型上的公共属性方法,完成数组的相关操作 => arr.push():arr基于proto原型链的查找机制,找到Array.prototype上的push方法,然后把push方法执行,push方法执行:
=> 方法中的this是arr这个数组的实例
=> 作用向arr(也就是this)的末尾追加新的值
=> 返回结果是新增后数组的长度
// 向内置类原型扩展方法:
// Array.prototype.xxx = xxx;
// =>这种方法存在风险:我们自己设置的属性名可能会把内置的属性给覆盖掉
// =>一般我们自己在内置类原型上扩展的方法,设置的属性名做好加上前缀
Array.prototype.push = function () {
console.log('自己的push');
};
let arr = [10,20];
arr.push(100);
// 浏览器为了保护内置类原型上的方法,不允许我们重新定向内置类原型的指向(严格模式下会报错)
Array.prototype = {
AA:100
};
/*
* 模拟内置的push方法
* 在类的原型上编写的方法,让方法执行,我们一般都这样操作:实例.方法(),所以方法中的this一般都是我们要操作的这个实例,我们基于this操作就是操作这个实例
*/
Array.prototype.push = function puah(value) {
// this:要操作的数组实例
this[this.length] = value;
return this.length;
};
let arr = [10,20];
console.log(arr.push(100),arr);