算法基础
计算机算法是产生特定结果的一系列步骤。要写一个算法,你必须先理解一个特定的问题,然后编写代码去解决它。
function convertToF(celsius) {
let fahrenheit = celsius * 9 / 5 + 32;
return fahrenheit;
} // 将摄氏温度转换成华氏温度:摄氏度 × 9/5 + 32
convertToF(30);
翻转字符串
在反转字符串前可能需要将其切分成字符的数组。
function reverseString(str) { return str.split('').reverse().join(''); }
数字的阶乘
若 n 是一个整数,n 的阶乘就是所有小于等于 n 的正整数的乘积。
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);
}
返回数组中最大的数字
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));
}
检查字符串的结尾
检查一个字符串(第一个参数, str )是否以给定的字符串(第二个参数 target )结束。function confirmEnding(str, target) { return str.slice(str.length - target.length) === target; }
,还可以直接使用方法.endsWith()
。
重复字符串
将一个给定的字符串(第一个参数, str )重复 num (第二个参数)次。如果 num 不是一个正数,返回一个空字符串。
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) : '';
}
截断字符串
如果一个字符串(第一个参数)的长度大于给出的值(第二个参数),则截断它并在其后加上 ...
。返回被截断的字符串。
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) + '...';
}
}
发现者与看护者
检查一个数组(第一个参数)中的元素,并返回数组中第一个通过校验测试(第二个参数,一个接受一个参数并返回一个布尔值的函数)的元素。如果没有元素通过测试,则返回 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
将第一个数组中的所有元素依次复制到第二个数组中。
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;
}
去除数组中的假值
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);
}
我身在何处
返回数组(第一个参数)被排序后,将一个值(第二个参数)插入到该数组中而使数组保持有序的最小的索引。返回的值应该是一个数字。
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);
}
集合之间的关系
输入参数是一个有两个字符串元素的数组。如果第一个字符串中包含了第二个字符串中的所有字母,则返回 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;
});
}
猴子吃香蕉
将一个数组(第一个参数)分割成一组长度为 size(第二个参数)的数组,然后在一个二维数组中返回这些结果。
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;
}
面向对象编程
面向对象编程将代码组织成对象定义。这些有时被称为类,它们将数据和相关行为组合在一起。数据是对象的属性,行为(或函数)是方法。
对象结构能够在程序中灵活使用,比如对象可以通过调用数据并将数据传递给另一个对象的方法来传递信息。此外,新对象可以从基类(或父类)接收或继承所有功能,这有助于减少重复代码。
创建对象
JavaScript 中的对象
可以用来描述现实世界中的物体,并赋予他们属性
和行为
,就像它们在现实世界中的对应物一样。下面是使用这些概念来创建一个duck 对象
的示例:
let duck = {
name: "Aflac",
numLegs: 2
};
在对象上创建方法
对象
可以有一个叫做方法
的特殊属性
。方法
其实是一个值为函数的属性,它可以为一个对象添加不同的行为。
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 关键字使代码更加可重用
如果变量名发生了改变,那么引用了原始名称的任何代码都需要更新。使用this
关键字这个方法来避免这一问题:
let duck = {
name: "Aflac",
numLegs: 2,
sayName: function() {return "The name of this duck is " + this.name + ".";}
};
如果把对象的变量名改为mallard,那使用this
就没有必要在代码中找到所有指向duck的部分,这样可以使得代码更具有可读性和复用性。
定义构造函数
构造函数
用以创建一个新对象,并给这个新对象定义属性和行为。因此这是创建新对象的一个最基本的方式。
function Bird() {
this.name = "Albert";
this.color = "blue";
this.numLegs = 2;
}
构造函数
遵循一些惯例规则:
-
构造函数
函数名的首字母最好大写,这是为了方便我们区分构造函数
和其他非构造函数。 -
构造函数
使用this
关键字来给它将创建的这个对象设置新的属性。在构造函数
里面,this
指向的就是它新创建的这个对象。 -
构造函数
定义了属性和行为就可创建对象,而不是像其他函数一样需要设置返回值。
function Bird() {
this.name = "Albert";
this.color = "blue";
this.numLegs = 2; // 构造函数里面的 "this" 总是指向新创建的实例。
}
let blueBird = new Bird(); // 使用构造函数创建对象
blueBird.name; // => Albert
blueBird.color; // => blue
blueBird.numLegs; // => 2
blueBird.name = 'Elvira';
blueBird.name; // => Elvira
function Bird(name, color) { // 扩展构造函数以接收参数
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 验证对象的构造函数
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); }
}
使用原型属性来减少重复代码
原型
是一个可以在所有Bird
实例之间共享的对象。以下是一个在Bird prototype
中添加numLegs
属性的示例:Bird.prototype.numLegs = 2;
现在所有的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"]
了解构造函数属性:由于constructor
属性可以被重写(在下面两节挑战中将会遇到),所以使用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); }
};
更改原型时,记得设置构造函数属性:手动给新对象重新设置原型对象,会产生一个重要的副作用:删除了constructor
属性,console.log(duck.constructor); // undefined
。
为了解决这个问题,凡是手动给新对象重新设置过原型对象的,都别忘记在原型对象中定义一个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 中所有的对象(除了少数例外)都有自己的原型
。而且,对象的原型
本身也是一个对象。
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构造函数是父级,duck是子级。Object则是Bird构造函数和duck实例共同的父级。
继承
使用继承避免重复:有一条原则叫做:Don't Repeat Yourself
,常以缩写形式DRY
出现,意思是“不要自己重复”。编写重复代码会产生的问题是:任何改变都需要去多个地方修复所有重复的代码。这通常意味着我们需要做更多的工作,会产生更高的出错率。
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方法在两个地方重复定义了。
function Animal() { };
Animal.prototype = {
constructor: Animal,
describe: function() { console.log("My name is " + this.name); }
}; // 根据DRY原则,通过创建一个Animal 超类(或者父类)来重写这段代码
Bird.prototype = {
constructor: Bird
};
Dog.prototype = {
constructor: Dog
}; // 将Bird和Dog这两个构造函数的方法删除掉
从超类继承行为:第一步:创建一个超类(或者叫父类)的实例。
let animal = new Animal();
let animal = Object.create(Animal.prototype); // 等价
// Object.create(obj)创建了一个新对象,并指定了obj作为新对象的原型。
// 回忆一下,我们之前说过原型就像是创建对象的“配方”。
animal.eat(); // 输出 "nom nom nom"
animal instanceof Animal; // => true
将子辈的原型设置为父辈的实例:第二个步骤:给子类型
(或者子类
)设置原型
。
Bird.prototype = Object.create(Animal.prototype);
let duck = new Bird("Donald");
duck.eat(); // 输出 "nom nom nom"
// duck继承了Animal构造函数的所有属性,其中包括了eat方法。
重置一个继承的构造函数属性
当一个对象从另一个对象那里继承了其原型
,那它也继承了父类
的 constructor 属性。
function Bird() { }
Bird.prototype = Object.create(Animal.prototype);
let duck = new Bird();
duck.constructor // function Animal(){...}
但是duck
和其他所有Bird
的实例都应该表明它们是由Bird创建的,而不是由Animal
创建的。为此,你可以手动把Bird的 constructor
属性设置为Bird对象:
Bird.prototype.constructor = Bird;
duck.constructor // function Bird(){...}
继承后添加方法
从父类
继承其原型
对象的构造函数除了继承的方法之外,还可以有自己的方法。
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()函数
console.log("I'm flying!");
}; // 函数会以一种与其他构造函数相同的方式添加到Bird的原型中
let duck = new Bird();
duck.eat(); // 输出 "nom nom nom"
duck.fly(); // 输出 "I'm flying!"
重写继承的方法:一个对象可以通过复制另一个对象的原型
来继承其属性和行为(或方法)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() 方法吗?=> 是的。执行它并停止往上搜索。
- Animal => 这里也定义了 eat() 方法,但是 JavaScript 在到达这层原型链之前已停止了搜索。
- Object => JavaScript 在到达这层原型链之前也已经停止了搜索。
使用 Mixin 在不相关对象之间添加共同行为
行为是可以通过继承来共享的。然而,在有些情况下,继承不是最好的解决方案。继承不适用于不相关的对象,比如Bird和Airplane。虽然它们都可以飞行,但是Bird并不是一种Airplane,反之亦然。
对于不相关的对象,更好的方法是使用mixins
。mixin
允许其他对象使用函数集合。
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!"
用闭包保护对象内的属性不被外部修改
bird有一个公共属性name。公共属性的定义就是:它可以在bird的定义范围之外被访问和更改。bird.name = "Duffy";
使属性私有化最简单的方法就是在构造函数中创建变量。可以将该变量范围限定在构造函数中,而不是全局可用。这样,属性只能由构造函数中的方法访问和更改。
function Bird() {
let hatchedEgg = 10; // 私有属性
this.getHatchedEggCount = function() { // bird 对象可以是使用的公有方法
return hatchedEgg;
};
}
let ducky = new Bird(); // hatchedEgg是在与getHachedEggCount相同的上下文中声明的
ducky.getHatchedEggCount(); // 返回 10
在 JavaScript 中,函数总是可以访问创建它的上下文。这就叫做闭包
。
了解立即调用函数表达(IIFE)
JavaScript 中的一个常见模式就是,函数在声明后立刻执行:函数没有名称,也不存储在变量中。函数表达式末尾的两个括号()导致它被立即执行或调用。这种模式被叫做自执行函数表达式
或者IIFE
。
(function () {
console.log("Chirp, chirp!");
})(); // 这是一个立即执行的匿名函数表达式,立即输出 "Chirp, chirp!"
使用 IIFE 创建一个模块:一个自执行函数表达式(IIFE)通常用于将相关功能分组到单个对象或者是模块中。
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!"); };
}
}
}) (); // 末尾的两个括号导致函数被立即调用
一个自执行函数表达式
(IIFE
)返回了一个motionModule
对象。返回的这个对象包含了作为对象属性的所有mixin
行为。
模块
模式的优点是,所有的运动行为都可以打包成一个对象,然后由代码的其他部分使用。下面是一个使用它的例子:
motionModule.glideMixin(duck);
duck.glide();
网友评论