引言
原型,其实一点不难,跟着我,你一点也不会晕
思考第一个问题:为什么需要原型?
看一份文字案例:
描述:每个人都有相同的属性,比如:小明和小红同属一个班级、xx小学和xxxx小学都属于xxx地区等等,这些相同的属性存在现实生活中,那我们代码里是如何表示的呢?
现在用一个简单的代码描述一个班级的情况:有三位同学,都是一年级二班的,每次实例化一位同学的信息的时,我们都需要传入一个实参“一年级二班”
主要有两个缺点:
- 这样是不是太麻烦了
- 代码重复了,因为实例化对象表示在每个对象里都存了一条信息:
class_number:'一年级二班'
思考:我们是不是可以把class_number:'一年级二班'抽离出来,作为一个公共属性,放在一个对象里?
function Student(name, class_number) {
this.name = name;
this.class_number = class_number;
}
var s1 = new Student('zs', '一年级二班');
var s2 = new Student('ls', '一年级二班');
var s3 = new Student('ww', '一年级二班');
console.log(s1);
console.log(s2);
console.log(s3);
所以,就需要用到原型(prototype),我们这样做
function Student(name) {
this.name = name;
}
Student.prototype.class_number = '一年级二班';
var s1 = new Student('zs');
var s2 = new Student('ls');
var s3 = new Student('ww');
console.log(s1);
console.log(s2);
console.log(s3);
你肯定要问了,这打印的信息没有显示'一年级二班'啊??!
是的,确实没打印出来,那么这样呢?
function Student(name) {
this.name = name;
}
Student.prototype.class_number = '一年级二班';
var s1 = new Student('zs');
var s2 = new Student('ls');
var s3 = new Student('ww');
console.log(s1, s1.class_number);
console.log(s2, s2.class_number);
console.log(s3, s3.class_number);
没错,能访问到这个属性
为什么?
这是因为在构造函数里的属性,比如:name属于显式拥有的属性,
而class_number保存在.prototype指向的对象里,属于隐式拥有的属性。
在打印对象的时候,只会打印显示拥有的属性,但是能访问到隐式拥有的属性
我知道你有疑惑
请看新概念
显式原型
概念:函数天生拥有一个属性,叫做prototype,这个属性指向的是一个对象
我可没骗你,请看VCR
你可以理解为,这个对象就是用来存放公共属性和方法的
再了解一个知识点:实例对象无法修改、无法删除原型上的属性和方法,只有原型自己才能操作
如下图,输出结果,原型执行删除操作后,就出现访问不到函数say,这是正常现象
function Person() {
this.name = 'zhangsan'
}
Person.prototype.say = function () {
console.log('hello');
}
const p1 = new Person()
console.log(p1);//显示拥有name属性,隐示拥有say属性
delete Person.prototype.say;//只有原型自己才能操作原型上的属性和方法
p1.say();
所以,现在,你应该已经知道了,对于构造函数而言,通过.prototype可以访问到公共的属性和方法
我知道你还有疑问:我们前面的代码里,可是用的实例对象访问的公共属性和方法,代码为什么能这么写!
console.log(s1, s1.class_number);
再来了解一个概念:隐式原型
隐式原型
概念:
- 每个对象都有一个属性,叫做__proto__,该属性值也是一个对象
- V8在访问对象中的属性时,会先访问对象上显示拥有的属性,如果找不到,就会去对象的隐式原型中查找,也就是__proto__指向的对象中查找
- 实例对象的隐式原型 === 构造函数的显示原型
什么意思?请看图
补充一点:
.constructor就是单纯指向构造函数的
所以,你会发现
function Student(name) {
this.name = name;
}
Student.prototype.class_number = '一年级二班';
var s1 = new Student('zs');
console.log(Student.prototype === s1.__proto__);//true
console.log(Student.prototype.constructor === Student);//true
console.log(s1.constructor === Student);//true
console.log(s1.constructor.prototype === s1._ _proto_ _);
原型链
正经地解释一下:
v8 在访问对象中的属性时,会先访问对象上的显示拥有的属性,如果找不到,就会去对象的隐式原型中查找,也就是__proto__ 中查找,如果还找不到,就会去隐式原型的隐式原型中查找,也就是__proto__的__proto__ 中查找,层层往上,直到找到 null 为止
如何理解呢?
谨记两点足矣
.prototype存放公共的属性和方法[[proto]]继承公共属性和方法
注意一点:原型链的尽头是null
面试官:所有的对象都有原型吗?
图解一切!
总结
原型(显式原型)
- 函数天生拥有一个属性,叫做prototype,这个属性指向的是一个对象,
- 意义:
- 可以把一些固定的方法和属性放到这个对象中,
- 当函数创建实例的时候,实例可以共享这个对象中的属性和方法
- 实例对象无法修改、无法删除原型上的属性和方法
对象原型(隐式原型)
- 每个对象都有一个属性,叫做__proto__,该属性值也是一个对象
- V8在访问对象中的属性时,会先访问对象上显示拥有的属性,如果找不到,就会去对象的隐式原型中查找,也就是__proto__指向的对象中查找
- 实例对象的隐式原型 === 构造函数的显示原型
- 为什么:
- new实现原理:
- 创建一个空对象,
- 让构造函数的this指向这个空对象,
- 执行构造函数中的代码
- 将空对象的隐式原型__proto__赋值成构造函数的显示原型prototype
- 返回这个对象
- new实现原理:
- 设计意义:
- 让实例对象继承到构造函数的属性和方法,方便我们为某一个数据类型添加属性和方法
- 为什么:
原型链
- v8 在访问对象中的属性时,会先访问对象上的显示拥有的属性,如果找不到,就会去对象的隐式原型中查找,也就是__proto__ 中查找,如果还找不到,就会去隐式原型的隐式原型中查找,也就是__proto__的__proto__ 中查找,层层往上,直到找到 null 为止
没有原型的对象
- Object.create() 创建一个新对象,让这个新对象的隐式原型等于传入的obj
- Object.create(null) 创建一个没有原型的对象,没有原型的对象没有隐式原型,所以没有原型的对象没有原型链