是 Ray 不是 Array

Welcome.Web.World
歡迎來到網路世界

這章節主要講解 ES6(JavaScript EcmaScript 2015 或 EcmaScript 6) 的部分,而這章節將講解 ES6 另一種建立物件與設定原型的方法。 class 在其他程式語言是非常受歡迎的,可以用來定義物件的方法、屬性等該做什麼,但前面有講過 JavaScript 本身並沒有類別 (class)。

閱讀全文 »

前面我們已經瞭解使用函數建構子建立物件的方式,我們也看過函數建構子的出現原因是為了模仿其他不能實作原型繼承的程式語言,所以這邊會有點尷尬,在其他的程式語言是使用 `class` 來定義物件該做什麼,然後再用 `new` 來建立物件,而這也就是函數建構子想模仿的事情。

閱讀全文 »

今天面試的時候面試官講到一個東西,但發現自己並不是很熟悉,所以這邊查一下資料試著理解成自己的東西並做一下筆記,方便自己回顧。

閱讀全文 »

前言

我們對於函數建構子已經有一個基本瞭解了,所以這邊就可以來討論一下內建的函數建構子。

內建的函數建構子

這邊將會開始講一下已經存在於 JavaScript 的內建函數建構子,所以我們可以看看內建的函數建構子有什麼,或許我們已經用了許多次,但不一定瞭解它。

來試著撰寫程式碼瞭解吧。

1
var a = new Number(3);

首先這是一個函數,一個函數建構子,用了首字母大寫的傳統。

圖片

我們可以看到輸出的是一個物件,為什麼?函數建構子會建立什麼?物件。

所以我們可以試試看我們所瞭解的內建建構子。

1
2
3
var a = new Number(3);
var b = new String('Hellow');
var c = new Object({firstname: 'jack'});

圖片

那函數建構子本身有原型,所以我們可以查看原型,而它內建一些方法。

1
2
var a = new Number(3);
Number.prototype

圖片

所以我們可以這樣使用

1
2
var a = new Number(3);
a.toFixed(2)

圖片

另外小提一下 b,並不是字串,也不是純值。

1
var b = new String('Hellow');

圖片

它是一個物件,所以我們才能夠這樣子使用 indexOf()

1
2
var b = new String('Hellow');
b.indexOf('e');

圖片

所以這些內建的函數建構子看起來很像是幫我們建議字串,但其實是物件,且包含了字串。

那其實下面這個做法

1
"Hello".length;

等於這個樣子

1
new String("Hello");

所以說下次若我們看到 new,例如 new Date

1
var a = new Date('2019/05/23');

圖片

我們其實是取得了一個物件,然後使用了 JavaScript 內建提供的方法。

圖片

而這些方法實際是存在於 Date

圖片

那如果今天我們要自己加入另一個原型呢?

1
2
3
4
5
String.prototype.isLengthGreaterThan = function (limit) {
return this.length > limit;
}

console.log('jack'.isLengthGreaterThan(3));

圖片

那這時候所有字串就都可以使用 isLengthGreaterThan 這個方法,而這就是原型繼承的強大,許多框架都會這樣做,但是撰寫時要注意不要複寫到原本提供的建構子。

字串可以這樣做,那數字呢?

1
2
3
4
5
6
7
8
9
10
String.prototype.isLengthGreaterThan = function (limit) {
return this.length > limit;
}

console.log('jack'.isLengthGreaterThan(3));

Number.prototype.isPositive = function () {
return this.length > 0;
}
console.log(10.isPositive());

圖片

得到了一個錯誤,為什麼?

雖然 JavaScript 會將字串轉換為物件,但數字並不會。

但是可以這樣寫

1
2
3
4
5
Number.prototype.isPositive = function () {
return this.length > 0;
}
var a = new Number(3);
console.log(a.isPositive());

圖片

所以我們可以替這些原型增加功能,進而方便我們。

圖源

JavaScript 全攻略:克服 JS 奇怪的部分

前言

