原型基本概念
prototype: 浏览器的标准属性,程序员使用的,显示原型,存在于函数中
__protp__
: 浏览器的非标准属性,浏览器使用的,隐式原型,存在于实例对象中函数中有prototype、
__protp__
,实例对象中有__protp__
实例的__proto__与对应函数的prototype都指向原型对象
无论是构造函数还是普通函数,或者是方法,只要是函数,内部就有prototype
1.原型的产生
1.1执行-函数定义和执行函数
执行-函数定义
<!--执行函数定义-->
function f1() {
console.log('f1函数');
}
console.dir(f1);
//f1是一个引用变量,存储了函数的代码所存储的空间的地址.
执行函数
<!--执行函数,也可以说执行函数体-->
//是指函数的调用,也就是函数体中的代码执行时
function f1() {
console.log('f1函数');
}
f1()
1.2显示原型和隐式原型
-
执行函数定义的时候,prototype
显示原型
就创建出来了(函数定义执行的时候产生的,在构造函数中 -
由于函数本身也是实例对象,创建实例对象的时候产生的里面的
隐式原型
__proto__
就产生了,在实例对象中
[[ prototype ]] === __prototype __
//在不同版本的浏览器显示不同,左侧是一些书籍规范的书写方式
- 原型链
原型链基础
//简单的原型链
function F1() {
}
F1.prototype.number = 100;
function F2() {
}
//将F1的实例对象直接赋值到F2的原型上
F2.prototype = new F1;
console.dir(F2);
F2.prototype.number = 200;
function F3() {
}
//将F1实例化对象通过F2函数又赋值给了F3的原型
F3.prototype = new F2();
//这个数字等于修改了F1实例化对象的number的值
F3.prototype.number = 300;
let f3 = new F3();
console.dir(F3);
console.log(f3.number);//300
console.log(obj.__proto__ === Object.prototype);
console.log(Fn.prototype.__proto__ === Object.prototype);
对F2构造函数进行dir打印,最终返回的结果是,构造函数prototype指向原型对象,原型对象有一个F1的实例对象[[prototype]]
也就是__proto__
F1的实例对象指向F1的原型对象,原型对象有一个constructor指向F1构造函数。
最开始300是没找到的,在原型上找到的
进一步理解原型
//进一步理解原型
function Fn() {}
var obj = {};//实例对象
console.log();
console.log(obj.__proto__ === Object.prototype);//?
console.log(Fn.prototype.__proto__ === Object.prototype);//?
2.图解原型链
1.原型与原型链
2.原型与原型链
3.原型与原型链
因为JS原型链设计时定义了Object 是一等公民,Function 是二等公民, 所以你再想想为什么 Function.prototype.__proto__ === Object.prototype
为什么为 true
3.原型与继承
继承(改变原型指向/借用构造函数/组合/拷贝)
1.原型链继承
原型链继承的本质是重写原型对象,代之以一个新类型的实例对象。
function Animal() {
this.value = 'animal';
}
Animal.prototype.run = function() {
return this.value + ' is runing';
}
function Cat() {}
// 这里是关键,创建 Animal 的实例,并将该实例赋值给 Cat.prototype
// 相当于 Cat.prototype.__proto__ = Animal.prototype
Cat.prototype = new Animal();
var instance = new Cat();
instance.value = 'cat'; // 创建 instance 的自身属性 value
console.log(instance.run()); // cat is runing
产生的问题
- 多个实例对引用类型的操作会被篡改
function Animal(){
this.names = ["cat", "dog"];
}
function Cat(){}
Cat.prototype = new Animal();
var instance1 = new Cat();
instance1.names.push("tiger");
console.log(instance1.names); // ["cat", "dog", "tiger"]
var instance2 = new Cat();
console.log(instance2.names); // ["cat", "dog", "tiger"]
- 子类型的原型上的 constructor 属性被重写了
- 给子类型原型添加属性和方法必须在替换原型之后
- 创建子类型实例时无法向父类型的构造函数传参
构造函数继承
function SuperType(){
this.color=["red","green","blue"];
}
function SubType(){
//继承自SuperType
SuperType.call(this);
}
var instance1 = new SubType();
instance1.color.push("black");
alert(instance1.color);//"red,green,blue,black"
var instance2 = new SubType();
alert(instance2.color);//"red,green,blue"
缺点:
- 只能继承父类的实例属性和方法,不能继承原型属性/方法
- 无法实现复用,每个子类都有父类实例函数的副本,影响性能
3.组合式继承
原型链加构造函数
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
};
function SubType(name, age){
// 继承属性
// 第二次调用SuperType()
SuperType.call(this, name);
this.age = age;
}
// 继承方法
// 构建原型链
// 第一次调用SuperType()
SubType.prototype = new SuperType();
// 重写SubType.prototype的constructor属性,指向自己的构造函数SubType
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
alert(this.age);
};
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
instance1.sayName(); //"Nicholas";
instance1.sayAge(); //29
var instance2 = new SubType("Greg", 27);
alert(instance2.colors); //"red,blue,green"
instance2.sayName(); //"Greg";
instance2.sayAge(); //27
缺点:
- 第一次调用
SuperType()
:给SubType.prototype
写入两个属性name,color。 - 第二次调用
SuperType()
:给instance1
写入两个属性name,color。
实例对象instance1
上的两个属性就屏蔽了其原型对象SubType.prototype的两个同名属性。所以,组合模式的缺点就是在使用子类创建实例对象时,其原型中会存在两份相同的属性/方法。
4.寄生式组合继承
function inheritPrototype(subType, superType){
var prototype = Object.create(superType.prototype); // 创建对象,创建父类原型的一个副本
prototype.constructor = subType; // 增强对象,弥补因重写原型而失去的默认的constructor 属性
subType.prototype = prototype; // 指定对象,将新创建的对象赋值给子类的原型
}
// 父类初始化实例属性和原型属性
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
};
// 借用构造函数传递增强子类实例属性(支持传参和避免篡改)
function SubType(name, age){
SuperType.call(this, name);
this.age = age;
}
// 将父类原型指向子类
inheritPrototype(SubType, SuperType);
// 新增子类原型属性
SubType.prototype.sayAge = function(){
alert(this.age);
}
var instance1 = new SubType("xyc", 23);
var instance2 = new SubType("lxy", 23);
instance1.colors.push("2"); // ["red", "blue", "green", "2"]
instance2.colors.push("3"); // ["red", "blue", "green", "3"]
这个例子的高效率体现在它只调用了一次SuperType
构造函数,并且因此避免了在SubType.prototype
上创建不必要的、多余的属性。于此同时,原型链还能保持不变;因此,还能够正常使用instanceof
和isPrototypeOf()
5. 基于class类实现继承
class Rectangle {
// constructor
constructor(height, width) {
this.height = height;
this.width = width;
}
// Getter
get area() {
return this.calcArea()
}
// Method
calcArea() {
return this.height * this.width;
}
}
const rectangle = new Rectangle(10, 20);
console.log(rectangle.area);
// 输出 200
-----------------------------------------------------------------
// 继承
class Square extends Rectangle {
constructor(length) {
super(length, length);
// 如果子类中存在构造函数,则需要在使用“this”之前首先调用 super()。
this.name = 'Square';
}
get area() {
return this.height * this.width;
}
}
const square = new Square(10);
console.log(square.area);
// 输出 100
extends
继承的核心代码如下,其实现和上述的寄生组合式继承方式一样
function _inherits(subType, superType) {
// 创建对象,创建父类原型的一个副本
// 增强对象,弥补因重写原型而失去的默认的constructor 属性
// 指定对象,将新创建的对象赋值给子类的原型
subType.prototype = Object.create(superType && superType.prototype, {
constructor: {
value: subType,
enumerable: false,
writable: true,
configurable: true
}
});
if (superType) {
Object.setPrototypeOf
? Object.setPrototypeOf(subType, superType)
: subType.__proto__ = superType;
}
}
super继承的参数值和顺序,与打印结果
具体继承的顺序和父类的构造器内部顺序相关
4.原型的作用
作用一
共享数据,节省内存空间
1.实例对象一般都是通过构造函数进行创建的,
实例化对象的时候做的四件事:
-
var per = new Person('卡卡西',20)
- 右侧的new Person叫做实例对象,也叫做匿名对象
- 赋值之后,是把匿名对象的地址指向了per
- 申请一块空闲的空间,用来存储当前的实例对象
- 设置this为当前的实例对象(修改this的指向)
- 初始化实例对象中的属性和方法的值
- 把this作为当前对象进行返回
2.在构造函数中定义的属性及方法,仅仅是编写代码进行定义而已,而实际上里面定义的属性及方法是属于每个实例对象的,所以,创建多个对象,就会开辟多个空间,每个空间中的每个对象都有自己的属性及方法,大量创建对象,对象的方法都不是同一个方法(方法也是函数,函数代码也占用空间),为了节省内存空间,那么可以使用原型的方式,实现数据共享,节省内存空间
作用二
实现JS中的继承
1). 通过改变原型指向实现继承
2). 借用构造函数显示继承
3). 组合继承
4). 拷贝继承:浅拷贝和深拷贝(递归后再说)
5.原型相关表达式
所有的函数都是大Function的实例对象,所有函数的也是实例对象,所有已有一个__proto__
指向了Function的prototype
1.instance of
instanceof
运算符用于检测构造函数的 prototype
属性是否出现在某个实例对象的原型链上。
A instanceof B
B的prototype属性是否在A的实例对象的原型链上
本质:B的原型对象是否在实例对象A的原型链上
主要作用:检测构造函数B是否出现在实例对象A的原型链上
6.原型面试题
-
说说函数对象上的prototype属性?(prototype什么时候出现,执行函数定义的时候)
-
执行函数定义(有可能被提升执行)创建函数对象
-
给函数对象添加prototype属性, 属性值为空的Object实例对象, 也就是原型对象
-
给原型对象添加constructor属性, 值为函数
-
伪代码:
// 给函数对象添加prototype属性, 属性值为空的Object实例对象, 也就是原型对象 function Fn(){} console.dir(Fn) var obj ={} console.log(obj.__proto__===Object.prototype) console.log(Fn.prototype.__proto__===Object.prototype) // 伪代码 this.prototype = {} // this就是函数对象 this.prototype.constructor = Fn
-
-
说说实例对象上的
__proto__
属性?(_proto-什么时候出现,实例化对象的时候) JS引擎在创建实例对象时内部自动执行时, 会自动给实例对象添加
__proto__
属性, 值为构造函数的 prototype属性的值this.__proto__ = Fn.prototype // this是实例对象
-
原型链(实际上是隐式原型链,言外之意就是和显示原型没毛关系,显示原型产生实例的一瞬间起的作用)
- 从对象的
__proto__
开始, 连接的所有对象, 就是我们常说的原型链, 也可称为隐式原型链
- 查找对象属性简单说: 先在自身上查找, 找不到就沿着原型链查找,如果还找不到返回undefined
- 从对象的