目錄
this
prototype
constructor
首頁 web前端 js教程 JavaScript 继承详解(二)_js面向对象

JavaScript 继承详解(二)_js面向对象

May 16, 2016 pm 06:50 PM
javascript 繼承

this

this表示当前对象,如果在全局作用范围内使用this,则指代当前页面对象window; 如果在函数中使用this,则this指代什么是根据运行时此函数在什么对象上被调用。 我们还可以使用apply和call两个全局方法来改变函数中this的具体指向。

先看一个在全局作用范围内使用this的例子:

  <script type="text/javascript">
   console.log(this === window); // true
   console.log(window.alert === this.alert); // true
   console.log(this.parseInt("021", 10)); // 10
  </script>
  
登入後複製

函数中的this是在运行时决定的,而不是函数定义时,如下:

  // 定义一个全局函数
  function foo() {
   console.log(this.fruit);
  }
  // 定义一个全局变量,等价于window.fruit = "apple";
  var fruit = "apple";
  // 此时函数foo中this指向window对象
  // 这种调用方式和window.foo();是完全等价的
  foo(); // "apple"

  // 自定义一个对象,并将此对象的属性foo指向全局函数foo
  var pack = {
   fruit: "orange",
   foo: foo
  };
  // 此时函数foo中this指向window.pack对象
  pack.foo(); // "orange"
  
登入後複製

全局函数apply和call可以用来改变函数中this的指向,如下:

  // 定义一个全局函数
  function foo() {
   console.log(this.fruit);
  }
  
  // 定义一个全局变量
  var fruit = "apple";
  // 自定义一个对象
  var pack = {
   fruit: "orange"
  };
  
  // 等价于window.foo();
  foo.apply(window); // "apple"
  // 此时foo中的this === pack
  foo.apply(pack); // "orange"
  
登入後複製
注:apply和call两个函数的作用相同,唯一的区别是两个函数的参数定义不同。

因为在JavaScript中函数也是对象,所以我们可以看到如下有趣的例子:

  // 定义一个全局函数
  function foo() {
   if (this === window) {
    console.log("this is window.");
   }
  }
  
  // 函数foo也是对象,所以可以定义foo的属性boo为一个函数
  foo.boo = function() {
   if (this === foo) {
    console.log("this is foo.");
   } else if (this === window) {
    console.log("this is window.");
   }
  };
  // 等价于window.foo();
  foo(); // this is window.
  
  // 可以看到函数中this的指向调用函数的对象
  foo.boo(); // this is foo.
  
  // 使用apply改变函数中this的指向
  foo.boo.apply(window); // this is window.
  
登入後複製

prototype

我们已经在第一章中使用prototype模拟类和继承的实现。 prototype本质上还是一个JavaScript对象。 并且每个函数都有一个默认的prototype属性。
如果这个函数被用在创建自定义对象的场景中,我们称这个函数为构造函数。 比如下面一个简单的场景:

  // 构造函数
  function Person(name) {
   this.name = name;
  }
  // 定义Person的原型,原型中的属性可以被自定义对象引用
  Person.prototype = {
   getName: function() {
    return this.name;
   }
  }
  var zhang = new Person("ZhangSan");
  console.log(zhang.getName()); // "ZhangSan"
  
登入後複製
作为类比,我们考虑下JavaScript中的数据类型 - 字符串(String)、数字(Number)、数组(Array)、对象(Object)、日期(Date)等。 我们有理由相信,在JavaScript内部这些类型都是作为构造函数来实现的,比如:
  // 定义数组的构造函数,作为JavaScript的一种预定义类型
  function Array() {
   // ...
  }
  
  // 初始化数组的实例
  var arr1 = new Array(1, 56, 34, 12);
  // 但是,我们更倾向于如下的语法定义:
  var arr2 = [1, 56, 34, 12];
  
登入後複製
同时对数组操作的很多方法(比如concat、join、push)应该也是在prototype属性中定义的。
实际上,JavaScript所有的固有数据类型都具有只读的prototype属性(这是可以理解的:因为如果修改了这些类型的prototype属性,则哪些预定义的方法就消失了), 但是我们可以向其中添加自己的扩展方法。
  // 向JavaScript固有类型Array扩展一个获取最小值的方法
  Array.prototype.min = function() {
   var min = this[0];
   for (var i = 1; i < this.length; i++) {
    if (this[i] < min) {
     min = this[i];
    }
   }
   return min;
  };
  
  // 在任意Array的实例上调用min方法
  console.log([1, 56, 34, 12].min()); // 1
  
登入後複製