這章節是關於「new」與函數的危險小叮嚀,在前面我們有大致上講過,當加上 new 時,會建立空的物件,然後 this 會指向空的物件,如果你不回傳任何東西的話,它會回傳新物件,但這邊就是函數危險的地方。

「new」與函數

如果今天我們忘記加上 new 會發生什麼事情呢?

1
2
3
4
5
6
7
8
9
function Person(first, last) {
console.log(this);
this.firstname = first;
this.lastname = last;
}

var john = Person('John', 'Doe');

console.log(john);

圖片

第一個我們可以發現 this 指向了 window,然後 JavaScript 並不知道我們要對這個函數執行,對他來講只是一個函數,也因為這樣子所以他只會回傳 undefined。

那這樣會有甚麼問題?當我們取用 proto 就會出現錯誤。

1
2
3
4
5
6
7
8
9
10
11
12
function Person(first, last) {
console.log(this);
this.firstname = first;
this.lastname = last;
}
Person.prototype.getFullName = function() {
return this.firstname + ' ' + this.lastname;
}

var john = Person('John', 'Doe');

console.log(john.getFullName());

圖片

這個錯誤是因為 john 是一個 undefined,所以無法取得方法。

通常這都是大家認為的函數建構子缺點,但如果我們遵照一些規範來設計的話,就可以減少一些問題,例如任何我們要作為函數建構子的函數首字
母寫成大寫,第一個字母大寫,這樣子會比較方便於 debug,而這是一個傳統方式。

任何我們要作為函數建構子的函數,永遠都要第一個字母大寫作為建構子名稱。

圖源

JavaScript 全攻略:克服 JS 奇怪的部分

前言

我們已經知道函數建構子可以幫助新物件設定屬性及方法,但原型呢?所以接下來就要來做講檢該如何設定原型。

prototype

這是上一章節的範例

1
2
3
4
5
6
7
8
function Person(first, last) {
this.firstname = first;
this.lastname = last;
}

var john = new Person('John', 'Doe');

console.log(john);

當我們使用函數建構子的時候,就已經被 JavaScript 給準備好了,所以我們可以試著輸入 john.__proto__

圖片

我們會得到一個物件,不信?我收合給你看。

圖片

那這個物件是什麼?我們先前有講過函數就是物件,在 JavaScript 函數就是物件,然後所有物件都有原型屬性 (prototype property),但是一般來講我們並不會用到,他只是掛在那裏而已。

圖片

除非我們將函數作為函數建構子來使用。

圖片

讓我們來試試看。

1
2
3
4
5
6
7
8
9
10
11
12
function Person(first, last) {
this.firstname = first;
this.lastname = last;
}

Person.prototype.getFullName = function() {
return this.firstname + ' ' + this.lastname;
}

var john = new Person('John', 'Doe');

console.log(john);

圖片

我們可以看到我們可以原型下有了 getFullName(),換一個角度來看。

圖片

所以我們可以知道,當然函數被當作函數建構子使用時,就會有原型,那為什麼我們要將 getFullName 放進原型內?我們也可以直接放在函數建構子內?

1
2
3
4
5
6
7
8
9
10
11
function Person(first, last) {
this.firstname = first;
this.lastname = last;
this.getFullName = function() {
return this.firstname + ' ' + this.lastname;
}
}

var john = new Person('John', 'Doe');

console.log(john);

圖片

假設今天有一千種物件都放在 getFullName 內,就會佔據比較多的記憶體,但若放在原型上就會比較好。

以效能來講將重複的屬性與方法放在原型上會比較好,因為這樣做我們就不用每一次都在 Person 裡面加入物件,只需要將重複需要使用的放在原型,然後透過原型去取得使用就好了。

所以就可以這樣寫

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function Person(first, last) {
this.firstname = first;
this.lastname = last;
}

Person.prototype.getFullName = function() {
return this.firstname + ' ' + this.lastname;
}

Person.prototype.getFormaFullName = function() {
return this.firstname + ', ' + this.lastname;
}

var john = new Person('John', 'Doe');
console.log(john);

