上篇的最后咱们提到了hasOwnProperty
是用来检测某个属性是否为当前实例的私有属性的,咱们还本身编写了hasPubProperty
用来检测某个属性是否为当前实例的公有方法的;私有方法上文中已经介绍,就是实例自己私有的方法,存在当前实例中;那什么是公有方法,他们又在哪里呢?这就是咱们今天要讲的原型和原型链。javascript
咱们以数组为例:java
每个数组都是Array这个内置数组类的实例;数组
let arr1 = [10, 20];
let arr2 = [30, 40];
console.log(arr1 instanceof Array); //=>true
console.log(arr1.hasOwnProperty('push')); //=>false
console.log(arr1.push === arr2.push); //=>true
arr1.push(100); //=>对象.属性 说明PUSH是ARR1的一个属性,并且是公有属性(其它数组中经常使用的方法也都是数组实例的公有属性)
复制代码
那push
等一系列数组的方法在哪里呢?浏览器
咱们先记住三句话:很重要、很重要、很重要,重要的事说三遍bash
一、每个函数都天生具有一个属性:
prototype
(原型),prototype
的属性值是一个对象(浏览器默认会给其开辟一个堆内存)函数
- =>“原型对象上所存储的属性和方法,就是供当前类实例所调用的公有的属性和方法”
二、在类的
prototype
原型对象中,默认存在一个内置的属性:constructor
(构造函数),属性值就是当前类(函数)自己,因此咱们也把类称为构造函数ui三、每个对象都天生具有一个属性:
__proto__
(原型链),属性值是当前实例(对象)所属类的prototype
原型this
仍是以Array
数组内置类为例spa
类(自定义类仍是JS内置类)都是函数数据类型prototype
咱们能够打开控制台输出:
由于Array是内置类,因此存储的为原生代码,浏览器为了保护原生代码,不让咱们查看具体内容,因此输出为[native code]
,若是是咱们本身建立的自定义类是能够看见的;
这里咱们记住一句话
全部的实例都是对象数据类型值(除基本数据类型值的实例外)
let arr1 = [10, 20];
let arr2 = [30, 40];
复制代码
数组arr1
中的0:10
,1:20
,length:2
都是arr1
实例的私有属性; arr1
和arr2
里的内容互不冲突(缘由在单例模式中讲过);
每个实例对象,本身堆内存中储存的属性都是私有属性
不管是arr1
仍是arr2
均可以调用数组的push
...等方法,可是这些方法私有里又都没有,那这些方法怎么出来的呢?
这里咱们就用到了上面的三句话:
prototype
属性;属性值是一个对象每个函数(包括👇)都天生具有一个属性:
prototype
(原型),prototype
的属性值是一个对象(浏览器默认会给其开辟一个堆内存)
- 普通函数
- 类也是函数类型的值
原型对象上所存储的属性和方法,就是供当前类实例所调用的公有的属性和方法
prototype
原型对象中,默认存在一个constructor
属性,属性值就是当前函数自己在类的
prototype
原型对象中,默认存在一个内置的属性:constructor
(构造函数),属性值就是当前类(函数)自己,因此咱们也把类称为构造函数
dir(Array)
找到
prototype
找到
constructor
结果是
Array
,这样一直找会无限循环...
__proto__
属性,属性值是当前实例所属类的prototype
原型每个对象都天生具有一个属性:
__proto__
(原型链),属性值是当前实例(对象)所属类的prototype
原型 这里的对象为泛指:包括
- 对象数据类型值
- 普通对象
- 数组对象
- 正则对象
- ...
- 实例也是对象类型值(除基本值外)
- 类的
prototype
原型属性值也是对象- 函数也具有对象的特征(它有一重身份就是对象类型)
- ...
如今咱们输出:
arr1.length
或者 arr1[0]
;
arr1.push()
;
__proto__
原型链属性,找所属类prototype
原型上的公共属性和方法arr1.push() === arr2.push()
;
arr1.push
在找到 arr2.push
true
;因此咱们说原型上存放的是实例的公共属性和方法;
arr1.proto.push
push
方法,相似于 Arrary.prototype.push
这样找arr1.__proto__.push === arr2.push === Array.prototype.push
咱们知道arr1
arr2
实例对象,他们所属的类是 Array
,因此 arr1.__proto__===Array.prototype
确定没有问题,
那咱们
Array.prototype
这个对象是谁的实例呢?
- 一、确定不是
Array
的实例(他是Array
的原型),数组才是Array
的实例- 二、他自己是一个对象;
全部的对象数据类型值,都是内置类
Object
的一个实例
那Object
的原型也是一个对象,每一个对象都有一个__proto__
属性指向当前所属类的原型,而全部对象数据类型,都是Object
的一个实例;
咱们发现他最后指向了他本身;指向本身就失去了原型链查找的意义,因此咱们规定Object.prototype.__proto__ === null
Object
是全部对象的基类,在他的原型上的__proto__
属性,若是存在也是指向本身的原型,这样没有意义,因此他的__proto__
属性值为null
ARR1(数组的实例对象)的整个原型链:
__proto__
找到所属类的原型Array.prototype
;若是尚未Array.prototype
的__proto__
找到Object.prototype
; 若是尚未这一系列就是咱们的原型链查找;
原型链查找机制:基于实例的__proto__找所属类的prototype
- => 实例的私有属性方法
- => 实例的公共属性和方法
基于这种查找机制,帮助咱们实现了实例既有私有的属性和方法,也有公有的属性和方法了
这也是整个面向对象的核心。
咱们能够打开控制台,输出dir[10,20])
:
arr1.hasOwnProperty
时
arr1.__proto__.__proto__ => Object.prototype
arr1.hasOwnProperty("push") => false
Array.prototype.hasOwnProperty("push") => true
push
是arr1
实例的公有方法;可是是Array.prototype
的私有属性
hasOwnProperty
是arr1
实例的“公有属性方法”
__proto__
查找就有的__proto__
找prototype
上的JS中的全部值,最后基于
__proto__
原型链,都能找到Object.prototype
原型,也就是都是对象类的实例,也就是都是对象,这就是“万物接对象”
老规矩,咱们在来一道题
function Fn() {
this.x = 100;
this.y = 200;
this.getX = function () {
console.log(this.x);
}
}
Fn.prototype.getX = function () {
console.log(this.x);
};
Fn.prototype.getY = function () {
console.log(this.y);
};
let f1 = new Fn;
let f2 = new Fn;
console.log(f1.getX === f2.getX);
console.log(f1.getY === f2.getY);
console.log(f1.__proto__.getY === Fn.prototype.getY);
console.log(f1.__proto__.getX === f2.getX);
console.log(f1.getX === Fn.prototype.getX);
console.log(f1.constructor);
console.log(Fn.prototype.__proto__.constructor);
f1.getX();
f1.__proto__.getX();
f2.getY();
Fn.prototype.getY();
复制代码
作原型类的题目,没有比画图更好的方式了
console.log(f1.getX === f2.getX); //=> false
console.log(f1.getY === f2.getY); //=> true
console.log(f1.__proto__.getY === Fn.prototype.getY); //=> true
console.log(f1.__proto__.getX === f2.getX); //=> false
console.log(f1.getX === Fn.prototype.getX); //=> false
console.log(f1.constructor); //=> Fn 实例的构造函数通常指的就是它所属的类
console.log(Fn.prototype.__proto__.constructor); //=> Object
f1.getX() ;
执行的是私有的getX => function () {console.log(this.x);}
方法中的this => f1
代码执行
console.log(this.x); => f1.x => 100
f1.__proto__.getX() ;
执行的是原型上公有的getX => function () {console.log(this.x);};
方法中的this => f1.__proto__
代码执行
console.log(this.x); => f1.__proto__.x => undefined
f2.getY() ;
执行的是原型上公有的getY => function () {console.log(this.y);};
方法中的this => f2
代码执行
console.log(this.y) => f2.y =>200
Fn.prototype.getY() ;
执行的是原型上的getY => function () {console.log(this.y);};
方法中的this => Fn.prototype
代码执行
console.log(this.y) => Fn.prototype.y => undefined
复制代码
(供其实例调取使用的公有属性和方法)
Fn.prototype.xxx = xxx
(经常使用)Fn.prototype
设置别名)constructor
这个属性let prop = Fn.prototype;
prop.A = 100;
prop.B = 200;
prop.C = 300;
复制代码
Object.prototype.xxx = xxx
(不经常使用)f1.__proto__.xxx = xxx
(基本不用)__proto__
找到的就是所属类的原型,也至关于给原型上扩展属性方法IE
浏览器中,为了防止原型链的恶意篡改,是禁止咱们本身操做__proto__
属性的(IE中不让用__proto__
)Fn.prototype = {...}
(经常使用)Fn.prototype
constructor
这个属性的;因此真实项目中,为了保护结构的严谨性,咱们须要本身手动设置constructor
Object.assign(原来对象,新对象)
function Fn(num) {
this.x = this.y = num;
}
Fn.prototype = {
x: 20,
sum: function () {
console.log(this.x + this.y);
}
};
let f = new Fn(10);
console.log(f.sum === Fn.prototype.sum); //true
f.sum();//20
Fn.prototype.sum();//NaN
console.log(f.constructor);//Object
复制代码
JS
中有不少内置类,并且在内置类的原型上有不少内置的属性和方法,虽然内置类的原型上有不少的方法,可是不必定彻底够项目开发所用,因此真实项目中,须要咱们本身向内置类原型扩展方法,来实现更多的功能操做
Array.prototype.xxx = xxx
(以数组为例)因此通常咱们本身在内置类原型上扩展的方法,设置的属性名作好加上前缀
浏览器为了保护内置类原型上的方法,不容许咱们从新定向内置类原型的指向(严格模式下会报错)
需求1:模拟内置的
PUSH
方法
- 在类的原型上编写的方法,让方法执行,咱们通常都这样操做:
实例.方法()
,因此方法中的THIS
通常都是咱们要操做的这个实例,咱们基于THIS
操做就是操做这个实例
/* * JS中有不少内置类,并且在内置类的原型上有不少内置的属性和方法 * Array.prototype:数组做为Array的实例,就能够调取原型上的公共属性方法,完成数组的相关操做 => arr.push():arr基于__proto__原型链的查找机制,找到Array.prototype上的push方法,而后把push方法执行,push方法执行 * + 方法中的THIS是要操做的arr这个数组实例 * + 做用是向arr(也就是this)的末尾追加新的值 * + 返回结果是新增后数组的长度 * * 向内置类原型扩展方法: * Array.prototype.xxx = xxx * =>这种方法存在风险:咱们本身设置的属性名可能会把内置的属性给覆盖掉 * =>通常咱们本身在内置类原型上扩展的方法,设置的属性名最好加上前缀 * * Array.prototype={...} * =>浏览器为了保护内置类原型上的方法,不容许咱们从新定向内置类原型的指向(严格模式下会报错) */
Array.prototype.myPush = function () {
console.log('本身的PUSH');
};
let arr = [10, 20];
arr.myPush(100);
复制代码
Array.prototype.myPush = function myPush(value) {
// this:要操做的数组arr实例
this[this.length] = value;
return this.length;
};
let arr = [10, 20];
console.log(arr.myPush(100), arr);
let arr2 = [];
console.log(arr2.myPush('小芝麻'), arr2);
复制代码
需求2:数组的原型上有
SORT
实现数组排序的方法,可是没有实现数组去重的方法,咱们接下来向内置类原型扩展方法:myUnique
,之后arr.myUnique
执行能够把数组去重
Array.prototype.myUnique = function myUnique() {
// this:当前要操做的数组实例
let obj = {};
for (let i = 0; i < this.length; i++) {
let item = this[i];
if (typeof obj[item] !== "undefined") {
this[i] = this[this.length - 1];
this.length--;
i--;
continue;
}
obj[item] = item;
}
obj = null;
//为了实现链式写法
return this;
};
let arr = [12, 23, 13, 23, 12, 12, 2, 3, 1, 2, 3, 2, 1, 2, 3];
arr.myUnique().sort((a, b) => a - b);
console.log(arr);
复制代码
arr
之因此能调用myUnique
或者sort
等数组原型上的方法,是由于arr
是Array
的实例,arr.myUnique().sort((a, b) => a - b).map(item => {
return '@@' + item;
}).push('小芝麻').shift();
//Uncaught TypeError: arr.myUnique(...).sort(...).map(...).push(...).shift is not a function
//=> 由于push返回的是新增后数组的长度,是个数字,再也不是数组了,就不能继续调用数组的方法了
复制代码
基于
new
执行,构造函数-函数体中的this
是当前类的一个实例给实例扩展的私有或者公有方法,这些方法中的
this
彻底看前面是否有“点”来决定
仍是上面的例题