0基础进大厂,第7天——面试官:所有的对象都有原型吗?

70 阅读5分钟

引言

原型,其实一点不难,跟着我,你一点也不会晕

思考第一个问题:为什么需要原型?

看一份文字案例:
描述:每个人都有相同的属性,比如:小明和小红同属一个班级、xx小学和xxxx小学都属于xxx地区等等,这些相同的属性存在现实生活中,那我们代码里是如何表示的呢? image.png
现在用一个简单的代码描述一个班级的情况:有三位同学,都是一年级二班的,每次实例化一位同学的信息的时,我们都需要传入一个实参“一年级二班”
主要有两个缺点:

  • 这样是不是太麻烦了
  • 代码重复了,因为实例化对象表示在每个对象里都存了一条信息: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);

image.png
你肯定要问了,这打印的信息没有显示'一年级二班'啊??!
是的,确实没打印出来,那么这样呢?

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);

image.png
没错,能访问到这个属性
为什么?
这是因为在构造函数里的属性,比如:name属于显式拥有的属性,
class_number保存在.prototype指向的对象里,属于隐式拥有的属性。
在打印对象的时候,只会打印显示拥有的属性,但是能访问到隐式拥有的属性
我知道你有疑惑
请看新概念

显式原型

概念:函数天生拥有一个属性,叫做prototype,这个属性指向的是一个对象

我可没骗你,请看VCR
你可以理解为,这个对象就是用来存放公共属性和方法的

image.png

再了解一个知识点:实例对象无法修改无法删除原型上的属性和方法,只有原型自己才能操作

如下图,输出结果,原型执行删除操作后,就出现访问不到函数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();

image.png

所以,现在,你应该已经知道了,对于构造函数而言,通过.prototype可以访问到公共的属性和方法

我知道你还有疑问:我们前面的代码里,可是用的实例对象访问的公共属性和方法,代码为什么能这么写!

console.log(s1, s1.class_number);

再来了解一个概念:隐式原型

隐式原型

概念:

  • 每个对象都有一个属性,叫做__proto__,该属性值也是一个对象
  • V8在访问对象中的属性时,会先访问对象上显示拥有的属性,如果找不到,就会去对象的隐式原型中查找,也就是__proto__指向的对象中查找
  • 实例对象的隐式原型 === 构造函数的显示原型

什么意思?请看图

补充一点:
.constructor就是单纯指向构造函数的 image.png

所以,你会发现

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 image.png

面试官:所有的对象都有原型吗?

图解一切! image.png

总结

原型(显式原型)

  • 函数天生拥有一个属性,叫做prototype,这个属性指向的是一个对象,
  • 意义:
    • 可以把一些固定的方法和属性放到这个对象中,
    • 当函数创建实例的时候,实例可以共享这个对象中的属性和方法
  • 实例对象无法修改无法删除原型上的属性和方法

对象原型(隐式原型)

  • 每个对象都有一个属性,叫做__proto__,该属性值也是一个对象
  • V8在访问对象中的属性时,会先访问对象上显示拥有的属性,如果找不到,就会去对象的隐式原型中查找,也就是__proto__指向的对象中查找
  • 实例对象的隐式原型 === 构造函数的显示原型
    • 为什么:
      • new实现原理:
        • 创建一个空对象,
        • 让构造函数的this指向这个空对象,
        • 执行构造函数中的代码
        • 将空对象的隐式原型__proto__赋值成构造函数的显示原型prototype
        • 返回这个对象
    • 设计意义:
      • 让实例对象继承到构造函数的属性和方法,方便我们为某一个数据类型添加属性和方法

原型链

  •  v8 在访问对象中的属性时,会先访问对象上的显示拥有的属性,如果找不到,就会去对象的隐式原型中查找,也就是__proto__ 中查找,如果还找不到,就会去隐式原型的隐式原型中查找,也就是__proto__的__proto__ 中查找,层层往上,直到找到 null 为止

没有原型的对象

  • Object.create() 创建一个新对象,让这个新对象的隐式原型等于传入的obj
  • Object.create(null) 创建一个没有原型的对象,没有原型的对象没有隐式原型,所以没有原型的对象没有原型链