var jack = new Person('John', 'Doe');
console.log(jack);

圖片

這樣兩者皆可以使用。

圖源

JavaScript 全攻略:克服 JS 奇怪的部分

前言

前面我們已經瞭解許多物件、原型繼承、原型鏈和物件屬性及方法,接下將更深入討論建立物件。

「new」

前面我們已經有講過幾種建立物件的方式,其中一個是物件實體語法。

1
2
3
4
var jack = {
firstname: 'jack',
lastname: 'Dec',
}

而其實還有別的物件建立方式。

1
var jack = new Person();

圖片

那為什麼會有 new?,這主要與 JavaScript 的誕生有關係,一個語言若都沒有人使用就代表著死亡,所以當初 JavaScript 會叫 JavaScript 就是希望可以吸引 Java 開發者,而 Java 開發者習慣使用 new 的關鍵字來定義物件等等,所以當 Java 開發者看到這樣寫會感覺比較親切,但是使用這種方式來建立物件有些許問題,但是我們還是需要瞭解,因為許多專案及原始碼都會有它的存在。

下面將用課堂的範例來做一點好玩的事情。

1
2
3
4
5
6
7
8
function Person() {
this.fistname = 'John';
this.lastname = 'Doe';
}

var john = new Person();

console.log(john);

圖片

我們發現 john 變成一個物件了,但是這邊我們要記得一件事情,我們只是在利用一些方法在 JavaScript 中建立物件,那為了建立物件我們必須給它屬性和方法,然後建立原型,而這是為了讓我們看更清楚而做的,所以接下來將使用比較正確的方式來增加屬性和方法、設定原型。

new 其實是一個運算子,為什麼這樣講呢?讓我們翻一下先前的文件>MDN 的文件 (在下方一點地方)

圖片

上面的例子中 new 會改變 this 指向的東西,this 指向到一個空的物件,而 new 建立了一個新物件,然後呼叫了 Person(),所以現在 this 變數指向 new 創造的新的空物件的記憶體位置。

所以當我們打 .firstname.lastname 就會增加到空物件上,就像這樣。

1
2
3
4
5
6
var john = {};

john.fistname = 'John';
john.lastname = 'Doe';

console.log(john);

而在 JavaScript 中會回傳 new 運算子建立的物件,這會建立一個新物件,而成為物件的一部分也會回傳東西,所以我們試著這樣嘗試看看。

1
2
3
4
5
6
7
8
9
function Person() {
this.fistname = 'John';
this.lastname = 'Doe';
console.log('我回傳囉');
}

var john = new Person();

console.log(john);

圖片

由此可知,new 運算子會對這個函數呼叫,然後在回傳裡面的東西變成物件。

那如果 this 我們什麼都不對他做的話,他將會回傳本身。

1
2
3
4
5
6
7
8
9
10
function Person() {
console.log(this);
this.fistname = 'John';
this.lastname = 'Doe';
console.log('我回傳囉');
}

var john = new Person();

console.log(john);

圖片

但是如果我們本身若對函數回傳的話,就會變成只回傳那個資料。

1
2
3
4
5
6
7
8
9
10
11
function Person() {
console.log(this);
this.fistname = 'John';
this.lastname = 'Doe';
console.log('我回傳囉');
return { greet: '我是回傳的物件' };
}

var john = new Person();

console.log(john);

圖片

如果沒有回傳時,JavaScript 就會改找我們設定 this 的變數。

所以我們可以從這邊知道,我們可以透過函數來建構物件,而這叫做函數建構子。

那我們可以依照 函數建構子 來做一點進階的作法,假設今天 firstnamelastname 是改由我們自己傳入呢?

1
2
3
4
5
6
7
8
function Person(first, last) {
this.firstname = first;
this.lastname = last;
}

var john = new Person('John', 'Doe');

console.log(john);

圖片

所以我們可以利用函數參數的特性來傳入資料進而改變 firstnamelastname

圖片

簡單來講 new 運算子可以做出新物件,函數建構子是被用來增加新物件的屬性和方法

