首先我们应该会问为什么会有原型这个概念,其次原型是解决什么问题的。
提到原型,必须要提到构造函数。
构造函数
构造函数是把客观事物封装成抽象的类,隐藏属性和方法,仅对外公开接口
- 说白了构造函数就是包含一系列属性和方法的类,便于被其他实例或类继承,达到共享的效果。
私有属性、公有属性、静态属性
私有属性和方法:只能在构造函数内访问不能被外部所访问(在构造函数内使用var声明的属性)公有属性和方法(或实例方法):对象外可以访问到对象内的属性和方法(在构造函数内使用this设置,或者设置在构造函数原型对象上)静态属性和方法:定义在构造函数上的方法(比如Cat.xxx),不需要实例就可以调用(例如Object.assign())
function Cat(name, color) {
var heart = '❤️' //私有属性
var stomach = '胃' //私有属性
var heartbeat = function () {
console.log(heart + '跳')
} //私有方法
this.name = name //公有属性
this.color = color //公有属性
this.jump = function () {
heartbeat() // 能跳起来表明这只猫是活的,心也就能跳
console.log('我跳起来了~来追我啊')
} //公有方法
}
Cat.descript = '我这个构造函数是用来生产出一只猫的' //静态属性
Cat.actingCute = function () {
console.log('一听到猫我就想到了它会卖萌')
} //静态方法
Cat.prototype.cleanTheBody = function () {
console.log('我会用唾液清洁身体')
} //公有方法
在构造函数中,由于公有属性和方法可以被实例共享,公有属性和方法也是用的最多的。但是公有属性和方法既可以在构造函数内部用this来定义,又可以设置在构造函数的原型上面,那是不是多此一举,直接定义在构造函数内部不好吗,为什么还要搞出原型的概念呢!
答案是肯定的,在构造函数内部用this定义属性和方法肯定有不足的地方,那不足的地方在哪呢?我们通过一个例子来解析。
function Cat(name, color) {
this.name = name
this.color = color
this.num = 10
this.jump = function () {
console.log('我跳起来了~来追我啊')
}
}
let cat1 = new Cat('mimi', 'red')
let cat2 = new Cat('xiaohei', 'black')
console.log(cat1.num); //10
console.log(cat2.num); //10
console.log(cat1.num === cat2.num); //true
cat1.jump() //我跳起来了~来追我啊
cat2.jump() //我跳起来了~来追我啊
console.log(cat1.jump === cat2.jump); //false
通过上面的示例我们可以看出,对于公有属性而言,无论被多少实例继承都是一样的内容,但对于在构造函数内部用this定义的引用数据类型,每次生成实例,都是新开辟一个内存空间存方法。这样会导致内存的极大浪费,从而影响性能。
我们有没有什么方法可以使得每个实例共享同一种方法呢?
答案是有的,就是利用原型,在构造函数的原型上面设置方法,从而达到共享的作用。
function Cat(name, color) {
this.name = name
this.color = color
}
Cat.prototype.jump = function () {
console.log('我跳起来了~来追我啊')
}
let cat1 = new Cat('mimi', 'red')
let cat2 = new Cat('xiaohei', 'black')
cat1.jump() //我跳起来了~来追我啊
cat2.jump() //我跳起来了~来追我啊
console.log(cat1.jump === cat2.jump); //true
除此之外,我们还可以对原型的方法进行修改,可以达到全部更新的效果。
Cat.prototype.jump = function () {
console.log('我跳起来了')
}
cat1.jump() //我跳起来了
cat2.jump() //我跳起来了
console.log(cat1.jump === cat2.jump); //true
总结
原型是为了解决在构造函数内部用this定义公有方法时,每次生成实例,都是新开辟一个内存空间存方法。这样会导致内存的极大浪费,从而影响性能的问题。而原型可以达到共享的效果。
公共属性定义到构造函数里面,公共方法我们放到原型对象身上。
原型
概念
在JavaScript中是使用构造函数来新建一个对象的,每一个构造函数的内部都有一个 prototype 属性,它的属性值是一个对象,这个对象包含了可以由该构造函数的所有实例共享的属性和方法。当使用构造函数新建一个对象后,在这个对象的内部将包含一个指针,这个指针指向构造函数的 prototype 属性对应的值,在 ES5 中这个指针被称为对象的原型。
概念很多,我们就拆分下来解析一下。先看第一句话。
- 在JavaScript中是使用构造函数来新建一个对象的,每一个构造函数的内部都有一个 prototype 属性,它的属性值是一个对象,这个对象包含了可以由该构造函数的所有实例共享的属性和方法。
上面这句话其实很好理解,通过上一章的构造函数,我们知道了构造函数为什么会有原型。上面这句活概括起来就是构造函数内部有一个prototype属性,这个属性包含了可以由所有实例共享的属性和方法。
下面看第二句
- 当使用构造函数新建一个对象后,在这个对象的内部将包含一个指针,这个指针指向构造函数的 prototype 属性对应的值,在 ES5 中这个指针被称为对象的原型。
简单理解就是当构造函数实例化后,实例对象内部包含一个指针用来指向可以共享的构造函数上在prototype 上定义的方法,这个指针就是对象的原型。
其实就是:
obj.__proto__ === Star.prototype
其中Star是构造函数,obj是实例化对象,而__proto__就是对象的原型。
原型链
概念
当访问一个对象的属性时,如果这个对象内部不存在这个属性,那么它就会去它的原型对象里找这个属性,这个原型对象又会有自己的原型,于是就这样一直找下去,也就是原型链的概念。
这个概念其实很好理解,就是在对象的原型上一直找下去,直到找到或者找到头为止。