其实原型我看了有一段时间了,也没有看很长时间,就是找一个下午的时间听听课,然后就感觉很懵,似懂非懂的,接着就自己想想,然后就还是懵。平时翻翻技术类的公众号或者CSDN上的文章也会涉及到原型的内容,加上前天我又看一一遍讲解,看的多了慢慢的就更熟悉更了解了。
今天,下定决心要总结总结写写原型了。内容多少会有点问题,谅解谅解,毕竟是菜鸟的小白为了记录自己的学习过程。
构造函数和实例对象
构造函数也是一个函数,它与普通函数的区别就在于构造函数名首字母要大写。通过构造函数创建对象的过程叫对象的实例化,创建的对象是实例对象。Object构造函数,是系统提供好的构造函数,我们自定义的构造函数也是构造函数。
var obj2 = new Object();
function Person(uname, uage) {
this.uname = uname;
this.uage = uage;
}
Person("xz", 18);
构造函数中的属性和方法,分为两种:实例成员和静态成员。一般构造函数中都是实例成员,静态成员的添加方法是直接在构造函数上面添加的。
上面的uname和uage都是实例成员,静态成员:Person.sex = “male”,这里的sex就是静态成员。
实例成员:构造函数内部通过this添加的属性和方法
静态成员:构造函数本身添加的属性和方法
创建对象
再谈谈创建对象的几种方式吧。
一般是三种方式:对象字面量创建、Object创建、自定义构造函数创建。其实总的来说是两种方式:对象字面量创建和通过构造函数创建。
// 1. 字面量创建对象
var obj1 = {
name: "lyf",
age: 18
}
// 2. 通过 new Object
var obj2 = new Object();
obj2.name = "zly";
obj2.age = 17;
// 3. 通过自定义构造函数
function Person(uname, uage) {
this.uname = uname;
this.uage = uage;
}
// 通过new创建出来的对象,是构造函数实例化出来的对象 —— 对象的实例化
var obj3 = new Person("xz", 18);
new关键的过程
- 先创建一个空对象
- 把空对象赋值给this
- 把属性和方法都挂在this上面
- 最后把this对象返回
所以这就是在创建对象时,不需要返回值的原因。
原型
上面说完了构造函数和实例对象,接下来我们就来介绍介绍原型。
每个函数都有一个prototype属性叫原型,这个属性是一个对象,所以叫原型对象。
每个对象都有一个__proto__属性,叫对象的原型,它指向构造该对象的构造函数的原型对象。
自定义构造函数:
// 只要是函数,就有原型对象
function Person(uname, uage) {
this.uname = uname;
this.uage = uage;
}
// 构造函数也是函数, 所以构造函数也有自己的原型对象
var obj = new Person("xz", 18);
// 通过prototype访问函数的原型对象
console.log(Person.prototype);
这就是原型对象。可以看到,原型对象里有constructor和__proto__两个属性。
上面说了,只要是对象就有__proto__ 属性,指向构造该对象的构造函数的原型对象。
__proto__是对象的原型,这里__proto__指向创建Person.prototype对象的构造函数的原型对象 —— Object.prototype。
constructor是构造函数,指向创建该对象的函数。简单来说,就是记录谁创建了这个对象。这里constructor指向创建Person.prototype对象的函数 —— Person。Person创建了Person.prototype原型对象,所以constructor指向Person。
// 通过prototype访问函数的原型对象
console.log(Person.prototype); // Person的原型对象
console.log(Person.prototype.__proto__); // Person原型对象的原型
// 原型对象里有一个constructor属性,该属性指向创建它(原型对象)的函数
console.log(Person.prototype.constructor);
console.log(Person.prototype.__proto__ === Object.prototype);
第一个输出的是Person构造函数的原型对象:Person.prototype。
第二个输出的是Person构造函数的原型对象的原型:Object.prototype。Object是顶级对象,任何一个对象都是Object的实例化对象。
第三个输出的是Person构造函数的原型对象的构造函数:Person。constructor属性是指向创建该对象的构造函数。
第四个输出的是true。
看了构造函数的原型,现在再来看下实例对象的原型
var obj = new Person("xz", 18);
// 构造函数实例化的对象
console.log(obj);
// 实例化对象有一个原型属性:__ptoro__
// 对象的__proto__ 属性 指向构造函数的原型对象
console.log(obj.__proto__);
// 对象的原型 === 构造函数的原型对象
console.log(obj.__proto__ === Person.prototype);
第一个输出的是obj实例对象自己,对象除了有构造函数定义的属性外,还有有__ptoto__属性。
第二个输出的是obj的原型 —— Person构造函数。因为obj是通过Person构造函数实例化出来的,所以obj的原型是Person构造函数d的原型对象。
第三个输出的是true。因为obj的原型是Person构造函数原型对象,Person构造函数的原型对象是Person.prototype,所以输出true。
来一张obj、Person、Person.prototype的图,就明白了
这个图再完善一下,加上Object就更清晰了。我们都知道,所有的对象到最后都会返回Object,Object对象是顶级对象。
这样的话,是不是有人还想试试Object.prototype.proto?那就试试呗。
返回null,这就解释了为什么Object是顶级对象,再往上找就没有了。
原型链
讲完原型,终于可以说原型链了。接下来原型链就在原型的基础上就稍稍的简单了一点。
看图看图。
没错,就是看这个图,有没有看到对象的__proto__,用__proto__连接起来的就是原型链。当访问到对象本身没有的属性或者方法时,此时就会顺着原型链进行查找,一直找到Object,prototype,如果找到的话就调用返回,如果没有找到就会返回undefined。
还是这个例子。
function Person(uname, uage) {
this.uname = uname;
this.uage = uage;
}
// 构造函数也是函数, 所以构造函数也有自己的原型对象
var obj = new Person("xz", 18);
假如现在访问sex属性。肯定返回undefined,整个原型链上都没有sex属性。
console.log(obj.sex);
我在Perso.prototype上添加sex属性后,再去访问。此时就可以访问到了,obj对象本身没有这个属性,但是obj的原型Peroson.prototype上有,obj通过__proto__属性访问Peroson.prototype得到sex属性,最后返回。
Person.prototype.sex = "male";
同理,不在Peroson.prototype上添加sex属性,在Object.prototype上添加。同样的obj依然可以访问到,通过原型链中__proto__属性。
当obj对象、obj对象的原型以及Object原型对象都有sex属性时,obj对象会返回哪个值?
Person.prototype.sex = "male";
Object.prototype.sex = "女";
function Person(uname, uage, sex) {
this.uname = uname;
this.uage = uage;
this.sex = usex;
}
// 构造函数也是函数, 所以构造函数也有自己的原型对象
var obj = new Person("xz", 18, "男");
console.log(obj.sex);
返回的是obj对象自己的属性值。
需要注意的是,先从自己找,自己没有就找自己的原型,原型没有就再往上以及原型找,就这样一层一层的找。
JavaScript查找机制:就近原则。
补充
这种形式是在原型对象上追加属性
Person.prototype.sex = "male";
这种形式是改写了原型对象,也就是说,这个把系统提供的Person.prototype给覆盖了。再访问Person.prototype时只有sex这一个属性了。
Person.prototype = {
sex: "male"
}
覆盖的问题要谨慎。
emmm,结束了结束了。原型和原型链暂时就了解了这些哈。