注意:这里有一个陷阱,向Array的原型中添加扩展方法后,当使用for-in循环数组时,这个扩展方法也会被循环出来。
下面的代码说明这一点(假设已经向Array的原型中扩展了min方法):
  var arr = [1, 56, 34, 12];
  var total = 0;
  for (var i in arr) {
   total += parseInt(arr[i], 10);
  }
  console.log(total); // NaN
  
  
登入後複製
解决方法也很简单:
  var arr = [1, 56, 34, 12];
  var total = 0;
  for (var i in arr) {
   if (arr.hasOwnProperty(i)) {
    total += parseInt(arr[i], 10);
   }
  }
  console.log(total); // 103
  
登入後複製

constructor

constructor始终指向创建当前对象的构造函数。比如下面例子:

  // 等价于 var foo = new Array(1, 56, 34, 12);
  var arr = [1, 56, 34, 12];
  console.log(arr.constructor === Array); // true
  // 等价于 var foo = new Function();
  var Foo = function() { };
  console.log(Foo.constructor === Function); // true
  // 由构造函数实例化一个obj对象
  var obj = new Foo();
  console.log(obj.constructor === Foo); // true
  
  // 将上面两段代码合起来,就得到下面的结论
  console.log(obj.constructor.constructor === Function); // true
  
登入後複製

但是当constructor遇到prototype时,有趣的事情就发生了。
我们知道每个函数都有一个默认的属性prototype,而这个prototype的constructor默认指向这个函数。如下例所示:

  function Person(name) {
   this.name = name;
  };
  Person.prototype.getName = function() {
   return this.name;
  };
  var p = new Person("ZhangSan");
  
  console.log(p.constructor === Person); // true
  console.log(Person.prototype.constructor === Person); // true
  // 将上两行代码合并就得到如下结果
  console.log(p.constructor.prototype.constructor === Person); // true
  
登入後複製
当时当我们重新定义函数的prototype时(注意:和上例的区别,这里不是修改而是覆盖), constructor的行为就有点奇怪了,如下示例:
  function Person(name) {
   this.name = name;
  };
  Person.prototype = {
   getName: function() {
    return this.name;
   }
  };
  var p = new Person("ZhangSan");
  console.log(p.constructor === Person); // false
  console.log(Person.prototype.constructor === Person); // false
  console.log(p.constructor.prototype.constructor === Person); // false
  
登入後複製
为什么呢?
原来是因为覆盖Person.prototype时,等价于进行如下代码操作:
  Person.prototype = new Object({
   getName: function() {
    return this.name;
   }
  });
  
登入後複製
而constructor始终指向创建自身的构造函数,所以此时Person.prototype.constructor === Object,即是:
  function Person(name) {
   this.name = name;
  };
  Person.prototype = {
   getName: function() {
    return this.name;
   }
  };
  var p = new Person("ZhangSan");
  console.log(p.constructor === Object); // true
  console.log(Person.prototype.constructor === Object); // true
  console.log(p.constructor.prototype.constructor === Object); // true
  
登入後複製
怎么修正这种问题呢?方法也很简单,重新覆盖Person.prototype.constructor即可:
  function Person(name) {
   this.name = name;
  };
  Person.prototype = new Object({
   getName: function() {
    return this.name;
   }
  });
  Person.prototype.constructor = Person;
  var p = new Person("ZhangSan");
  console.log(p.constructor === Person); // true
  console.log(Person.prototype.constructor === Person); // true
  console.log(p.constructor.prototype.constructor === Person); // true
  
登入後複製

 


下一章我们将会对第一章提到的Person-Employee类和继承的实现进行完善。

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

C++ 函式繼承詳解:如何在繼承中使用「基底類別指標」和「衍生類別指標」? C++ 函式繼承詳解:如何在繼承中使用「基底類別指標」和「衍生類別指標」? May 01, 2024 pm 10:27 PM

在函數繼承中,使用「基底類別指標」和「衍生類別指標」來理解繼承機制:基底類別指標指向派生類別物件時,執行向上轉型,只存取基底類別成員。派生類別指標指向基底類別物件時,執行向下轉型(不安全),必須謹慎使用。

簡易JavaScript教學:取得HTTP狀態碼的方法 簡易JavaScript教學:取得HTTP狀態碼的方法 Jan 05, 2024 pm 06:08 PM

JavaScript教學:如何取得HTTP狀態碼,需要具體程式碼範例前言:在Web開發中,經常會涉及到與伺服器進行資料互動的場景。在與伺服器進行通訊時,我們經常需要取得傳回的HTTP狀態碼來判斷操作是否成功,並根據不同的狀態碼來進行對應的處理。本篇文章將教你如何使用JavaScript來取得HTTP狀態碼,並提供一些實用的程式碼範例。使用XMLHttpRequest

