前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JavaScript面试卷(二) -- 复杂的创建对象模型

JavaScript面试卷(二) -- 复杂的创建对象模型

作者头像
用户7293182
发布2022-01-17 21:56:53
6010
发布2022-01-17 21:56:53
举报
文章被收录于专栏:jQuery每日经典

接着上一篇文章说,上一篇创建的对象没有向外部提供直接设置属性值的入口。都是在new 创建对象时,给定默认值。

本文在创建新的实例时指定属性值。

上面JavaScript 定义过程中使用了一种设置默认值的特殊惯用法:

代码语言:javascript
复制
this.name = name || "";

JavaScript 的逻辑或操作符(||)会对第一个参数进行判断。如果该参数值运算后结果为真,则操作符返回该值。否则,操作符返回第二个参数的值。因此,这行代码首先检查name是否 是对 name 属性有效的值。 如果是,则设置其为this.name 的值。否则设置this.name 的值为空的字符串。

由上面的定义,当创建对象的实例时,您可以为本地定义的属性指定值。

代码语言:javascript
复制
var jane = new Engineer("belau");

此时,Jane的属性如下所示:

代码语言:javascript
复制
jane.name == "";
jane.dept == "engineering";
jane.projects == [];
jane.machine == "belau"

注意,由上面对类的定义,您无法为诸如name 这样的继承属性指定初始值。如果想在JavaScript 中为继承的属性指定初始值,需要在构造器函数中添加更多的代码。

下面是Engineer 构造器的重新定义:

代码语言:javascript
复制
function Engineer (name, projs, mach) {
 this.base = WorkerBee;
 this.base(name, "engineering", projs);
 this.machine = mach || "";}

假如您创建了一个新的Engineer 对象, 如下所示:

代码语言:javascript
复制
var jane = new Engineer(
    "Doe, Jane", 
    ["navigator", "javascript"], 
    "belau");

执行时,JavaScript 会有以下步骤:

  1. new 操作符创建了一个新的通用对象,并将其__proto__ 属性设置为 Engineer.prototype。
  2. new 操作符将该新对象作为 this 的值传递给 Engineer 构造器。
  3. 构造器为该新对象创建了一个名为base的新属性,并指向 WorkerBee 的构造器。 这使得 WorkerBee 构造器成为Engineer 对象 的一个方法。 base 属性的名称并没有什么特殊性,我们可以使用任何其他合法的名称来代替;base仅仅是为了贴近它的用意。
  4. 构造器调用base 方法,将传递给该该构造器的参数中的两个,作为参数传递给base 方法,同时还传递一个字符串参数“engineering”。显式地在构造器中使用“engineering”表明所有 Engineer 对象继承的 dept 属性具有相同的值,且该值重载了继承自 Employee 的值。
  5. 因为 base 是 Engineer 的一个方法,在调用 base 时,JavaScript 将在步骤 1 中创建的对象绑定给 this 关键字。这样,WorkerBee 函数接着将 “Doe,Jane” 和 “engineering”参数传递给Employee 构造器函数。当从Employee 构造器 函数返回时, WorkerBee 函数用剩下的参数设置 projects 属性。
  6. 当从 base 方法返回后,Engineer 构造器将对象的 machine 属性初始化为 “belau”。
  7. 当从构造器返回时, JavaScript 将新对象赋值给 Jane 变量。

可以认为,在 Engineer 的构造器中调用了 WorkerBee 的构造器,也就为 Engineer 对象设置好了继承关系。事实并非如此。调用WorkerBee 狗仔器确保了 Engineer 对象以所有在构造器中所有的属性被调用。但是,如果后续在 Employee 或者 WorkerBee 原型中添加了属性,那些属性不会被 Engineer 对象继承。例如,假设如下语句:

代码语言:javascript
复制
function Engineer (name, projs, mach) {
 this.base = WorkerBee;
 this.base(name, "engineering", projs);
 this.machine = mach || "";
}
var jane = new Engineer(
        "Doe, Jane", 
        ["navigator", "javascript"], 
        "belau");
Employee.prototype.specialty = "none";

对象 jane 不会继承 specialty 属性。您必须显式地设置原型才能确保动态的继承。如果修改成如下的语句:

代码语言:javascript
复制
function Engineer (name, projs, mach) {
 this.base = WorkerBee;
 this.base(name, "engineering", projs);
 this.machine = mach || "";
}
var jane = new Engineer(
         "Doe, Jane", 
         ["navigator", "javascript"], 
         "belau");
Employee.prototype.specialty = "none";

现在 jane 对象的specialty 属性为 “none” 了。

继承的另一种途径是使用call() / apply() 方法。

代码语言:javascript
复制
function Engineer (name, projs, mach) {
 WorkerBee.call(this, name, "engineering", projs);
 this.machine = mach || "";}

