你根本不懂Javascript(3):類和模組
本文最初釋出於http://szhshp.org/tech/2017/02/18/JavaSprite.html
轉載請註明
類和模組
工廠函式
function range(from, to) {
var r = inherit(range.methods); //繼承對應的方法
r.from = from;
r.to = to;
return r;
};
range.methods = {
includes: function (x) {
return this.from <= x && x <= this.to;
},
foreach: function (f) {
for (var x = Math.ceil(this.from); x <= this.to; x++) f(x);
},
toString: function () {
return "(" + this.from + "..." + this.to + ")";
}
}
// Here are example uses of a range object.
var r = range(1, 3); // Create a range object
r.includes(2); // => true: 2 在對應的範圍之內
r.foreach(console.log); // Prints 1 2 3
console.log(r); // Prints (1...3)
- 這裡給
range()
函式定義了一個屬性range.method
用於快捷地存放定義類的原型物件。 - 類裡面的屬性
from
和to
都是非共享屬性,是不可以繼承的
使用建構函式代替工廠函式
function Range(from, to) { //注意這兒函式名首字母變為大寫了
this.from = from;
this.to = to;
}
Range.prototype = { //通過prototype來給類定義方法
includes: function (x) {
return this.from <= x && x <= this.to;
},
foreach: function (f) {
for (var x = Math.ceil(this.from); x <= this.to; x++) f(x);
},
toString: function () {
return "(" + this.from + "..." + this.to + ")";
} };
var r = new Range(1, 3); // 注意新建obj的時候需要用到關鍵字new
r.includes(2); // => true: 2 is in the range
r.foreach(console.log); // Prints 1 2 3
console.log(r); // Prints (1...3)
- 普通函式方法一般都首字母小寫,但是構造方法需要首字母大寫
- 其次呼叫的時候需要新增關鍵字
new
, 同時這種情況下就不需要呼叫inherit()
方法了
Constructor屬性
var F = function() {}; // This is a function object.
var p = F.prototype; // This is the prototype object associated with it.
var c = p.constructor; // This is the function associated with the prototype.
c === F; // => true: F.prototype.constructor==F for any function
//對於任意函式,F.prototype.constructor == F
var o = new F(); // Create an object o of class F
o.constructor === F; // => true: the constructor property specifies the class
類的擴充
簡單易懂,如果原型中不存在對應方法就初始化對應方法
//給ES3中的函式類新增bind方法
if (!Function.prototype.bind) {
Function.prototype.bind = function (o /*, args */) {
// Code for the bind method goes here... };
}
}
簡單例子:
var n = 3;
n.times(function (n) {
console.log(n + " hello");
});
//下面分別給Number,string/function等新增方法或屬性
Number.prototype.times = function (f, context) {
var n = Number(this);
for (var i = 0; i < n; i++) f.call(context, i);
};
String.prototype.trim = String.prototype.trim || function () {
if (!this) return this; // Don't alter the empty string
return this.replace(/^\s+|\s+$/g, ""); // Regular expression magic
};
Function.prototype.getName = function () {
return this.name || this.toString().match(/function\s*([^(]*)\(/)[1];
};
給原型新增的方法可以使得所有的物件都可以呼叫這個方法
類和型別
有三種方法用於檢測物件類:
instanceof/isprototypeof
缺點:
- 無法通過物件獲得類名,只能檢測物件是否屬於特定類
- 多視窗和多框架的Web應用中相容存在問題
Constructor
function typeAndValue(x) {
if (x == null) return "";
switch (x.constructor) {
case Number:return "Number:" + x;
case String:return "String: '" + x + "'";
case Date:return "Date: " + x;
case RegExp:return "Regexp: " + x;
case Complex:return "Complex: " + x;
}
}
缺點:
- 多視窗和多框架的Web應用中相容存在問題
注意case
後面的表示式都是函式。如果使用typeof
的話獲取到的結果會是字串,例如下文
function type(o) {
var t, c, n; // type, class, name
// Special case for the null value:
if (o === null) return "null";
// Another special case: NaN is the only value not equal to itself:
if (o !== o) return "nan";
// Use typeof for any value other than "object".
// This identifies any primitive value and also functions.
if ((t = typeof o) !== "object") return t;
// Return the class of the object unless it is "Object".
// This will identify most native objects.
if ((c = classof(o)) !== "Object") return c;
// Return the object's constructor name, if it has one
if (o.constructor && typeof o.constructor === "function" && (n = o.constructor.getName())) return n;
// We can't determine a more specific type, so return "Object"
return "Object";
}
// Return the class of an object.
function classof(o) {
return Object.prototype.toString.call(o).slice(8, -1);
};
// Return the name of a function (may be "") or null for nonfunctions
Function.prototype.getName = function () {
if ("name" in this) return this.name;
return this.name = this.toString().match(/function\s*([^(]*)\(/)[1];
};
此外並非所有物件都有Constructor
屬性,匿名函式就是個典型:
// This constructor has no name
var Complex = function (x, y) {
this.r = x;
this.i = y;
}
// This constructor does have a name
var Range = function Range(f, t) {
this.from = f;
this.to = t;
}
JS的物件導向技術
一個全面並且典型的純OOP例子:
function Set() { // This is the constructor
this.values = {}; // The properties of this object hold the set
this.n = 0; // How many values are in the set
this.add.apply(this, arguments); // All arguments are values to add
}
// Add each of the arguments to the set.
Set.prototype.add = function () {
for (var i = 0; i < arguments.length; i++) { // For each argument
var val = arguments[i]; // The value to add to the set
var str = Set._v2s(val); // Transform it to a string
if (!this.values.hasOwnProperty(str)) { // If not already in the set
this.values[str] = val; // Map string to value
this.n++; // Increase set size
}
}
return this; // Support chained method calls
};
// Remove each of the arguments from the set.
Set.prototype.remove = function () {
for (var i = 0; i < arguments.length; i++) { // For each argument
var str = Set._v2s(arguments[i]); // Map to a string
if (this.values.hasOwnProperty(str)) { // If it is in the set
delete this.values[str]; // Delete it
this.n--; // Decrease set size
}
}
return this; // For method chaining
};
// Return true if the set contains value; false otherwise.
Set.prototype.contains = function (value) {
return this.values.hasOwnProperty(Set._v2s(value));
};
// Return the size of the set.
Set.prototype.size = function () {
return this.n;
};
// Call function f on the specified context for each element of the set.
Set.prototype.foreach = function (f, context) {
for (var s in this.values) // For each string in the set
if (this.values.hasOwnProperty(s)) // Ignore inherited properties
f.call(context, this.values[s]); // Call f on the value
};
Set._v2s = function (val) { //這是一個內部函式,當然例項物件無法呼叫這個方法
switch (val) {
case undefined:
return 'u'; // Special primitive
case null:
return 'n'; // values get single-letter
case true:
return 't'; // codes.
case false:
return 'f';
default:
switch (typeof val) {
case 'number':
return '#' + val; // Numbers get # prefix.
case 'string':
return '"' + val; // Strings get " prefix.
default:
return '@' + objectId(val); // Objs and funcs get @
}
}
// For any object, return a string. This function will return a different
// string for different objects, and will always return the same string
// if called multiple times for the same object. To do this it creates a
// property on o. In ES5 the property would be nonenumerable and read-only.
function objectId(o) {
var prop = "|**objectid**|"; // Private property name for storing ids
if (!o.hasOwnProperty(prop)) // If the object has no id
o[prop] = Set._v2s.next++; // Assign it the next available
return o[prop]; // Return the id
}
};
Set._v2s.next = 100; // Start assigning object ids at this value.
另一種通過返回值設定類的方法
function Test() {
var map = 1;
function a(){
map = 2;
}
function b(){
console.log(map);
}
return{
a:a,
b:b
}
}
var t = new Test()
對於後者:
- 注意如果最後的return裡面包含了map那麼無論如何執行b()這個map的值都不會變, 因為返回的是一個Obj是額外空間
- 當然這裡也可以不放返回值
- 返回值的方法是為了閉合部分介面
- 更大的區別是:很難重寫第二種模式裡面的方法
子類
原書中的子類內容比較累贅,可以歸納為以下幾步:
- 繼承prototype中定義的屬性和方法;
- 繼承建構函式中定義的屬性和方法;
- 修改子類的prototype物件的constructor指標
function Animal(name) {
this.name = name;
}
Animal.prototype.set = "female";
Animal.prototype.info = function () {
console.log("animal");
}
function People(name) {
this.name = name;
}
People.prototype = new Animal("animal"); // 繼承父類中定義的屬性和方法;
People.prototype.info = function() { //重寫父類中定義的屬性和方法;
console.log("peopel")
};
//Demo
var cat = new Animal('cat');
console.log(cat instanceof Animal); //t
console.log(cat instanceof Object); //t
console.log( typeof Animal.prototype); //object
console.log( typeof Animal.constructor); //function
console.log(Animal.prototype.constructor == Animal); //true
var mike = new People("mike");
console.log(mike.sex);//female
mike.info();//People
console.log(mike instanceof People); //t
console.log(mike instanceof Animal); //t
console.log(mike instanceof Object); //t
console.log( typeof Animal.prototype); //object
console.log( typeof Animal.constructor); //function
console.log(People.prototype.constructor == People); //true
類的封裝
簡單封裝方法:
使用
var
關鍵字設定私有屬性-
阻止類的擴充套件:
使用
Object.seal()
可以阻止給物件新增屬性並將已有的屬性設定為不可配置的,即不可刪除
但是這種情況下依然可以修改屬性
Object.seal(mike); mike.sex = 'male'; //仍然可以修改 delete mike.sex; //Cannot delete property 'sex'
-
阻止類的修改:
和
Object.seal()
類似不過Object.freeze
方法將例項方法設定為不可寫的這種情況下修改對應方法將變得無效
Object.seal(mike); mike.sex = 'male'; //不會報錯但是修改無效
模組化模式
首先我們來看看Module模式的基本特徵:
- 模組化,可重用
- 封裝了變數和function,和全域性的namaspace不接觸,鬆耦合
- 只暴露可用public的方法,其它私有方法全部隱藏
基本用法
var Calculator = function (eq) {
//這裡可以宣告私有成員
var eqCtl = document.getElementById(eq);
return {
// 暴露公開的成員
add: function (x, y) {
var val = x + y;
eqCtl.innerHTML = val;
}
};
};
var calculator = new Calculator('eq');
calculator.add(2, 2);
匿名閉包
(function () {
// ... 所有的變數和function都在這裡宣告,並且作用域也只能在這個匿名閉包裡
// ...但是這裡的程式碼依然可以訪問外部全域性的物件
}());
注意,匿名函式後面的括號,這是JavaScript語言所要求的,因為如果你不宣告的話,JavaScript直譯器預設是宣告一個function函式,有括號,就是建立一個函式表示式,也就是自執行,用的時候不用和上面那樣在new了,當然你也可以這樣來宣告:
(function () {/* 內部程式碼 */})();
引用全域性變數
獲取全域性變數到匿名函式域
(function ($, YAHOO) {
// 這兒$相當於全域性的jQuery
} (jQuery, YAHOO));//這兩個是全域性變數, 我們把它們放到這兒說明使用這兩個引數呼叫上面那個匿名函式
從匿名函式域設定全域性變數
var blogModule = (function () {
var my = [1,2,3]
return my;//其實只要把這些變數返回回去就行了, 之後blogModule就相當於my這個變數
} ());
當然return也可以返回一個object
var blogModule = (function () {
var my = [1,2,3]
return {
my: my,
you: null
}
} ());
高階用法
對變數自身進行擴充套件
var blogModule = (function (my) { // 2. 這裡接收到了傳進來的blogModule並把blogModule命名為my
var AddPhoto = function () { // 3. 這裡給my新增了個函式, 因此blogModule也多了個函式
console.log(123);
};
return {AddPhoto: AddPhoto};
} (blogModule)); //1. 這裡將blogModule傳了進去
blogModule.AddPhoto()// 4. 擴充套件完畢後就可以呼叫了
鬆耦合擴充套件
上面的擴充套件必須要先定義這個blogModule
, 能否在未定義的時候初始化而在已定義的時候直接擴充套件來達到鬆耦合的目的呢:
var blogModule = (function (my) {
// 新增一些功能
return my;
} (blogModule || {}));
這樣可以英一順序載入module模式
緊耦合擴充套件
雖然鬆耦合擴充套件很牛叉了,但是可能也會存在一些限制,比如你沒辦法重寫你的一些屬性或者函式,也不能在初始化的時候就是用Module的屬性。緊耦合擴充套件限制了載入順序,但是提供了我們過載的機會,看如下例子:
var blogModule = (function (my) {
var oldAddPhotoMethod = my.AddPhoto;
my.AddPhoto = function () {
// 過載方法,依然可通過oldAddPhotoMethod呼叫舊的方法
};
return my;
} (blogModule));
子模組
blogModule.CommentSubModule = (function () {
var my = {};
// ...
return my;
} ());
相關文章
- QQ空間資料揭秘:你根本不懂95後
- 你根本不懂rebase-使用rebase打造可讀的git graphGit
- JavaScript權威指南(9)——類和模組JavaScript
- 碼農被3年資深程式設計師狂噴:根本不懂程式碼!程式設計師
- linux系列之:告訴他,他根本不懂killLinux
- 如果你不懂HTML,你連笑話都看不懂HTML
- 運營商回應WiFi釣魚質疑:黑客根本不懂WiFi黑客
- Python 3 快速入門 3 —— 模組與類Python
- 你女友都懂前端路由和react-router實現原理了,你還不懂。前端路由React
- JavaScript 模組JavaScript
- 你不懂的JS學習筆記(作用域和閉包)JS筆記
- JavaScript 模組(2):模組打包JavaScript
- 使用Babel和ES7建立JavaScript模組BabelJavaScript
- JavaScript模組化JavaScript
- 其實你不懂程式設計師程式設計師
- [PY3]——heap模組 和 堆排序排序
- 3.java類和物件Java物件
- 實驗3 類和物件物件
- 成不了AI高手?因為你根本不懂資料!聽聽這位老教授多年心血練就的最實用統計學AI
- 你不懂技術,你想創業,你該這麼做創業
- 玩轉 JavaScript 之不得不懂的原型JavaScript原型
- 你還不懂 Tomcat 的優化嗎?Tomcat優化
- 這樣你都不懂Promise,算我輸!Promise
- 你不懂技術,如何領導我們
- SD中的VAE,你不能不懂
- JavaScript 模組封裝JavaScript封裝
- Javascript 模組化指北JavaScript
- Javascript模組全攬JavaScript
- JavaScript 模組化解析JavaScript
- Javascript 模組化理解JavaScript
- JavaScript 模組相關JavaScript
- 提升----你所不知道的JavaScript系列(3)JavaScript
- 06 ## 模組分類
- python的模組和類有什麼區別Python
- JavaScript:內建類和方法:字串 / 正則JavaScript字串
- VBA標準模組與類模組(轉)
- 抽象類和介面,你瞭解多少?抽象
- 深度理解SpringIOC,面試你根本不需要慌!Spring面試