1:JavaScript是基于对象的,而且也存在继承。JavaScript中的继承是以原型为基础的继承。所以接下来我们谈一谈关于JavaScript中的原型。
2:函数的原型对象
当我们定义一个函数的时候,那么该函数会默认有一个属性:prototype。
针对定义的函数浏览器会按照一定的规则在内存中创建一个对象,这个对象称为该函数的原型对象。简称函数的原型。原型对象有一个默认的属性Constructor 指向了定义的函数。
函数的属性prototype 指向该函数的原型对象。
简单来说:函数的prototype属性指向了函数的原型对象,函数的原型对象的属性constructor指向了函数。
3:使用构造函数创建对象
1:当把一个函数作为构造函数 (理论上任何函数都可以作为构造函数) 使用new创建对象的时候,那么这个对象就会存在一个默认的不可见的属性,来指向了构造函数的原型对象。 这个不可见的属性我们一般用 [[prototype]] 来表示,只是这个属性没有办法直接访问到。我们没有办法通过 对象.prototype 去访问函数原型对象。
2:当我们通过一个构造方法创建多个对象的时候,那么这些对象隐含的[[prototype]]属性都同时指向了构造函数的原型对象。
3:我们可以通过代码给函数的原型对象添加属性和方法,那么通过构造方法创建的多个对象就共享了该原型对象中的添加的属性和方法。
4:如果要访问创建的一个对象中的某个a属性。如果对象中存在该属性,那么则直接返回,如果对象中不存在该属性,则去该对象的[[prototype]]属性指向的原型对象中去查找,如果找到返回,找不到,则继续找原型的原型对象(原型链)。
5:如果创建的某一个对象中包含了a属性,那么该对象的原型对象中如果也存在属性a,那么原型对象中的a属性就会被屏蔽。对象就不能访问原型对象中的a属性了。
6:通过创建的对象不能修改原型对象中的属性。只能访问原型对象中的属性。只能通过函数的prototype属性去修改原型对象的属性。
4:与原型相关的属性和方法
1: prototype 存在于构造函数中 (其实任意函数中都有,只是不是构造函数的时候prototype我们不关注而已) ,他指向了这个构造函数的原型对象。通过 【构造方法名.prototype 】访问函数的原型对象。
2:constructor属性存在于原型对象中,他指向了构造函数。如果我们给函数的原型对象指向了一个新的对象,那么原型对象的Constructor属性将不再指向原来的构造函数了。
3:__proto__ 属性(注意:左右各是2个下划线)
用构造方法创建一个新的对象之后,这个对象中默认会有一个不可访问的属性 [[prototype]] , 这个属性就指向了构造方法的原型对象。
但是在个别浏览器中,也提供了对这个属性[[prototype]]的访问(chrome浏览器和火狐浏览器。ie浏览器不支持)。访问方式:对象.__proto__。
尽量不要用这种方式去访问,因为操作不慎会改变这个对象的继承原型链。
4:hasOwnProperty() 方法
我们用去访问一个对象的属性的时候,这个属性既有可能来自对象本身,也有可能来自这个对象的[[prototype]]属性指向的原型。可以通过hasOwnProperty() 来判断属性是属于对象还是属于原型对象的。如果返回true,则属于对象,如果属性属于原型或者不存在,则返回false。
5:in 操作符
in操作符用来判断一个属性是否存在于这个对象中。但是在查找属性时候,先在对象本身中找,如果对象找不到再去原型中找。换句话说,只要对象和原型中有一个地方存在这个属性,就返回true。否则返回false。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>原型对象</title>
<script type="text/javascript">
//关于函数的原型对象
function Person(){}
//obj 就是 Person函数的原型对象
var obj = Person.prototype;
//函数的原型对象的Constructor指向了Person函数。
var con = obj.constructor;
//测试
alert(con === Person);//true
//对象的原型对象
var person = new Person();
//对象的原型对象指向了函数的原型对象
//不能直接访问对象的原型对象,得到的结果是undefined。
alert(person.prototype);//undefined
//给函数原型对象添加属性和方法
obj.show = function(){
alert("我的名字:"+this.name);
}
obj.name = "Tom";
//通过Person 创建的对象都可以访问 原型对象上的属性name和方法
var p1 = new Person();
var p2 = new Person();
alert(p1.name);//Tom
alert(p2.name);//Tom
p1.show();//我的名字:Tom
p2.show();//我的名字:Tom
//给p1添加属性,那么通过p1不能在访问原型对象上的属性name
p1.name = "Jone";
alert(p1.name);//Jone
p1.show();//我的名字:Jone
//p2没有name属性,所以只能还是访问原型对象中的name
alert(p2.name);//Tom
p2.show();//我的名字:Tom
//在Chrome浏览器中访问对象的__proto__属性。
alert(p1.__proto__ === Person.prototype);//true
//hasOwnProperty()测试属性属于谁
alert(p1.hasOwnProperty("name"));//true
alert(p2.hasOwnProperty("name"));//false
//in 操作符
alert("name" in p1);//true
alert("name" in p2);//true
alert("sex" in p1);//false
//将一个新对象赋值给函数的原型对象
//函数的原型对象的Constructor 指向 原函数。
alert(Person.prototype.constructor === Person);//true
//将一个新对象赋值给函数的原型对象。那么原型对象的Constructor属性将不再指向原来的构造函数了。
Person.prototype = {
name:"Smith",
age:20
};
alert(Person.prototype.constructor === Person);//false
</script>
</head>
<body>
</body>
</html>
5:原型模型创建对象的缺陷
原型中的所有的属性都是共享的。也就是说,用同一个构造函数创建的对象去访问原型中的属性的时候,大家都是访问的同一个对象,如果一个对象对原型的属性进行了修改,则会反映到所有的对象上面。但是在实际使用中,每个对象的属性一般是不同的。张三的姓名是张三,李四的姓名是李四。但是,这个共享特性对方法(属性值是函数的属性)又是非常合适的。所有的对象共享方法是最佳状态。这种特性在c#和Java中是天生存在的。所以对于需要共享的属性(类似于java中的static属性)和方法可以在原型对象中添加。
6:构造函数模型创建对象的缺陷
在构造函数中添加的属性和方法,每个对象都有自己独有的一份,大家不会共享。这个特性对属性比较合适,但是对方法又不太合适。因为对所有对象来说,他们的方法应该是一份就够了,没有必要每人一份,会造成内存的浪费和性能的低下。
7:使用组合模式解决上述两种缺陷
原型模式适合封装方法,构造函数模式适合封装属性,综合两种模式的优点就有了组合模式。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>原型构造函数创建对象</title>
<script type="text/javascript">
//属性封装到构造方法中。
function Person(name,age){
this.name = name;
this.age = age;
}
//方法添加到函数原型中
Person.prototype.show = function(){
alert("name = "+this.name + " age = "+this.age);
}
var p1 = new Person("小刚",19);
var p2 = new Person("小马",18);
//name = 小刚 age = 19
p1.show();
//name = 小马 age = 18
p2.show();
</script>
</head>
<body>
</body>
</html>
8:动态原型模式创建对象
前面的组合模式,也并非完美无缺,有一点也是感觉不是很完美。把构造方法和原型分开写,总让人感觉不舒服,应该想办法把构造方法和原型封装在一起,所以就有了动态原型模式。动态原型模式把所有的属性和方法都封装在构造方法中,而仅仅在需要的时候才去在构造方法中初始化原型,又保持了同时使用构造函数和原型的优点。
组合模式和动态原型模式是JavaScript中使用比较多的两种创建对象的方式。
建议以后使用动态原型模式。他解决了组合模式的封装不彻底的缺点。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>动态原型模式创建对象</title>
<script type="text/javascript">
//动态原型模式创建对象
function Person(name,age){
this.name = name;
this.age = age;
//第一次调用Person 的时候,show不存在,就添加show 方法。
//以后调用,因为show方法存在了,就直接调用了。
if(typeof this.show !== "function"){
//在原型中添加方法
Person.prototype.show = function(){
alert("name = "+this.name + " age = "+this.age);
}
}
}
var p1 = new Person("小刚",19);
var p2 = new Person("小马",18);
//name = 小刚 age = 19
p1.show();
//name = 小马 age = 18
p2.show();
</script>
</head>
<body>
</body>
</html>
PS:笔者有大量的学习资料:java、python、大数据、人工智能、前端等。需要的小伙伴请加群:711408961
笔者的b站中有一些教学视频分享。感兴趣的小伙伴可以关注:https://space.bilibili.com/412362068