JS基础之原型
学习之前,我要提出一个自己的思想观点:技术知识点的提出,都是为了解决问题。所以不妨先在心中设置一些问题:
“它是什么,它用来解决什么问题,它是如何解决的”
一切要从对象开始
1. 构造函数
这里,我们要明白一个概念,那就是对象皆为函数所创建。创建对象的函数,我们称之为构造函数。例如:
var obj= new Object(); // 原生构造函数
var person1 = new Person("XiaoYan"); //自定义的构造函数
var a = {}; // 语法糖, 相当于 new Object()
2. constructor属性
如果我们想批量的创建对象,就需要创建自定义的构造函数,从而自定义对象类型的属性和方法。例如:
// 构造函数Person
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = function() {
alert(this.name);
}
}
// 批量创建对象
var person1 = new Person('Luffy', 18, 'poacher');
var person2 = new Person('Usopp', 18, 'Gunner');
这样创建出来的对象person1和person2就分别保存着Person不同的实例,并且他们都有constructor属性指向Person,来代表他们与Person()之间的关系。
3.创建对象的问题
虽然说批量创建对象的问题解决了,但是构造函数依旧存在缺点:
对于共享的方法,它是分别保存的!!也就是说,person1.sayName() 与person2.sayName() 是分别存储的!!
因此,我们需要一种解决方案来解决他们——原型登场啦!!
到这里我们明白了,原型解决了对象中共享的属性和方法名不副实的问题。
原型模式
1. 什么是原型
原型模式,是将共享的属性和方法放在原型对象,以便实现封装性。不需要共享的,对象自己设置。
原型对象,简称原型。
我们创建的每一个函数都有prototype(原型)属性,这个属性是一个指针,它指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。—— 《JavaScript高级程序设计》
需要注意的是,每个函数都有prototype属性,而非构造函数独有的。(构造函数,宁有种乎!)
// 构造函数Person
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
}
}
Person.prototype.sayName = function() {
alert(this.name);
}
// 批量创建对象
var person1 = new Person('Luffy', 18, 'poacher');
var person2 = new Person('Usopp', 18, 'Gunner');
person1.sayName(); //"Luffy"
person1.sayName(); //"Usopp"
OK,现在我们要暂停一下,理清思路。这里有一个关系网——对象,构造函数,原型。
2. 对象,构造函数和原型的关系
为了便于理解,请让我打一个比方。
| 原型 | 对象 | 构造函数 |
|---|---|---|
| 爸爸 | 儿子 | 妈妈 |
| 概念 | 比喻 | 关系 |
|---|---|---|
| 构造函数创建了对象,对象共享了原型的属性和方法 | 妈妈生出了儿子,儿子继承了爸爸的基因。 | |
| 构造函数的prototype属性指向原型对象 | 妈妈通过prototype认定谁为老公 | 夫妻关系 |
对象的_proto_指向原型对象 | 儿子通过_proto_认定谁为爸爸 | 父子关系 |
-
每一个对象都有一个隐藏属性
_proto_(隐式原型),这个属性指向了其构造函数的原型对象。 -
创建了自定义函数之后,原型对象默认只会取得
constructor属性,也就是说constructor也是共享的 -
ECMAScript提供了一些方法来验证他们的关系:
-
isPrortotypeOf 确定对象之间的关系(父子关系)
Person.prototype.isPrortotypeOf(person1); //true -
Object.getPrototypeOf 取得一个对象的原型 (找爸爸)
Object.getPrototypeOf(person1); -
hasOwnProperty 确定一个属性是否来自实例,如果是返回true
-
-
当给对象添加一个元素时,这个属性会屏蔽原型对象中的同名对象;通过delete可以删除实例元素
创建自定义类型
回到最初的问题,原型是什么,它解决了什么问题,它是如何解决的
原型是一种对象,它包含着创建对象时需要共享的元素和方法。用它可以解决对象中共享的属性和方法名不副实的问题。
1. 默认模式
但是,它并不是完美的,原因便是其共享的本性。例如:
// 构造函数Person
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
}
}
Person.prototype.sayName = function() {
alert(this.name);
}
// 共享引用类型值
Person.prototype.friends = ['Nami','Solon'];
// 批量创建对象
var person1 = new Person('Luffy', 18, 'poacher');
var person2 = new Person('Usopp', 18, 'Gunner');
// Luffy添加自己的伙伴
person1.friends.push('monkey');
person1.friends; // "Nami, Solon, monkey"
person2.friends; // "Nami, Solon, monkey"
所有的实例都共享着同一个数组,当不需要如此时,应当怎么解决呢?
将构造函数的特点——个体和原型的特点——共享结合起来,那边出现了一种组合使用的默认模式。
// 构造函数Person
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.friends = ['Nami','Solon'];
}
}
Person.prototype = {
constructor: Person,
sayName: function() {
alert(this.name);
}
}
2. 稳妥构造函数模式(了解)
Durable Objects
// 构造函数Person
function Person(name, age, job) {
var o = new Object();
//定义私有变量和函数
//添加方法
o.sayName = function() {
alert(name);
}
return o;
}
var person1 = Person('Luffy', 18, 'poacher');
person1.sayName(); //Luffy
person1.name;// undefined
这种模式下,除了sayName, 没有其他方法访问name
小结
ECMAScript支持面向对象(OO)编程,虽然没有类,但可以通过原型模式来创建对象。
补充一点,函数也是对象,所以函数也有自己的爸爸(原型对象);原型对象也是对象,所以它也有自己的爸爸......顺着_proto_可以发现一条链条,那便是原型链。原型链用来做什么呢?那便是OO最重要的事情——继承。