重温JavaScript(lesson11):面向对象(4)

大家好,又见面了,我们一起重温JavaScript。上一次我们一起重温了对象属性特征的相关内容,我们来一张导图回顾一下上次的内容吧:

图片

你还了解这些属性特征的作用和使用方法吗?如果忘记了,建议你先回顾一下上次的内容:重温JavaScript(lesson10):面向对象(3)。这一次我们将要学习有关原型的内容。

我们在面向对象第一次分享中,说到了创建对象可以通过对象字面量和new关键字,当我们学习了原型之后我们就可以通过原型继承的方式创建一个新对象了。

1.原型对象的定义

开始说重点了啊!JS中的每个对象都有一个对象(单身狗已经哭晕在厕所~)。几乎每一个JS对象(null除外)都和另外一个对象相关联,这个"另外一个对象"就是原型或者叫原型对象

每一个对象都从原型继承属性,可以把原型对象看作是对象的基类。所有通过对象字面量的方式创建的对象都具有同一个原型对象,并且可以通过Object.prototype获得原型对象的引用。一个对象实例可以通过内部属性[[Prototype]]跟踪其原型对象,可以使用对象的Object.getPrototypeOf()方法读取[[Prototype]]属性的值。我们来看代码:

var person1 = {
  name: 'New_Name'
}
var person2 = {
  name: "重温新知"
}
console.log(Object.prototype);
//{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}
console.log(Object.getPrototypeOf(person1));
//{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}
console.log(Object.getPrototypeOf(person2));
//{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}
console.log(Object.getPrototypeOf(person1) === Object.getPrototypeOf(person2));
//true
console.log(Object.getPrototypeOf(person1) === Object.prototype);
//true

在这段代码中我们使用了Object.prototype获得了原型对象,使用了Object.getPrototypeOf(某对象)获取了此对象的原型对象。我们验证了所有通过对象字面量的方式创建的对象都具有同一个原型对象

我们刚刚说过,几乎每一个对象都有一个原型对象,那么没有原型对象的除了null还有谁呢?我们看代码:

var person = null;
console.log(Object.getPrototypeOf(person));
//Uncaught TypeError: Cannot convert undefined or null to object

这段代码说明null是没有原型对象的。我们再来看:

var person = Object.prototype;
console.log(Object.getPrototypeOf(person));
//null

这段代码说明:Object.prototype也是没有原型的,因为它不继承任何属性。而其他对象,包括其他原型对象都是普通对象,普通对象都具有原型。我们再来看一段代码:

var date = new Date();
console.log(Object.getPrototypeOf(date));
//{constructor: ƒ, toString: ƒ, toDateString: ƒ, toTimeString: ƒ, toISOString: ƒ, …}
var datePrototype = Object.getPrototypeOf(date);
var datePrototypePrototype = Object.getPrototypeOf(datePrototype);
console.log(datePrototypePrototype);
//{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}

从这段代码我们看到datePrototype是date的原型,而datePrototypePrototype又是datePrototype的原型。像这样一些列链接的原型对象就是所谓的"原型链"(prototype chain)。在了解了原型的一些基本知识后,我们看看和原型有关的创建对象的方式:Object.create()

2.Object.create()

Object.create()方法用于创建一个对象,其中的第一个参数是这个对象的原型,第二个参数是可选的,是对此对象属性的进一步描述。我们看代码:

var person = {
  name: "New_Name",
  age: 18
}
var obj1 = Object.create(person);
console.log(obj1);
/**
 {}
 _proto__:
age: 18
name: "New_Name"
__proto__: Object
 */
console.log(obj1.age);
//18
console.log(obj1.name);
//New_Name

在这段代码中,我们以person对象为原型创建出了obj1这个对象。下图为执行结果的截图:

图片

在浏览器控制台中打印出的obj1是一个空对象{},它没有自己的属性而是继承了原型的属性。我们再来看一个例子:

var person = {
  name: "New_Name",
  age: 18
}
var obj1 = Object.create(person);
var obj2 = Object.create(obj1);
console.log(obj2);
/**
{}
__proto__:
__proto__:
age: 18
name: "New_Name"
__proto__: Object
 */
console.log(obj2.name);
//New_Name
console.log(obj2.age);
//18

在这段代码中,首先以person为原型创建了obj1,之后以obj1为原型创建了obj2。控制台打印obj2的输出结果如下图所示:

 

 

