说到原型呀,我们就不得不来了解一下 构造函数
。
构造函数
是一个专门用来创建对象的函数,当需要创建的对象格式一样时,我们就可以使用构造函数来创建。如下:
function User(name, age) {
this.name = name;
this.age = age;
};
const user1 = new User('Jack', 21);
const user2 = new User('Tom', 18);
这样是不是比我们平时用对象字面量来创建更加方便呢?
我们接着往下看:
function User(name, age) {
this.name = name;
this.age = age;
this.sayHello = function () {
console.log(`我叫${this.name}, 今年${this.age} 岁`);
}
};
const user1 = new User('Jack', 21);
console.log(user1.sayHello()); // 我是Jack, 今年21岁。
const user2 = new User('Tom', 18);
console.log(user2.sayHello()); // 我是Tom, 今年18岁。
当创建的实例对象,有一个共同的方法时,如果像上面的例子一样,把 sayHello
这个方法保存在不同的存储空间中,就会对性能不好,所以我们就需要对它进行优化:
function User(name, age) {
this.name = name;
this.age = age;
};
User.prototype.sayHello = function() {
console.log(`我叫${this.name}, 今年${this.age} 岁`);
};
const user1 = new User('Jack', 21);
console.log(user1.sayHello()); // 我是Jack, 今年21岁。
const user2 = new User('Tom', 18);
console.log(user2.sayHello()); // 我是Tom, 今年18岁。
console.log(user1.sayHello === user2.sayHello); // true
这个时候,user1
和 user2
的 sayHellow
函数用的就是同一个了。
而且,当我们直接输出 user1
或者 user2
查看对象的时候,是看不到 sayHello
这个函数的,那我们怎么可以调用呢?这就得说说原型了,也就是我们今天的主角。
使用构造函数的注意事项:
- 为了便于分清楚普通函数和构造函数,我们一般把构造函数的首字母大写,也就是大驼峰命名!
- 构造函数如果不
new
的话,和普通函数没有任何区别! - 当你
new
一个构造函数的时候,构造函数内部会生成一个空对象,并且this
会自动指向这个空对象,最后会自动返回this
指向的这个对象。如果手动return
一个原始值的话,构造函数会把这个return
忽略,(还是返回this
指向的那个对象);如果是return
一个引用值的话,那么就会把这个引用值当作返回对象,忽略this
指向的那个对象。
原型
原型,其实就是一个普通的对象,每一个函数对象,都有一个原型(prototype
),现在我们就来看看原型长什么样吧,我们通过 __proto__
来访问原型:
function User() {};
const user = new User();
console.log(user.__proto__); // {constructor: f};
我们可以看到,user
的原型是一个对象,对象中有一个 constructor
的属性,我们把它叫做构造器,它的值一个函数,我们再来看看 constructor
的值是什么吧:
// 接着上个代码块
console.log(user.__proto__.constructor); // function User() {};
从上面的输出,我们可以看到 user.__proto__.constructor
指向的是构造出 user
这个实例对象的构造函数 User
。就如下图所表示的原因:
[这里有张图片]
这样看起来是不是就很清晰了呢?
那么我们回过头来看看刚刚的之前的那个示例,代码如下:
function User(name, age) {
this.name = name;
this.age = age;
}
User.prototype.sayHello = function() {
console.log(`我叫${this.name}, 今年${this.age} 岁`);
};
const user1 = new User('Jack', 21);
const user2 = new User('Tom', 18);
console.log(user1.__proto__); // {constructor: f};
console.log(user1.__proto__ === User.prototype); // true
console.log(user1.__proto__.constructor === User); // true
// 还记得下面的这个代码吗
user1.sayHello(); // 我是Jack, 今年21岁。
user1 上根本就没有 sayHello
这个方法呀,那么它是怎么找到这个函数并且成功执行的呢?
这个可全要归功于 __proto__
,这个大功臣呀,让我来简单的介绍一下。
proto
每个对象身上都有一个属性,那就是 __proto__
了,它是用来指向构造出它的构造函数的原型对象的,相信刚刚上面的示例代码,大家也可以看出来吧。它还有个功能,让我们来看看下面的代码吧:
function A() {};
A.prototype.name = 'aaaa';
const a = new A();
function B() {};
B.prototype = a;
const b = new B();
function C() {};
C.prototype = b;
const c = new C();
console.log(c.name); // aaaa
我们明明没有给 c
添加 name
属性,为什么可以输出呢?
这是因为,当 c
想要访问 name
的值时,它会现在自身查找,自身没有时,它会沿着隐式原型 __proto__
去找 C.prototype
,看它的身上有没有,如果没有的话,他就会继续沿着 __proto__
去找 B.prototype
,看它的身上有没有,如果没有的话,他就会继续沿着 __proto__
去找 A.prototype
,然后看到 A.prototype
上有 name
这个属性,那么就会返回 name
的属性值。如果还没有找到,它就会继续沿着隐式原型 __proto__
去找 Object.prototype
,看它的身上有没有,如果没有的话,他就会沿着 __proto__
去找,然后返回 null
。这样一层一层靠着 __proto__
形成的链式结构,我们就把它叫做原型链。
console.log(Object.prototype.__proto__ === null); // true
null
是原型链的顶端!
所以有些时候,我们明明没有创建一些方法,却可以使用,就是要归功于原型链!
好了,今天的原型和原型链就到这里了,下次会整理一份更详细的笔记给大家~