圖源

JavaScript 全攻略:克服 JS 奇怪的部分

前言

接下來將要講解另一個建立物件的函數很有趣很有效的東西,許多資料庫都會使用到,而這稱為 extend。

而通常我們都是用一個叫 Reflection 的東西來做到 extend。

Reflection

圖片

Reflection: 一個物件可以看到自己的東西,然後改變自己的屬性和方法。

讓我們試著從範例來學習。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var person = {
firstname: 'Default',
lastname: 'Default',
getFullName: function() {
return this.firstname + ' ' + this.lastname;
}
}

var john = {
firstname: 'John',
lastname: 'Doe',
}
// 千萬不要使用這種方式在真正的專案開發上,這只是為了理解原型而已。
john.__proto__ = person;

for(var prop in john) {
console.log(prop + ':' + john[prop]);
}

圖片

這時候會看到一個很神奇的東西,getFullName(),這是因為 for in 會到外面取用屬性和方法的 不只在物件本身,還會到原型上找

那如果今天我們要

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var person = {
firstname: 'Default',
lastname: 'Default',
getFullName: function() {
return this.firstname + ' ' + this.lastname;
}
}

var john = {
firstname: 'John',
lastname: 'Doe',
}
// 千萬不要使用這種方式在真正的專案開發上,這只是為了理解原型而已。
john.__proto__ = person;

for(var prop in john) {
if(john.hasOwnProperty(prop)) { // hasOwnProperty 是原型物件的東西,我們可以透過這種方式來確定是否物件有這個屬性。
console.log(prop + ':' + john[prop]);
}
}

圖片

這樣 getFullName() 就沒有出現了,因為 john 的物件屬性並沒有 getFullName()

而這個概念可以用來幫助我們做一些事情【補足原型繼承】,所以這邊試試看引入 underscore.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var newscript = document.createElement('script');
newscript.setAttribute('type','text/javascript');
newscript.setAttribute('src','https://underscorejs.org/underscore-min.js');
var john = {
firstname: 'John',
lastname: 'Doe',
}

var jane = {
address: '111 Main St.',
getFormFullName: function() {
return this.lastname + ', ' + this.firstname;
}
}

var jim = {
getFirstName: function() {
return firstname;
}
}

_.extend(john, jane, jim); // 這樣做將會把物件結合起來
console.log(john);

圖片

這時候我們可以看到 johnjane 的物件地址、jim 物件的 getFirstName 函數,還有 jane 物件的 getFormalFullName()

那這是怎麼做到的?首先先開啟 underscore.js,開啟之前記得千萬不要害怕開源的專案,這是一個非常好學習教材,我們試著找 extend 看看。

圖片

首先我們可以看到他建立了屬性或方法,而這個叫 createAssigner 的函數我們試著找看看。

圖片

我們可以發現 createAssigner 是一個函數,其中他接受了 keys 還有 defaults,然後回傳函數本身,我們可以看到這是一個閉包。

首先它會先取得傳入參數的長度

圖片

再來是包裝物件,他將物件包裝起來(避免參考問題) (((我猜)))

圖片

接下來就是當物件長度若小於兩筆,或是物件為空,就返回物件

圖片

接下來他會用 for 將所有物件跑一遍,因為陣列第一筆是 john ,所以起始會直接跳過 01 開始

圖片

所以因為經過 underscore.js 的處理, jim 就可以使用 getFormFullName()了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var newscript = document.createElement('script');
newscript.setAttribute('type','text/javascript');
newscript.setAttribute('src','https://underscorejs.org/underscore-min.js');
var john = {
firstname: 'John',
lastname: 'Doe',
}

var jane = {
address: '111 Main St.',
getFormFullName: function() {
return this.lastname + ', ' + this.firstname;
}
}

var jim = {
getFirstName: function() {
return firstname;
}
}

_.extend(john, jane, jim); // 這樣做將會把物件結合起來
console.log(john);

john.getFormFullName();

圖片

圖源

JavaScript 全攻略:克服 JS 奇怪的部分

0%