结果表明obj本身没有name和age属性,其原型上也没有,其原型的原型上有。当通过obj2访问name和age属性的时候,首先看obj2自身有没有;由于obj2没有name和age属性,所以去其原型上找,看有没有对应的属性;由于obj2的原型obj1也没有name和age属性,就去obj1的原型上去找;找到了person对象,在person对象上找到了对应的属性,查找结束。这样的一个查找过程就是在原型链上查找的过程

此外,关于Object.create()我们还要注意:可以通过传入参数null来创建一个没有原型的对象,但是通过这种方式创建的对象不会继承任何东西,甚至不包括如toString()这样的基础方法。看代码:

var obj = Object.create(null);
console.log(Object.getPrototypeOf(obj));
//null

以上就是关于Object.create(原型)创建对象的方式的主要内容,至此我们已经学习到3种创建对象的方式:

图片

可能细心的同学会发现,浏览器控制台的打印结果中有一个属性那就是proto(注意前后都有两个下划线,本文为了表示方便,省略了)。我们暂且可以这么说:proto属性指向了此对象的原型。下面我们结合对象的构造函数来详细地研究一下:

3.对象构造函数与原型

我们在学习函数的时候说过函数是一等公民,函数也是对象。既然函数也是对象,那么函数也有对应的原型对象。而且这个原型对象将自动设为用该函数创建对象的原型

还有一点也是要补充说明的:我们在学习函数参数时候学习过函数的属性,函数有length属性,它表示传入函数的实参个数。函数还有一个重要的属性就是prototype属性,这个属性的值是指向函数原型对象的引用。对函数属性的一个小结:

图片

有了以上的前置知识,我们就可以看代码啦:

function Person() {}
Person.prototype.sayName = function() {
  console.log("New_Name");
}
const person1 = Person();
console.log(person1 === undefined);
//true
const person2 = new Person();
console.log(person2 === undefined);
//false
console.log(person2);
/**
Person {}
  __proto__:
  sayName: ƒ ()
  constructor: ƒ Person()
  __proto__: Object
 */

在这段代码中,我们定义了一个名为Person的空函数,并且通过两种方式进行调用:一种是作为普通函数进行调用,调用结果保存在person1中,由于Person函数没有返回值所以person1的结果为undefined;另外是作为构造函数调用,此时创建了新分配的对象,并将这个新对象作为函数的上下文,person2的值是这个新对象的引用。控制台打印出person2的详细信息见下图:

 

我们看到person2是Person类型的对像(也就是Person函数),本身什么属性也没有,但是这个函数Person有原型对象,它的proto属性就指向这个原型对象。而函数的原型对象有一个constructor属性,属性值为一个对象的引用,引用了Person函数本身。而constructor对象的原型被设置为新创建的对象的原型。也就是说原型和对象实例的关系是在创建对象时确立的。有没有懵圈(不爱了)啊?再来看代码:

function Person() {}
Person.prototype.sayName = function() {
  console.log("New_Name");
}
const person2 = new Person();
console.log(Person.prototype);
//{sayName: ƒ, constructor: ƒ}
console.log(Person.prototype.constructor === Person)
//true
console.log(Person.prototype.constructor.prototype === Object.getPrototypeOf(person2));
//true
console.log(person2.__proto__ === Person.prototype);
//true

在这段代码中,我们首先打印出Person函数的原型对象,这个原型对象有一个constructor属性。接着验证了Person函数的原型对象的constructor属性指向了Person函数本身。第三个打印语句验证了constructor对象的原型被设为新创建对象的原型。这是第二遍说这个内容了哦,当然重要的话说三遍:

1.每一个函数都具有一个原型对象。

2.每一个函数的原型对象都具有一个constructor属性,该属性指向函数本身。

3.constructor对象的原型设置为新创建的对象的原型。

你要是还不理解,看看下面这张图,配合着上面的文字和代码,你肯定能理解啦~

图片

到目前我们知道了:

1.对象有属性proto__,指向该对象的构造函数的原型对象。2.方法或函数也是对象,方法除了有属性__proto,还有属性prototype,prototype指向该方法的原型对象。

注意到没有?我们没有解释上面那段代码的最后一个打印语句:

console.log(person2.__proto__ === Person.prototype);
//true

那句代码说明通过某函数作为构造函数创建对象的原型和函数本身的原型是一个对象。符合我们刚才强调的第2点和第3点。

好了,这就是我们今天要分享的关于原型的有关内容了,下次我们将要学习面向对象中有关继承的知识,来张导图总结一下我们今天的内容:

图片

如有错误,请不吝指正。温故而知新,欢迎和我一起重温旧知识,攀登新台阶~

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

重温新知

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值