有bug我不说,测试提了再改!大家好,我是以一己之力养活全公司测试的狗前端---曹八哥
前端面试老是被问到什么是原型,什么是原型链哈! 一直搞不明白的童鞋们,跟我一起来扒一扒。 我将学习原型和原型链的过程写成了 <<找对象5部曲>>
- 了解对象(object),追对象第一件事得去了解她,不然怎么投其所好?
- 对象怎么来的(创建对象)?对象不是天上掉的,或相亲,或自由恋爱,或网恋奔现?
- 了解未来丈母娘(原型prototype),要搞定对象,还得搞定丈母娘,毕竟她手里拽着户口本呢😜
- 怎么和未来老丈人丈母娘保持联系? 得记住地址、联系方式呀!(__proto__ 、prototype、constructor)
- 了解对象的家庭史(原型链) ok,当一切尽在掌握之中,结果必然是...
结果我TM怎么知道?我又没有谈过恋爱😭
哈哈哈...
言归正传,正文开始
1.什么是JS对象(object)
1)对象是属性(变量)和方法(函数)的容器
2)对象通过运算符点【.】来调用它的属性和方法,如
访问对象:objectName.propertyName
访问方法:objectName.mathodName() 3)js中万物皆对象,数字是对象,字符串是对象、函数是对象,类也是对象
记住上面的三点定义,通过上述图片中的方法可以说明:js中所有事物都是对象。
标准的对象,都有很多隐式的属性和方法,包括 __proto__ 和 constructor
本文主要讲的内容都是关于标准的对象
难道还有不标准的对象?
这里简单的提一下,比如说null、undefined,Global等
- null表示无,即此处此时不应该有值
- undefined是指此处应该有个值,但是尚未定义
- Global对象是js内置对象,也是js中最特殊的对象,因为它本身是不存在的,不能通过new创建出来,有且只有一个,还不能直接拿出来调用
在ECMAScript中,不存在独立的函数,所有函数都必须是某个对象的方法
因此在js中将一些内置的构造函数: Object、Array、Function、Boolean、String、Number、Date、RegExp、Error等等,一些特殊属性:undenfined、NaN、Infinity等 和本地对象的构造函数、全局变量等都放到了对象Global里,不需要通过对象来调用,直接使用就行
如:new Array() 、parseInt()
因此,凡是没有通过 【对象.方法】 的方式来调用的方法都是Global对象的方法
2.如何创建对象
网上一搜人家能给你列举出7、8、9、10种,这种死扣区别列举出一大堆方法,个人感觉真的很没有必要,其实大致分为三种(或4种)就行
- 字面量方式(最常用)
- new关键字动态创建方式
- 构造函数创建
- 类(ES6引入)创建
- Object.create() 方式
//1.字面量方式
let a = {name:'a'}
//2.new关键字动态创建方式
//2.1 构造函数创建方式
function B(name) {
this.name = name
}
let b = new B("b")
//2.2 类创建方式
class C {
constructor(name){//类中的构造函数名字必须是constructor
this.name = name
}
}
let c = new C("c")
//3. Object.create() 方式
let d = Object.create({ name:"d" })
浏览器控制台展示:
3.什么是原型prototype
在ES6之前,JS是没有类class的概念的,但是又想要类这样的继承功能,于是在声明构造函数时默认实例化了一个对象,这个对象就是原型Prototype,在Prototype上定义的属性将会被该构造函数实例化的对象所继承。相当于这一系列对象的公用变量的容器
4.对象的__proto__ 、 constructor属性 和 构造函数的属性prototype
一个对象创建成功后,默认存在很多属性和方法
其中:
constructor: 指向该对象的构造函数,对象不是凭空出现的,标准对象都由其对应的构造器(也叫构造函数)去实例化它,该属性就是该对象指向构造器的引用
__proto__: 一般指向该对象的构造器的原型
使用Object.create方法创建的对象,其 __proto__ 属性不是指向其构造函数原型,后面再详细讲解
构造函数也是对象,但同时它又是函数,因此,它具有对象的所有属性,但自己也有函数特有的一些属性和方法,如prototype,这是指向原型对象Prototype的引用
将对象、构造函数(构造器)、构造原型有的属性整理一下:
根据上图我们可以得知构造函数A、对象a、原型对象Prototype之间有两种关系:实例化关系(蓝线)和引用关系(红线)
- 构造函数A —— 实例化 ——> 原型对象Prototype
- 构造函数A —— 实例化 ——> 对象a
- 构造函数A —— prototype引用 ——> 原型对象Prototype
- 原型对象Prototype —— constructor引用 ——> 原型对象Prototype
- 对象a —— constructor引用 ——> 构造函数A
- 对象a —— __proto__ 引用 ——> 原型对象Prototype
根据以上关系我们可以得出以下结论:
a.constructor === A
a.__proto__ === A.prototype
a.__proto__.constructor === A
A.prototype.constructor === A
浏览器控制台展示:
注: 原型对象不能直接拿出来使用,它不是独立存在的,要想调用它,得通过它的构造函数来引用
a.__proto__ === Prototype ❌
a.__proto__ === A.prototype ✔️
5.对象__proto__的追根溯源(原型链)
动手在浏览器控制台输入以下代码:
//#构造函数声明
//1. 构造函数A
function A(name){
this.name = name
}
//2. 类B
class B{
constructor(name){
this.name = name
}
}
//3.构造函数Object,js的内置构造函数之一,直接使用即可
//#对象创建
//1. A 实例化对象a
let a = new A("a")
//2. B 实例化 对象b
let b = new B("b")
//3. 拷贝对象a
let c = Object.create(a)
c.name = "c"
//4. 拷贝对象b
let d = Object.create(b)
d.name = "d"
//5. 拷贝对象 {name:'e'}
let o = {name:'e'}
let e = Object.create(o)
/*
* 1.每个对象都有一个原型引用__proto__和构造函数引用constructor
* 2.构造函数对象则多一个prototype引用
* 3.接下来的任务是,帮每一个对象(实例对象、构造函数、原型对象)的引用属性都找到对应的对象
* 4.不清楚引用指向谁的,可以直接打在控制台查看
* 5.原型对象都是匿名对象,需要赋值给变量,方便操作
**/
//A:(prototype、__proto__、constructor)
let pa = A.prototype
let pf = A.__proto__
A.constructor === Function
//B:(prototype、__proto__、constructor)
let pb = B.prototype
B.constructor === Function //true
//A和B的构造函数相同,那么其构造函数的原型应该也相同
B.__proto__ === pf
//a:(__proto__、constructor)
a.__proto__ === pa
a.constructor === A
//b:(__proto__、constructor)
b.__proto__ === pb
b.constructor === B
//c:(__proto__、constructor)
c.__proto__ === a
//之前A和B的构造函数相同,__proto__指向同一个对象,但是这里有区别,c是由a拷贝而来,其__proto__指向了拷贝的对象
c.constructor === A
//d:(__proto__、constructor)
d.__proto__ === b
d.constructor === B
//e:(__proto__、constructor)
e.__proto__ === o
e.constructor === Object
//o:(__proto__、constructor)
let po = o.__proto__
o.constructor === Object
//pa:(__proto__、constructor)
pa.constructor === A
Object.prototype === po
//pb:(__proto__、constructor)
pb.constructor === B
pb.__proto__ === po
//po:(__proto__、constructor)
po.__proto__ === null
po.constructor === Object
//Function:(prototype、__proto__、constructor)
Function.prototype === pf
let p = Function.__proto__
Function.constructor === Function
//Object:(prototype、__proto__、constructor)
Object.prototype === po
Object.constructor === Function
Object.__proto__ === p
//p:(__proto__、constructor)
p.__proto__ === po
p.constructor === Function
//pf:(__proto__、constructor)
pf.__proto__ === po
pf.constructor === Function
//将上面对象整理下:
/*
*构造函数(3个引用属性): A、B、Object、Function
*原型对象(2个引用属性): pa、pb、po、pf、p
*实例对象(2个引用属性): a、b、c、d、e、o
*其它(无引用属性):null
*/
根据上面代码整理出来的对象,可以画出一个关系图:
- 绿色线:实例化
- 蓝色线:构造函数引用
- 黄色线:原型引用