freeCodeCamp 旅途7 - 算法基礎(chǔ)和面向?qū)ο?/h2>

算法基礎(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、null0、""undefinedNaN。

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的原型鏈上尋找方法的過程:

  1. duck => 這里定義了 eat() 方法嗎?沒有。
  2. Bird => 這里定義了 eat() 方法嗎?=> 是的。執(zhí)行它并停止往上搜索。
  3. Animal => 這里也定義了 eat() 方法,但是 JavaScript 在到達這層原型鏈之前已停止了搜索。
  4. Object => JavaScript 在到達這層原型鏈之前也已經(jīng)停止了搜索。

使用 Mixin 在不相關(guān)對象之間添加共同行為

行為是可以通過繼承來共享的。然而,在有些情況下,繼承不是最好的解決方案。繼承不適用于不相關(guān)的對象,比如Bird和Airplane。雖然它們都可以飛行,但是Bird并不是一種Airplane,反之亦然。
對于不相關(guān)的對象,更好的方法是使用mixinsmixin允許其他對象使用函數(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();
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容