二、本地值和继承值

在访问一个对象的属性时, JavaScript 将执行下面的步骤:

  1. 检查本地值是否存在。如果存在,返回该值、
  2. 如果本地值不存在,检查原型链(通过__proto__属性)
  3. 如果原型链中的某个对象具有指定属性的值,则返回该值。
  4. 如果这样的属性不存在,则对象没有该属性。

以上步骤的结果依赖于是如何定义的。最早的例子中具有如下定义:

代码语言:javascript
复制
function Employee () {
 this.name = "";
 this.dept = "general";
}
function WorkerBee () {
 this.projects = [];
}
WorkerBee.prototype = new Employee;

基于这些定义,假定通过如下的语句创建 WorkerBee 的实例 amy:

代码语言:javascript
复制
var amy = new WorkerBee;

则 amy 对象将具有一个本地属性,projects。name 和 dept 属性则不是 amy 对象本地的,而是从 amy 对象的 __proto__属性获得的。因此,amy 将具有如下的属性值:

代码语言:javascript
复制
amy.name == "";
amy.dept == "general";
amy.projects == [];

1. 优先本地值

现在,假设修改了与 Employee 的相关原型中的 name 属性的值:

代码语言:javascript
复制
Employee.prototype.name = "Unknown"

乍一看,可能觉得新的值传递给所有 Employee 的实例。然而,并非如此。

在创建 Employee 对象的任意实例时,该实例的 name属性将获得一个本地值(空的字符串)。这就意味着在创建一个新的 Employee 对象作为 WorkerBee 的原型时,WorkerBee.prototype 的 name 属性将具有一个本地值。因此,当 JavaScript 查找 amy 对象(WorkerBee 的实例)的 name 属性时,JavaScript 将找到 WorkerBee.prototype 中的本地值。因此,也就不会继续在原型链中向上找到 Employee.prototype 了。

2. 修改所有后代的某属性值

如果想在运行时修改一个对象的属性值并且希望该值被所有该对象的后代所继承,您就不能在该对象的构造器函数中定义该属性。而应该将该属性添加到该对象所关联的原型中。例如,假设将前面的代码作如下修改:

代码语言:javascript
复制
function Employee () {
 this.dept = "general";
}
Employee.prototype.name = "";
function WorkerBee () {
 this.projects = [];
}
WorkerBee.prototype = new Employee;
var amy = new WorkerBee;
Employee.prototype.name = "Unknown";

3. 判断实例的关系

JavaScript 的属性查找机制首先在自身对象的属性中查找,如果指定的属性名称没有找到,将在对象的特殊属性__proto__中查找。这个过程是递归的;被称为“在原型链中查找”。

特殊的__proto__属性是在构建对象时设置的;设置为构造器的prototype 属性的值。所有表达式 new Foo() 将创建一个对象,其 __proto__ == Foo.prototype。因而,修改Foo.prototype 的属性,将改变所有通过 new Foo() 创建的对象的属性的查找。

每个对象都有一个__proto__对象属性(除了Object);每个函数都有一个Prototype 对象属性。因此,通过“原型继承(prototype inheritance)”,对象与其他对象之间形成关系。通过比较对象的 __proto__ 属性和函数的prototype 属性可以检测对象的继承关系。JavaScript 提供了便捷方法:instanceof 操作符可以用来将一个对象和一个函数做检测,如果对象继承子函数的原型,则该操作符返回真。例如:

代码语言:javascript
复制
var f = new Foo();
var isTrue = (f instanceof Foo);

作为详细一点的例子,假定我们使用和在 继承属性 中相同的一组定义。创建 Engineer 对象如下:

代码语言:javascript
复制
var chris = new Engineer("Pigman, Chris", ["jsd"], "fiji");

对于该对象,以下所有语句均为真:

代码语言:javascript
复制
chris.__proto__ == Engineer.prototype;
chris.__proto__.__proto__ == WorkerBee.prototype;
chris.__proto__.__proto__.__proto__ == Employee.prototype;
chris.__proto__.__proto__.__proto__.__proto__ == Object.prototype;
chris.__proto__.__proto__.__proto__.__proto__.__proto__ == null;

基于此,可以写出一个如下所示的 instanceOf 函数:

代码语言:javascript
复制
function instanceOf(object, constructor) {
  while (object != null) {
     if (object == constructor.prototype)
        return true;
     if (typeof object == 'xml') {
       return constructor.prototype == XML.prototype;
     }
     object = object.__proto__;
  }
  return false;
}
代码语言:javascript
复制
instanceOf (chris, Engineer)
instanceOf (chris, WorkerBee)
instanceOf (chris, Employee)
instanceOf (chris, Object)

但如下表达式为假:

代码语言:javascript
复制
instanceOf (chris, SalesPerson)
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2017-07-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 jQuery每日经典 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档