算法基礎(chǔ)
計算機算法是產(chǎn)生特定結(jié)果的一系列步驟。要寫一個算法,你必須先理解一個特定的問題,然后編寫代碼去解決它。
function convertToF(celsius) {
let fahrenheit = celsius * 9 / 5 + 32;
return fahrenheit;
} // 將攝氏溫度轉(zhuǎn)換成華氏溫度:攝氏度 × 9/5 + 32
convertToF(30);
翻轉(zhuǎn)字符串
在反轉(zhuǎn)字符串前可能需要將其切分成字符的數(shù)組。
function reverseString(str) { return str.split('').reverse().join(''); }
數(shù)字的階乘
若 n 是一個整數(shù),n 的階乘就是所有小于等于 n 的正整數(shù)的乘積。
function factorialize(num) {
if (num === 0) { return 1; }
return num * factorialize(num-1);
}
查找字符串中最長的單詞
返回給出的句子中最長的單詞的長度。
function findLongestWordLength(s) {
return s.split(' ')
.reduce(function(x, y) {
return Math.max(x, y.length)
}, 0);
}
返回數(shù)組中最大的數(shù)字
function largestOfFour(arr) {
return arr.map(function(group){
return group.reduce(function(prev, current) {
return (current > prev) ? current : prev;
});
});
}
// 等價于
function largestOfFour(arr) {
return arr.map(Function.apply.bind(Math.max, null));
}
檢查字符串的結(jié)尾
檢查一個字符串(第一個參數(shù), str )是否以給定的字符串(第二個參數(shù) target )結(jié)束。function confirmEnding(str, target) { return str.slice(str.length - target.length) === target; },還可以直接使用方法.endsWith()。
重復(fù)字符串
將一個給定的字符串(第一個參數(shù), str )重復(fù) num (第二個參數(shù))次。如果 num 不是一個正數(shù),返回一個空字符串。
function repeatStringNumTimes(str, num) {
var accumulatedStr = '';
while (num > 0) { accumulatedStr += str; num--; }
return accumulatedStr;
}
function repeatStringNumTimes(str, num) {
if(num < 0) return "";
if(num === 1) return str;
else return str + repeatStringNumTimes(str, num - 1);
}
function repeatStringNumTimes(str, num) {
return num > 0 ? str.repeat(num) : '';
}
截斷字符串
如果一個字符串(第一個參數(shù))的長度大于給出的值(第二個參數(shù)),則截斷它并在其后加上 ... 。返回被截斷的字符串。
function truncateString(str, num) {
if (str.length > num && num > 3) {
return str.slice(0, (num - 3)) + '...';
} else if (str.length > num && num <= 3) {
return str.slice(0, num) + '...';
} else {
return str;
}
} // truncateString("A-tisket a-tasket A green and yellow basket", 8); A-tis...
function truncateString(str, num) {
if (str.length <= num) {
return str;
} else {
return str.slice(0, num > 3 ? num - 3 : num) + '...';
}
}
發(fā)現(xiàn)者與看護者
檢查一個數(shù)組(第一個參數(shù))中的元素,并返回數(shù)組中第一個通過校驗測試(第二個參數(shù),一個接受一個參數(shù)并返回一個布爾值的函數(shù))的元素。如果沒有元素通過測試,則返回 undefined。
function findElement(arr, func) {
let num = 0;
for(var i = 0; i < arr.length; i++) {
num = arr[i];
if (func(num)) { return num; }
}
return undefined;
}
真假測試
檢查一個值是否是原始的布爾值(boolean)類型。返回 true 或者 false。
function booWho(bool) { return typeof bool == 'boolean';}
booWho(null);
單詞的首字母大寫
將給出的字符串中所有單詞的第一個字母變成大寫,并返回得到的字符串。確保其余的字母是小寫的。
function titleCase(str) {
var newTitle = str.split(' ');
var updatedTitle = [];
for (var st in newTitle) {
updatedTitle[st] = newTitle[st].toLowerCase().replaceAt(0, newTitle[st].charAt(0).toUpperCase());
}
return updatedTitle.join(' ');
}
function titleCase(str) {
var convertToArray = str.toLowerCase().split(" ");
var result = convertToArray.map(function(val){
return val.replace(val.charAt(0), val.charAt(0).toUpperCase());
});
return result.join(" ");
}
function titleCase(str) {
return str.toLowerCase().replace(/(^|\s)\S/g, (L) => L.toUpperCase());
}
slice 和 splice
將第一個數(shù)組中的所有元素依次復(fù)制到第二個數(shù)組中。
function frankenSplice(arr1, arr2, n) {
let localArray = arr2.slice();
for (let i = 0; i < arr1.length; i++) { localArray.splice(n, 0, arr1[i]); n++; }
return localArray;
}
去除數(shù)組中的假值
JavaScript 中的假值有 false、null、0、""、undefined 和 NaN。
function bouncer(arr) {
// Don't show a false ID to this bouncer.
let newArr = [];
for(let i = 0; i < arr.length; i++){
if(arr[i]){ newArr.push(arr[i]); }
}
return newArr;
}
function bouncer(arr) {
return arr.filter(Boolean);
}
我身在何處
返回數(shù)組(第一個參數(shù))被排序后,將一個值(第二個參數(shù))插入到該數(shù)組中而使數(shù)組保持有序的最小的索引。返回的值應(yīng)該是一個數(shù)字。
function getIndexToIns(arr, num) {
arr.sort(function(a, b) { return a - b; });
for (var a = 0; a < arr.length; a++) {
if (arr[a] >= num) return a;
}
return arr.length;
}
function getIndexToIns(arr, num) {
var times = arr.length;
var count = 0;
for (var i=0;i<times;i++){ if(num>arr[i]){count++; } }
return count;
}
function getIndexToIns(arr, num) {
arr.push(num);
arr.sort(function(a, b){return a-b});
return arr.indexOf(num);
}
function getIndexToIns(arr, num) {
var index = arr.sort((curr, next) => curr > next)
.findIndex((currNum)=> num <= currNum);
return index === -1 ? arr.length : index;
}
function getIndexToIns(arr, num) {
return arr.concat(num).sort((a,b) => a-b).indexOf(num);
}
集合之間的關(guān)系
輸入?yún)?shù)是一個有兩個字符串元素的數(shù)組。如果第一個字符串中包含了第二個字符串中的所有字母,則返回 true。
function mutation(arr) {
var test = arr[1].toLowerCase();
var target = arr[0].toLowerCase();
for (var i=0;i<test.length;i++) {
if (target.indexOf(test[i]) < 0) return false;
}
return true;
}
function mutation(arr) {
return arr[1].toLowerCase()
.split('')
.every(function(letter) {
return arr[0].toLowerCase()
.indexOf(letter) != -1;
});
}
猴子吃香蕉
將一個數(shù)組(第一個參數(shù))分割成一組長度為 size(第二個參數(shù))的數(shù)組,然后在一個二維數(shù)組中返回這些結(jié)果。
function chunkArrayInGroups(arr, size) {
var temp = [];
var result = [];
for (var a = 0; a < arr.length; a++) {
if (a % size !== size - 1) temp.push(arr[a]);
else { temp.push(arr[a]); result.push(temp); temp = []; }
}
if (temp.length !== 0) result.push(temp);
return result;
}
面向?qū)ο缶幊?/h2>
面向?qū)ο缶幊虒⒋a組織成對象定義。這些有時被稱為類,它們將數(shù)據(jù)和相關(guān)行為組合在一起。數(shù)據(jù)是對象的屬性,行為(或函數(shù))是方法。
對象結(jié)構(gòu)能夠在程序中靈活使用,比如對象可以通過調(diào)用數(shù)據(jù)并將數(shù)據(jù)傳遞給另一個對象的方法來傳遞信息。此外,新對象可以從基類(或父類)接收或繼承所有功能,這有助于減少重復(fù)代碼。
創(chuàng)建對象
JavaScript 中的對象可以用來描述現(xiàn)實世界中的物體,并賦予他們屬性和行為,就像它們在現(xiàn)實世界中的對應(yīng)物一樣。下面是使用這些概念來創(chuàng)建一個duck 對象的示例:
let duck = {
name: "Aflac",
numLegs: 2
};
在對象上創(chuàng)建方法
對象可以有一個叫做方法的特殊屬性。方法其實是一個值為函數(shù)的屬性,它可以為一個對象添加不同的行為。
let duck = {
name: "Aflac",
numLegs: 2,
sayName: function() {return "The name of this duck is " + duck.name + ".";}
};
duck.sayName(); // 返回了: "The name of this duck is Aflac."
訪問對象的屬性
使用點符號來訪問對象的屬性:console.log(duck.name);
使用 this 關(guān)鍵字使代碼更加可重用
如果變量名發(fā)生了改變,那么引用了原始名稱的任何代碼都需要更新。使用this關(guān)鍵字這個方法來避免這一問題:
let duck = {
name: "Aflac",
numLegs: 2,
sayName: function() {return "The name of this duck is " + this.name + ".";}
};
如果把對象的變量名改為mallard,那使用this就沒有必要在代碼中找到所有指向duck的部分,這樣可以使得代碼更具有可讀性和復(fù)用性。
定義構(gòu)造函數(shù)
構(gòu)造函數(shù)用以創(chuàng)建一個新對象,并給這個新對象定義屬性和行為。因此這是創(chuàng)建新對象的一個最基本的方式。
function Bird() {
this.name = "Albert";
this.color = "blue";
this.numLegs = 2;
}
構(gòu)造函數(shù)遵循一些慣例規(guī)則:
-
構(gòu)造函數(shù)函數(shù)名的首字母最好大寫,這是為了方便我們區(qū)分構(gòu)造函數(shù)和其他非構(gòu)造函數(shù)。 -
構(gòu)造函數(shù)使用this關(guān)鍵字來給它將創(chuàng)建的這個對象設(shè)置新的屬性。在構(gòu)造函數(shù)里面,this指向的就是它新創(chuàng)建的這個對象。 -
構(gòu)造函數(shù)定義了屬性和行為就可創(chuàng)建對象,而不是像其他函數(shù)一樣需要設(shè)置返回值。
function Bird() {
this.name = "Albert";
this.color = "blue";
this.numLegs = 2; // 構(gòu)造函數(shù)里面的 "this" 總是指向新創(chuàng)建的實例。
}
let blueBird = new Bird(); // 使用構(gòu)造函數(shù)創(chuàng)建對象
blueBird.name; // => Albert
blueBird.color; // => blue
blueBird.numLegs; // => 2
blueBird.name = 'Elvira';
blueBird.name; // => Elvira
function Bird(name, color) { // 擴展構(gòu)造函數(shù)以接收參數(shù)
this.name = name;
this.color = color;
this.numLegs = 2;
}
let cardinal = new Bird("Bruce", "red");
let Bird = function(name, color) {
this.name = name;
this.color = color;
this.numLegs = 2;
}
let crow = new Bird("Alexis", "black");
crow instanceof Bird; // => true 使用 instance of 驗證對象的構(gòu)造函數(shù)
let canary = { name: "Mildred", color: "Yellow", numLegs: 2 };
canary instanceof Bird; // => false
function Bird(name) {
this.name = name;
this.numLegs = 2;
}
let canary = new Bird("Tweety");
let ownProps = [];
for(let p in canary){
if(canary.hasOwnProperty(p)){ ownProps.push(p); }
}
使用原型屬性來減少重復(fù)代碼
原型是一個可以在所有Bird實例之間共享的對象。以下是一個在Bird prototype中添加numLegs屬性的示例:Bird.prototype.numLegs = 2;
現(xiàn)在所有的Bird實例都擁有了共同的numLegs屬性值。
console.log(duck.numLegs); // 在控制臺輸出 2
console.log(canary.numLegs); // 在控制臺輸出 2
迭代所有屬性:
function Bird(name) {
this.name = name; // 自身屬性
}
Bird.prototype.numLegs = 2; // 原型屬性
let duck = new Bird("Donald");
let ownProps = [];
let prototypeProps = [];
for (let property in duck) {
if(duck.hasOwnProperty(property)) {
ownProps.push(property);
} else {
prototypeProps.push(property);
}
}
console.log(ownProps); // 輸出 ["name"]
console.log(prototypeProps); // 輸出 ["numLegs"]
了解構(gòu)造函數(shù)屬性:由于constructor屬性可以被重寫(在下面兩節(jié)挑戰(zhàn)中將會遇到),所以使用instanceof方法來檢查對象的類型會更好。
let duck = new Bird();
let beagle = new Dog();
console.log(duck.constructor === Bird); //輸出 true
console.log(beagle.constructor === Dog); //輸出 true
將原型更改為新對象:
Bird.prototype.numLegs = 2; // 添加屬性
Bird.prototype.eat = function() { console.log("nom nom nom");}
Bird.prototype.describe = function() { console.log("My name is " + this.name);}
Bird.prototype = {
numLegs: 2,
eat: function() { console.log("nom nom nom"); },
describe: function() { console.log("My name is " + this.name); }
};
更改原型時,記得設(shè)置構(gòu)造函數(shù)屬性:手動給新對象重新設(shè)置原型對象,會產(chǎn)生一個重要的副作用:刪除了constructor屬性,console.log(duck.constructor); // undefined。
為了解決這個問題,凡是手動給新對象重新設(shè)置過原型對象的,都別忘記在原型對象中定義一個constructor屬性:
Bird.prototype = {
constructor: Bird, // 定義 constructor 屬性
numLegs: 2,
eat: function() { console.log("nom nom nom"); },
describe: function() { console.log("My name is " + this.name); }
};
了解對象的原型來自哪里:
function Bird(name) {
this.name = name;
}
let duck = new Bird("Donald");
Bird.prototype.isPrototypeOf(duck); // 返回 true
了解原型鏈:JavaScript 中所有的對象(除了少數(shù)例外)都有自己的原型。而且,對象的原型本身也是一個對象。
Object是 JavaScript 中所有對象的父級,也就是原型鏈的最頂層。因此,所有對象都可以訪問hasOwnProperty方法。
function Bird(name) {
this.name = name;
} // 正因為原型是一個對象,所以原型對象也有它自己的原型
typeof Bird.prototype; // => object,Bird.prototype的原型就是Object.prototype
Object.prototype.isPrototypeOf(Bird.prototype); // 返回 true
let duck = new Bird("Donald");
duck.hasOwnProperty("name"); // => true
// 在這個原型鏈中,Bird構(gòu)造函數(shù)是父級,duck是子級。Object則是Bird構(gòu)造函數(shù)和duck實例共同的父級。
繼承
使用繼承避免重復(fù):有一條原則叫做:Don't Repeat Yourself,常以縮寫形式DRY出現(xiàn),意思是“不要自己重復(fù)”。編寫重復(fù)代碼會產(chǎn)生的問題是:任何改變都需要去多個地方修復(fù)所有重復(fù)的代碼。這通常意味著我們需要做更多的工作,會產(chǎn)生更高的出錯率。
Bird.prototype = {
constructor: Bird,
describe: function() { console.log("My name is " + this.name); }
};
Dog.prototype = {
constructor: Dog,
describe: function() { console.log("My name is " + this.name); }
}; // 可以看到describe方法在兩個地方重復(fù)定義了。
function Animal() { };
Animal.prototype = {
constructor: Animal,
describe: function() { console.log("My name is " + this.name); }
}; // 根據(jù)DRY原則,通過創(chuàng)建一個Animal 超類(或者父類)來重寫這段代碼
Bird.prototype = {
constructor: Bird
};
Dog.prototype = {
constructor: Dog
}; // 將Bird和Dog這兩個構(gòu)造函數(shù)的方法刪除掉
從超類繼承行為:第一步:創(chuàng)建一個超類(或者叫父類)的實例。
let animal = new Animal();
let animal = Object.create(Animal.prototype); // 等價
// Object.create(obj)創(chuàng)建了一個新對象,并指定了obj作為新對象的原型。
// 回憶一下,我們之前說過原型就像是創(chuàng)建對象的“配方”。
animal.eat(); // 輸出 "nom nom nom"
animal instanceof Animal; // => true
將子輩的原型設(shè)置為父輩的實例:第二個步驟:給子類型(或者子類)設(shè)置原型。
Bird.prototype = Object.create(Animal.prototype);
let duck = new Bird("Donald");
duck.eat(); // 輸出 "nom nom nom"
// duck繼承了Animal構(gòu)造函數(shù)的所有屬性,其中包括了eat方法。
重置一個繼承的構(gòu)造函數(shù)屬性
當(dāng)一個對象從另一個對象那里繼承了其原型,那它也繼承了父類的 constructor 屬性。
function Bird() { }
Bird.prototype = Object.create(Animal.prototype);
let duck = new Bird();
duck.constructor // function Animal(){...}
但是duck和其他所有Bird的實例都應(yīng)該表明它們是由Bird創(chuàng)建的,而不是由Animal創(chuàng)建的。為此,你可以手動把Bird的 constructor 屬性設(shè)置為Bird對象:
Bird.prototype.constructor = Bird;
duck.constructor // function Bird(){...}
繼承后添加方法
從父類繼承其原型對象的構(gòu)造函數(shù)除了繼承的方法之外,還可以有自己的方法。
function Animal() { }
Animal.prototype.eat = function() {
console.log("nom nom nom");
};
function Bird() { }
Bird.prototype = Object.create(Animal.prototype);
Bird.prototype.constructor = Bird;
Bird.prototype.fly = function() { // 給Bird對象添加一個fly()函數(shù)
console.log("I'm flying!");
}; // 函數(shù)會以一種與其他構(gòu)造函數(shù)相同的方式添加到Bird的原型中
let duck = new Bird();
duck.eat(); // 輸出 "nom nom nom"
duck.fly(); // 輸出 "I'm flying!"
重寫繼承的方法:一個對象可以通過復(fù)制另一個對象的原型來繼承其屬性和行為(或方法)ChildObject.prototype = Object.create(ParentObject.prototype);
ChildObject將自己的方法鏈接到它的原型中:ChildObject.prototype.methodName = function() {...};
以同樣的方式——通過使用一個與需要重寫的方法相同的方法名,向ChildObject.prototype中添加方法。
function Animal() { }
Animal.prototype.eat = function() {
return "nom nom nom";
};
function Bird() { }
Bird.prototype = Object.create(Animal.prototype); // 繼承了 Animal 的所有方法
Bird.prototype.eat = function() {
return "peck peck peck"; // Bird.eat() 重寫了 Animal.eat() 方法
};
JavaScript 在duck的原型鏈上尋找方法的過程:
- duck => 這里定義了 eat() 方法嗎?沒有。
- Bird => 這里定義了 eat() 方法嗎?=> 是的。執(zhí)行它并停止往上搜索。
- Animal => 這里也定義了 eat() 方法,但是 JavaScript 在到達這層原型鏈之前已停止了搜索。
- Object => JavaScript 在到達這層原型鏈之前也已經(jīng)停止了搜索。
使用 Mixin 在不相關(guān)對象之間添加共同行為
行為是可以通過繼承來共享的。然而,在有些情況下,繼承不是最好的解決方案。繼承不適用于不相關(guān)的對象,比如Bird和Airplane。雖然它們都可以飛行,但是Bird并不是一種Airplane,反之亦然。
對于不相關(guān)的對象,更好的方法是使用mixins。mixin允許其他對象使用函數(shù)集合。
let flyMixin = function(obj) {
obj.fly = function() { console.log("Flying, wooosh!"); }
}; // flyMixin能接受任何對象,并為其提供fly方法
let bird = { name: "Donald", numLegs: 2};
let plane = { model: "777", numPassengers: 524};
flyMixin(bird);
flyMixin(plane);
bird.fly(); // 輸出 "Flying, wooosh!"
plane.fly(); // 輸出 "Flying, wooosh!"
用閉包保護對象內(nèi)的屬性不被外部修改
bird有一個公共屬性name。公共屬性的定義就是:它可以在bird的定義范圍之外被訪問和更改。bird.name = "Duffy";
使屬性私有化最簡單的方法就是在構(gòu)造函數(shù)中創(chuàng)建變量。可以將該變量范圍限定在構(gòu)造函數(shù)中,而不是全局可用。這樣,屬性只能由構(gòu)造函數(shù)中的方法訪問和更改。
function Bird() {
let hatchedEgg = 10; // 私有屬性
this.getHatchedEggCount = function() { // bird 對象可以是使用的公有方法
return hatchedEgg;
};
}
let ducky = new Bird(); // hatchedEgg是在與getHachedEggCount相同的上下文中聲明的
ducky.getHatchedEggCount(); // 返回 10
在 JavaScript 中,函數(shù)總是可以訪問創(chuàng)建它的上下文。這就叫做閉包。
了解立即調(diào)用函數(shù)表達(IIFE)
JavaScript 中的一個常見模式就是,函數(shù)在聲明后立刻執(zhí)行:函數(shù)沒有名稱,也不存儲在變量中。函數(shù)表達式末尾的兩個括號()導(dǎo)致它被立即執(zhí)行或調(diào)用。這種模式被叫做自執(zhí)行函數(shù)表達式或者IIFE。
(function () {
console.log("Chirp, chirp!");
})(); // 這是一個立即執(zhí)行的匿名函數(shù)表達式,立即輸出 "Chirp, chirp!"
使用 IIFE 創(chuàng)建一個模塊:一個自執(zhí)行函數(shù)表達式(IIFE)通常用于將相關(guān)功能分組到單個對象或者是模塊中。
function glideMixin(obj) {
obj.glide = function() { console.log("Gliding on the water"); };
}
function flyMixin(obj) {
obj.fly = function() { console.log("Flying, wooosh!"); };
}
let motionModule = (function () { // 將這些mixins分成以下模塊
return {
glideMixin: function (obj) {
obj.glide = function() { console.log("Gliding on the water"); };
},
flyMixin: function(obj) {
obj.fly = function() { console.log("Flying, wooosh!"); };
}
}
}) (); // 末尾的兩個括號導(dǎo)致函數(shù)被立即調(diào)用
一個自執(zhí)行函數(shù)表達式(IIFE)返回了一個motionModule對象。返回的這個對象包含了作為對象屬性的所有mixin行為。
模塊模式的優(yōu)點是,所有的運動行為都可以打包成一個對象,然后由代碼的其他部分使用。下面是一個使用它的例子:
motionModule.glideMixin(duck);
duck.glide();