C++ 函式繼承詳解:如何偵錯繼承中出現的錯誤? C++ 函式繼承詳解:如何偵錯繼承中出現的錯誤? May 02, 2024 am 09:54 AM

繼承錯誤調試技巧:確保正確的繼承關係。使用偵錯器逐步執行程式碼,檢查變數值。確保正確使用virtual修飾符。檢查隱藏的繼承帶來的菱形繼承問題。檢查抽象類別中未實現的純虛函數。

C++ 函式繼承詳解:如何理解繼承中的「is-a」與「has-a」關係? C++ 函式繼承詳解:如何理解繼承中的「is-a」與「has-a」關係? May 02, 2024 am 08:18 AM

C++函式繼承詳解:掌握「is-a」和「has-a」關係什麼是函式繼承?函數繼承是C++中一種將衍生類別中定義的方法與基底類別中定義的方法關聯起來的技術。它允許衍生類別存取和重寫基底類別的方法,從而擴展了基底類別的功能。 「is-a」和「has-a」關係在函數繼承中,「is-a」關係指派生類別是基底類別的子類型,也就是說,衍生類別「繼承」了基底類別的特性和行為。 「has-a」關係指派生類別包含對基底類別物件的參考或指針,也就是說,衍生類別「擁有」了基底類別物件。語法以下是如何實作函數繼承的語法:classDerivedClass:pu

C++ 中繼承和多態性如何影響類別的耦合度? C++ 中繼承和多態性如何影響類別的耦合度? Jun 05, 2024 pm 02:33 PM

繼承和多態性會影響類別的耦合度:繼承會增加耦合度,因為衍生類別依賴基底類別。多態性可以降低耦合度,因為物件可以透過虛擬函數和基底類別指標以一致的方式回應訊息。最佳實踐包括謹慎使用繼承、定義公共介面、避免在基底類別中新增資料成員,以及透過依賴注入解耦類別。實戰案例顯示如何使用多態性和依賴注入來降低銀行帳戶應用程式中的耦合度。

如何在JavaScript中取得HTTP狀態碼的簡單方法 如何在JavaScript中取得HTTP狀態碼的簡單方法 Jan 05, 2024 pm 01:37 PM

JavaScript中的HTTP狀態碼取得方法簡介:在進行前端開發中,我們常常需要處理與後端介面的交互,而HTTP狀態碼就是其中非常重要的一部分。了解並取得HTTP狀態碼有助於我們更好地處理介面傳回的資料。本文將介紹使用JavaScript取得HTTP狀態碼的方法,並提供具體程式碼範例。一、什麼是HTTP狀態碼HTTP狀態碼是指當瀏覽器向伺服器發起請求時,服務

'PHP物件導向程式設計入門:從概念到實踐” 'PHP物件導向程式設計入門:從概念到實踐” Feb 25, 2024 pm 09:04 PM

什麼是物件導向程式設計?物件導向程式設計(OOP)是一種程式設計範式,它將現實世界中的實體抽象化為類,並使用物件來表示這些實體。類別定義了物件的屬性和行為,而物件則實例化了類別。 OOP的主要優點在於它可以使程式碼更易於理解、維護和重複使用。 OOP的基本概念OOP的主要概念包括類別、物件、屬性和方法。類別是物件的藍圖,它定義了物件的屬性和行為。物件是類別的實例,它具有類別的所有屬性和行為。屬性是物件的特徵,它可以儲存資料。方法是物件的函數,它可以對物件的資料進行操作。 OOP的優點OOP的主要優點包括:可重複使用性:OOP可以讓程式碼更

Java 介面與抽象類別:通往程式設計天堂之路 Java 介面與抽象類別:通往程式設計天堂之路 Mar 04, 2024 am 09:13 AM

介面:無實作的契約介面在Java中定義了一組方法簽名,但不提供任何具體實作。它充當一種契約,強制實作該介面的類別實現其指定的方法。介面中的方法是抽象方法,沒有方法體。程式碼範例:publicinterfaceAnimal{voideat();voidsleep();}抽象類別:部分實作的藍圖抽象類別是一種父類,它提供了一個部分實現,可以被它的子類別繼承。與介面不同,抽象類別可以包含具體的實作和抽象方法。抽象方法是用abstract關鍵字聲明的,並且必須被子類別覆蓋。程式碼範例:publicabstractcla

See all articles