在JavaScript中包含6种基本的值类型:数字(number)、字符串(string)、布尔值(boolean)、对象(object)、函数(function)、和未定义类型(undefined)node
数字(number)类型的值即数字值。在JavaScript中写成以下形式:程序员
13web
注意:在处理分数的时候,将其视为近似值,而非精确值shell
JavaScript中的算术运算以下所示:编程
100 + 4 * 11小程序
正无穷大: Infinity数组
负无穷大:-Infinity浏览器
非数值: NaNbash
咱们使用字符串来表示文本信息。使用引号将内容括起来。数据结构
"Patch my boat with chewing gum"
'Monkeys wave goodbye'
console.log(typeof 4.5); // number
console.log(typeof "x"); // string
复制代码
该类型只有两种取值:true和false
console.log(3 > 2); // true
console.log(3 < 2); // false
console.log("Aardark" < "Zoroaster"); // true
console.log( NaN == NaN); // false
复制代码
JavaScript支持三种逻辑运算符:与(and)、或(or)、非(not)
console.log(true && false); // false
console.log(true && true); // true
console.log(false || true); // true
console.log(false || false); // false
// 三元运算符
console.log(true ? 1 : 2); // 1
console.log(false ? 1 : 2); // 2
复制代码
null和undefined用于表示无心义的值。它们各自表示其自身含义,除此以外不包含任何信息。
console.log(8 * null); // 0
console.log("5" - 1); // 4
console.log("5" + 1); // 51
console.log("five" * 2); // NaN
console.log(false == 0); // true
console.log("" === false); // false
复制代码
逻辑运算符的短路特性
console.log(null || "user"); // user
console.log("Karel" || "user"); // Karel
复制代码
最简单的一条语句由一个表达式和其后的分号组成。好比这就是一个程序:
1;
!false;
复制代码
var caught = 5 * 5;
var ten = 10;
console.log(ten * ten); // 100
var mood = "light";
console.log(mood); // light
mood = "dark";
console.log(mood); // dark
var luigisDebt = 140;
luigisDebt = luigisDebt - 35;
console.log(luigisDebt); // 105
var one = 1,two = 2;
console.log(one + two); // 3
复制代码
某些具备特殊含义的单词称之为关键字,好比var,关键字不能做为变量名。另外还有一些单词预留给JavaScript使用。
咱们将给定时间内的变量和变量值的集合称为环境。
在默认环境中包含了不少函数类型的值。函数是指包装在变量中的一段程序,咱们可使用这些值来执行包装好的程序。
alert("Good morning!");
复制代码
var x = 30;
console.log("the value of x is ",x); // the value of x is 30
复制代码
咱们将函数生成值的操做称之为返回值。
console.log(Math.max(2,4); // 4
console.log(Math.min(2,4) + 100); // 102
复制代码
在现代web编程当中不多使用这两个函数,主要缘由是你没法对弹出窗口的风格进行控制。但对于小程序或测试程序来讲,这两个函数仍是很是有用的。
confirm("Shall we,then?");
prompt("Tell me everything you know.","...");
复制代码
若你的程序中包含了不止一条语句,那么这些语句必定是按照从上到下的顺序执行的。
var theNumber = Number(prompt("Pick a number","");
alert("Your number is the square root of " + theNumber * theNumber);
复制代码
var theNumber = Number(prompt("Pick a number","");
if(!isNaN(theNumber)){
alert("Your number is the square root of " + theNumber * theNumber);
}else{
alert("Hey,Why didn't you give me a number?");
}
var num = Number(prompt("Pick a number","");
if(num < 10){
alert("small");
}else if(num < 100){
alert("medium");
}else{
alert("large");
}
复制代码
var number = 0;
while(number <= 12){
console.log(number);
number = number + 2;
}
// 编写一个程序用于显示2的10次方
var result = 1;
var counter = 0;
while(counter < 10){
result = result * 2;
counter++;
}
console.log(result);
do{
var name = prompt("who are you?");
}while(!name);
console.log(name);
复制代码
for(var number = 0; number <= 12;number = number + 2){
console.log(number);
}
var result = 1;
for(var counter = 0;counter < 10;counter++){
result = result * 2;
}
console.log(result);
复制代码
for(var current = 20; ; current++){
if(current % 7 == 0){
break;
}
}
console.log(current); // 21
复制代码
counter += 1;
result *= 2;
counter++;
counter--;
复制代码
switch(prompt("What is the weather like?")){
case "rainly":
console.log("Remember to bring an umbrella");
break;
case "sunny":
console.log("Dress lightly");
break;
case "cloudly":
console.log("Go outside");
break;
default:
console.log("Unknown weather type!");
break;
}
复制代码
var accountBalance = calculateBalance(account);
// It's a green hollow where a river sings accountBalance.adjust(); // Madly catching white tatters in the grass var report = new Report(); // Where the sun on the proud mountain rings: addToReport(accountBalance,report); // It's a little valley,foaming like light in a glass
/*
I first found ths number scrawled on the back of one of
my notebooks a few years ago.Since then,it has often
dropped by,showing up in phone numbers and the serial
numbers of products that I've bought.It obviously likes me,so I've decided to keep it.
*/
var myNumber = 112213;
复制代码
// 1
var str = "";
for(var i = 0; i < 7;i++){
str = str + "#";
console.log(str);
}
// 2
for(var i = 1; i <= 100;i++){
if(i % 3 == 0){
console.log("Fizz");
}else if(i % 5 == 0){
console.log("Buzz")
}else{
console.log(i);
}
}
for(var i = 1; i <= 100;i++){
if(i % 3 == 0 && i % 5 == 0){
console.log("FizzBuzz");
}else if(i % 3 == 0){
console.log("Fizz")
}else if(i % 5 == 0){
console.log("Buzz")
}else{
console.log(i);
}
}
// 3
var str = "";
var size = 8;
for(var i = 0; i < size; i++){
for(var j = 0; j < size;j++){
if((i + j)% 2 == 0){
str = str + "#";
}else{
str = str + " ";
}
}
str += "\n";
}
console.log(str);
复制代码
一个函数定义就是普通的变量定义,只不过变量类型刚好是函数。
建立函数的表达式以关键字function开头。
某些函数能够产生值,而一些函数不会产生值。函数中的return语句决定了函数的返回值。当函数执行到return语句时,会当即跳出当前函数,并将返回值赋给调用者。若是return关键字后没有任何表达式,则该函数返回undefined。
// 计算给定数字的平方
var square = functon(x) {
return x*x;
}
console.log(square(12)); // 144
var makeNoise = function() {
console.log("Pling!");
}
makeNoise(); // Pling!
var power = function(base,exponent) {
var result = 1;
for(var count = 0;count < exponent;count++) {
result *= base;
return result;
}
}
console.log(power(2,10)); // 1024
复制代码
函数的参数如同一个普通变量,但其初始值是由函数的调用者提供的,而不是函数自身的内部代码。
函数有一个重要属性,就是其内部建立的变量以及参数,都属于函数的局部变量。这意味着示例中power函数的result变量会在每次函数调用从新建立,这种隔离机制确保了不一样函数之间不会相互干扰。
var x = "outside";
var f1 = function() {
var x = "inside f1";
}
f1();
console.log(x); // outside
var f2 = function() {
x = "inside f2";
}
f2();
console.log(x); // inside f2
复制代码
JavaScript不只能区分全局变量和局部变量,还能够在函数中建立其余函数,并产生不一样程度的局部做用域。
var landscape = function(){
var result = "";
var flat = function(size){
for(var count = 0;count < size;count++){
result +="_";
}
}
var mountain = function(size) {
result +="/";
for(var count = 0;count < size;count++){
result += "'";
}
result += "\\";
}
flat(3);
mountain(4);
flat(6);
mountain(1);
flat(1);
return result;
};
console.log(landscape());
复制代码
函数变量一般只是充当一段特定程序的名字。这种变量只须要定义一次,并且永远不会改变。
你不只能够直接调用函数,还能够像使用其余类型的值同样使用函数类型的值、将其用在任何表达式中、将其存放在新的变量中或者将其做为参数传递给其余函数等。同理,一个用于存放函数的变量也只不过是一个普通变量,能够被赋予新的值。
var launchMissiles = function(value) {
missileSystem.launch("now");
};
if(safeMode){
launchMissiles = function(value){
// do something
}
}
复制代码
相较于使用“var square = function...”表达式来声明函数,还可使用另外一种更为简洁的方法声明函数。
function square(x) {
return x*x;
}
复制代码
从概念上看,函数声明会被移动到其做用域的顶端,全部做用域内的函数调用都不会有任何问题。
console.log("The future says:",future());
function future(){
return "We STILL have no flying cars."
}
console.log("The future says:",future1()); // Uncaught TypeError: future1 is not a function
var future1 = function () {
return "1111";
}
复制代码
function greet(who){
console.log("Hello" + who);
}
greet("Harry");
console.log("Bye");
复制代码
因为函数须要在执行结束后跳转回调用该函数的代码位置,所以计算机必须记住函数调用的上下文。
咱们将计算机存储这个上下文的区域称之为调用栈。每当函数调用时,当前的上下文信息就会被存储在栈顶。当函数返回时,系统会删除存储在栈顶的上下文信息,并使用该信息继续执行程序。
栈须要保存在计算机内存中。若栈存储的空间过大,计算机就会提示相似于“栈空间溢出”或“递归过多”的信息。
function chicken(){
return egg();
}
function egg(){
return chicken();
}
console.log(chicken() + "came first.");
复制代码
JavaScript对传入函数的参数数量几乎不作任何限制。若是你传递了过多参数,多余的参数就会被忽略掉,而若是你传递的参数过少,遗漏的参数将会被赋值成undefined。
该特性的缺点是你可能刚好向函数传递了错误数量的参数,但没有人会告诉你这个错误。
该特性的优势是咱们能够利用这种行为来让函数接收可选参数。
function power(base,exponent){
if(exponent == undefined) {
exponent = 2;
}
var result = 1;
for(var count = 0;count < exponent;count++){
result *= base;
}
return result;
}
console.log(power(4)); // 16
console.log(power(4,3)); // 64
复制代码
函数能够做为值使用,并且其局部变量会在每次函数调用时从新建立。
function wrapValue(n) {
var localVariable = n;
return function() {
return localVariable;
}
}
var wrap1 = wrapValue(1);
var wrap2 = wrapValue(2);
console.log(wrap1()); // 1
console.log(wrap2()); // 2
复制代码
其实同一变量的多个实例能够同时存在,这也就很好地印证了局部变量会在每次函数调用时从新建立,不一样的函数调用是不会对其余函数内的局部变量产生影响的。
咱们把这种引用特定的局部变量实例的功能称为闭包。一个包装了一些局部变量的函数是一个闭包。
function multiplier(factor) {
return function(number) {
return number * factor;
}
}
var twice = multiplier(2);
console.log(twice(5)); // 10
复制代码
函数彻底能够本身调用本身,只要避免栈溢出的问题便可。咱们把函数调用自身的行为称为递归。
function power(base,exponent){
if(exponent == 0){
return 1;
}else{
return base * power(base,exponent -1);
}
}
console.log(power(2,3)); // 8
复制代码
在标准的JavaScript实现当中,递归写法的函数执行效率比循环写法的函数慢了大约10倍。执行简单的循环操做比屡次函数调用效率要高不少。
基本原则:除非程序执行速度确实太慢,不然先不要关注效率问题。一旦出现速度太慢的状况,找出占用时间最多的部分,而后将其替换成效率更高的代码。
可是,咱们并不能以偏概全的说递归就只是比循环的效率低。对于某些问题来讲,递归相较于循环更能解决问题。这类问题一般须要执行和处理多个分支,而每一个分支又会引出更多的执行分支。
// 从数字1开始,每次能够将数字加5或乘以3,循环执行。这样能够产生无穷多个新数字。那么如何编写函数来找出一个加法乘法序列,以产生指定的数字呢?例如,数字13能够经过1次乘法和2次加法生成,而数字15永远没法获得。使用递归编码的解决方案以下:
function findSolution(target){
function find(start,history){
if(start == target){
return history;
}else if(start > target){
return null;
}else{
return find(start + 5,"(" + history + "+5 )") || find(start * 3,"(" + history + "*3 )")
}
}
return find(1,"1");
}
console.log(findSolution(24));
复制代码
这里有两种经常使用的方法将函数引入到程序中。
第一种方法是找出你的程序中屡次出现的类似代码。咱们能够把重复功能的代码提取出来放到一个函数中去,并起一个合适的名字。
第二种方法是当你写一些新功能代码,并以为这些代码应该成为一个函数时,咱们将这部分代码写到一个函数中,并取一个函数名。
// 编写一个打印两个数字的程序,第一个数字是农场中牛的数量,第二个数字是农场中鸡的数量,并在数字后面跟上Cows 和Chickens用以说明,而且在两个数字前填充0,以使得每一个数字老是由三位数字组成。
function printFarmInventory(cows,chickens) {
var cowString = String(cows);
while(cowString.length < 3){
cowString = "0" + cowString;
}
console.log(cowString + " cows");
var chickenString = String(chickens);
while(chickenString.length < 3){
chickenString = "0" + chickenString;
}
console.log(chickenString + " Chickens");
}
printFarmInventory(7,11);
// 扩展软件来输出猪的数量
function printZeroPaddedWithLabel(number,label){
var numberString = String(number);
while(numberString.length < 3){
numberString = "0" + numberString;
}
console.log(numberString + " " + label);
}
function printFarmInventory(cows,chickens,pigs){
printZeroPaddedWithLabel(cows,"Cows");
printZeroPaddedWithLabel(chickens,"Chickens");
printZeroPaddedWithLabel(pigs,"Pigs");
}
printFarmInventory(7,11,3);
// 继续优化
function zeroPad(number,width){
var string = String(number);
while(string.length < width){
string = "0" + string;
}
return string;
}
function printFarmInventory(cows,chickens,pigs){
console.log(zeroPad(cows,3) + "Cows");
console.log(zeroPad(chickens,3) + "Chickens");
console.log(zeroPad(pigs,3) + "pigs");
}
printFarmInventory(7,16,3);
复制代码
比较好的方法是,尽可能不要在函数中添加过多功能,除非你真的须要它。咱们老是但愿可以编写一个通用的框架来解决遇到的各种问题。可是别急,这么作其实并不能解决任何问题,并且不会有人真的去使用这样的代码。
咱们能够将函数分红两类:一类调用后产生反作用,而另外一类则产生返回值(固然咱们也能够定义同时产生反作用和返回值的函数)。
相比于直接产生反作用的函数,产生返回值的函数则更容易集成到新的环境当中使用。
纯函数是一种只会产生值并且不会影响它本身范围外任何事情的函数。
// 1
function min(a,b){
if(a> b){
return b;
}else{
return a;
}
}
console.log(min(1,2));
// 2
function isEven(n){
if(n == 0){
return true;
}else if(n == 1){
return false;
}else if (n < 0){
return isEven(-n);
}else{
return isEven(n-2);
}
}
console.log(isEven(50)); // true
console.log(isEven(75)); // false
console.log(isEven(-1)); // false
// 3
function countBs(str){
var count = 0;
for(var i = 0;i < str.length;i++){
if(str[i] == 'B'){
count++;
}
}
return count;
}
console.log(countBs("12B45B12B"));
function countChar(str,target){
var count = 0;
for(var i = 0; i < str.length;i++){
if(str[i] == target){
count++;
}
}
return count;
}
console.log(countChar("12A345AaA","A"));
// 官方正解
function countChar(string, ch) {
let counted = 0;
for (let i = 0; i < string.length; i++) {
if (string[i] == ch) {
counted += 1;
}
}
return counted;
}
function countBs(string) {
return countChar(string, "B");
}
console.log(countBs("BBC"));
console.log(countChar("kakkerlak", "k"));
复制代码
数字、布尔值和字符串构成了基本的数据结构。少了其中任何同样,你可能都很难构造出完整的结构。咱们可使用对象来把值和其余对象组织起来,经过这种手段来构造更为复杂的结构。
JavaScript提供了一种数据类型,专门用于存储一系列的值。咱们将这种数据类型称为数组(array),将一连串的值写在方括号当中,值之间使用逗号(,)分隔。
var listOfNumbers = [2,3,5,7,11];
console.log(listOfNumbers[1]); // 3
console.log(listOfNumbers[1 -1]); // 2
复制代码
在JavaScript中,几乎全部的值都有属性。但null和undefined没有。
在JavaScript中有两种最为经常使用的访问属性的方法:使用点(.)和方括号[]。 若是使用点,则点以后的部分必须是一个合法变量名,即直接写属性名称。若是使用方括号,则JavaScript会将方括号中表达式的返回值做为属性名称。
除了length属性之外,字符串和数组对象还包含了许多其余属性,这些属性是函数值。
var doh = "Doh";
console.log(typeof doh.toUpperCase); // function
console.log(doh.toUpperCase()); // DOH
复制代码
咱们一般将包含函数的属性称为某个值的方法(method)。好比说,“toUpperCase是字符串的一个方法”。
var mack = [];
mack.push("Mack");
mack.push("the","knife");
console.log(mack);
console.log(mack.join(" "));
console.log(mack.pop());
console.log(mack);
复制代码
咱们可使用push方法向数组的末尾添加值,pop方法则做用相反:删除数组末尾的值,并返回给调用者。咱们可使用join方法将字符串数组拼接成单个字符串,join方法的参数是链接数组元素之间的文本内容。
对象类型的值能够存储任意类型的属性,咱们能够随意增删这些属性。一种建立对象的方法是使用大括号。
var day1 = {
squirrel: false,
events: ["work","touched tree","pizza","running","television"]
};
console.log(day1.squirrel); // false
console.log(day1.wolf); // undefined
day1.wolf = false;
console.log(day1.wolf); // false
复制代码
在大括号中,咱们能够添加一系列的属性,并用逗号分隔。每个属性均以名称开头,紧跟一个冒号,而后是对应属性的表达式。若是属性名不是有效的变量名或者数字,则须要使用引号将其括起来。
var description = {
work: "Went to work",
"touched tree": "Touched a tree"
}
复制代码
读取一个不存在的属性就会产生undefined值。
delete是个一元运算符,其操做数是访问属性的表达式,能够从对象中移除指定属性。
var anObject = {
left: 1,
right: 2
}
console.log(anObject.left); // 1
delete anObject.left;
console.log(anObject.left); // undefined
console.log("left" in anObject); // false
console.log("right" in anObject); // true
复制代码
二元运算符in的第一个操做数是一个表示属性名的字符串,第二个操做数是一个对象,它会返回一个布尔值,表示该对象是否包含该属性。将属性设置为undefined与使用delete删除属性的区别在于:对于第一种状况,对象仍然包含left属性,只不过该属性没有引用任何值;对于第二种状况,对象中已不存在left属性,所以in运算符会返回false。
数组只不过是一种用于存储数据序列的特殊对象,所以typeof[1,2]的执行结果是“Object”。
// 咱们能够用一个数组对象来表示雅克的日志
var journal = [
{
events: ["work","touched tree","pizza","running","television"],
squirrel: false
},
{
events: ["work","ice cream","cauliflower","lasagna","touched tree","brushed teeth"],
squirrel: false
}
/* and so on */
];
复制代码
数字、字符串和布尔值都是不可变值,咱们没法修改这些类型值的内容。
但对于对象来讲,咱们能够经过修改其属性来改变对象的内容。
var object1 = {value: 10};
var object2 = object1;
var object3 = {value: 10};
console.log(object1 == object2); // true
console.log(object1 == object3); // false
object1.value = 15;
console.log(object2.value); // 15
console.log(object3.value); // 10
复制代码
在JavaScript中,使用==运算符来比较两个对象时,只有两个对象引用了同一个值,结果才会返回true。比较两个不一样的对象将会返回false,哪怕对象内容相同。JavaScript中没有内置深度比较运算符(比较对象内容),但你能够本身编写一个。
var journal = [];
function addEntry(events,didITurnIntoASquirrel){
journal.push({
events: events,
squirrel: didITurnIntoASquirrel
})
}
addEntry(["work","touched tree","pizza","running","television"],false);
addEntry(["work","ice cream","cauliflower","lasagna","touched tree","brushed teeth"],false);
addEntry(["weekend","cycling","break","peanuts","beer"],true);
复制代码
// 计算数组的系数Φ
function phi(table) {
return (table[3] * table[0] - table[2] * table[1])/Math.sqrt((table[2] + table[3]) * (table[0] + table[1]) * (table[1] + table[3]) * (table[0] + table[2]));
}
console.log(phi([76,9,4,1]));
// 循环遍历整个记录,并计算出与变身成松鼠相关事件发生的次数。
var JOURNAL = [
{"events":["carrot","exercise","weekend"],"squirrel":false},
{"events":["bread","pudding","brushed teeth","weekend","touched tree"],"squirrel":false},
{"events":["carrot","nachos","brushed teeth","cycling","weekend"],"squirrel":false},
{"events":["brussel sprouts","ice cream","brushed teeth","computer","weekend"],"squirrel":false},
{"events":["potatoes","candy","brushed teeth","exercise","weekend","dentist"],"squirrel":false},
{"events":["brussel sprouts","pudding","brushed teeth","running","weekend"],"squirrel":false},
{"events":["pizza","brushed teeth","computer","work","touched tree"],"squirrel":false},
{"events":["bread","beer","brushed teeth","cycling","work"],"squirrel":false},
{"events":["cauliflower","brushed teeth","work"],"squirrel":false},
{"events":["pizza","brushed teeth","cycling","work"],"squirrel":false},
{"events":["lasagna","nachos","brushed teeth","work"],"squirrel":false},
{"events":["brushed teeth","weekend","touched tree"],"squirrel":false},
{"events":["lettuce","brushed teeth","television","weekend"],"squirrel":false},
{"events":["spaghetti","brushed teeth","work"],"squirrel":false},
{"events":["brushed teeth","computer","work"],"squirrel":false},
{"events":["lettuce","nachos","brushed teeth","work"],"squirrel":false},
{"events":["carrot","brushed teeth","running","work"],"squirrel":false},
{"events":["brushed teeth","work"],"squirrel":false},
{"events":["cauliflower","reading","weekend"],"squirrel":false},
{"events":["bread","brushed teeth","weekend"],"squirrel":false},
{"events":["lasagna","brushed teeth","exercise","work"],"squirrel":false},
{"events":["spaghetti","brushed teeth","reading","work"],"squirrel":false},
{"events":["carrot","ice cream","brushed teeth","television","work"],"squirrel":false},
{"events":["spaghetti","nachos","work"],"squirrel":false},
{"events":["cauliflower","ice cream","brushed teeth","cycling","work"],"squirrel":false},
{"events":["spaghetti","peanuts","computer","weekend"],"squirrel":true},
{"events":["potatoes","ice cream","brushed teeth","computer","weekend"],"squirrel":false},
{"events":["potatoes","ice cream","brushed teeth","work"],"squirrel":false},
{"events":["peanuts","brushed teeth","running","work"],"squirrel":false},
{"events":["potatoes","exercise","work"],"squirrel":false},
{"events":["pizza","ice cream","computer","work"],"squirrel":false},
{"events":["lasagna","ice cream","work"],"squirrel":false},
{"events":["cauliflower","candy","reading","weekend"],"squirrel":false},
{"events":["lasagna","nachos","brushed teeth","running","weekend"],"squirrel":false},
{"events":["potatoes","brushed teeth","work"],"squirrel":false},
{"events":["carrot","work"],"squirrel":false},
{"events":["pizza","beer","work","dentist"],"squirrel":false},
{"events":["lasagna","pudding","cycling","work"],"squirrel":false},
{"events":["spaghetti","brushed teeth","reading","work"],"squirrel":false},
{"events":["spaghetti","pudding","television","weekend"],"squirrel":false},
{"events":["bread","brushed teeth","exercise","weekend"],"squirrel":false},
{"events":["lasagna","peanuts","work"],"squirrel":true},
{"events":["pizza","work"],"squirrel":false},
{"events":["potatoes","exercise","work"],"squirrel":false},
{"events":["brushed teeth","exercise","work"],"squirrel":false},
{"events":["spaghetti","brushed teeth","television","work"],"squirrel":false},
{"events":["pizza","cycling","weekend"],"squirrel":false},
{"events":["carrot","brushed teeth","weekend"],"squirrel":false},
{"events":["carrot","beer","brushed teeth","work"],"squirrel":false},
{"events":["pizza","peanuts","candy","work"],"squirrel":true},
{"events":["carrot","peanuts","brushed teeth","reading","work"],"squirrel":false},
{"events":["potatoes","peanuts","brushed teeth","work"],"squirrel":false},
{"events":["carrot","nachos","brushed teeth","exercise","work"],"squirrel":false},
{"events":["pizza","peanuts","brushed teeth","television","weekend"],"squirrel":false},
{"events":["lasagna","brushed teeth","cycling","weekend"],"squirrel":false},
{"events":["cauliflower","peanuts","brushed teeth","computer","work","touched tree"],"squirrel":false},
{"events":["lettuce","brushed teeth","television","work"],"squirrel":false},
{"events":["potatoes","brushed teeth","computer","work"],"squirrel":false},
{"events":["bread","candy","work"],"squirrel":false},
{"events":["potatoes","nachos","work"],"squirrel":false},
{"events":["carrot","pudding","brushed teeth","weekend"],"squirrel":false},
{"events":["carrot","brushed teeth","exercise","weekend","touched tree"],"squirrel":false},
{"events":["brussel sprouts","running","work"],"squirrel":false},
{"events":["brushed teeth","work"],"squirrel":false},
{"events":["lettuce","brushed teeth","running","work"],"squirrel":false},
{"events":["candy","brushed teeth","work"],"squirrel":false},
{"events":["brussel sprouts","brushed teeth","computer","work"],"squirrel":false},
{"events":["bread","brushed teeth","weekend"],"squirrel":false},
{"events":["cauliflower","brushed teeth","weekend"],"squirrel":false},
{"events":["spaghetti","candy","television","work","touched tree"],"squirrel":false},
{"events":["carrot","pudding","brushed teeth","work"],"squirrel":false},
{"events":["lettuce","brushed teeth","work"],"squirrel":false},
{"events":["carrot","ice cream","brushed teeth","cycling","work"],"squirrel":false},
{"events":["pizza","brushed teeth","work"],"squirrel":false},
{"events":["spaghetti","peanuts","exercise","weekend"],"squirrel":true},
{"events":["bread","beer","computer","weekend","touched tree"],"squirrel":false},
{"events":["brushed teeth","running","work"],"squirrel":false},
{"events":["lettuce","peanuts","brushed teeth","work","touched tree"],"squirrel":false},
{"events":["lasagna","brushed teeth","television","work"],"squirrel":false},
{"events":["cauliflower","brushed teeth","running","work"],"squirrel":false},
{"events":["carrot","brushed teeth","running","work"],"squirrel":false},
{"events":["carrot","reading","weekend"],"squirrel":false},
{"events":["carrot","peanuts","reading","weekend"],"squirrel":true},
{"events":["potatoes","brushed teeth","running","work"],"squirrel":false},
{"events":["lasagna","ice cream","work","touched tree"],"squirrel":false},
{"events":["cauliflower","peanuts","brushed teeth","cycling","work"],"squirrel":false},
{"events":["pizza","brushed teeth","running","work"],"squirrel":false},
{"events":["lettuce","brushed teeth","work"],"squirrel":false},
{"events":["bread","brushed teeth","television","weekend"],"squirrel":false},
{"events":["cauliflower","peanuts","brushed teeth","weekend"],"squirrel":false}
];
function hasEvent(event,entry) {
return entry.events.indexOf(event) != -1;
}
function tableFor(event,journal) {
var table = [0,0,0,0];
for(var i = 0; i < journal.length; i++){
var entry = journal[i];
var index = 0;
if(hasEvent(event,entry)){
index += 1;
}
if(entry.squirrel){
index += 2;
}
table[index] += 1;
}
return table;
}
console.log(tableFor("pizza",JOURNAL));
复制代码
映射表(map)能够经过一个值(在本例中是事件名)来获取对应的另外一个值(在本例中是Φ系数)。
var map = {};
function storePhi(event,phi) {
map[event] = phi;
}
storePhi("pizza",0.069);
storePhi("touched tree",-0.081);
console.log("pizza" in map); // true
console.log(map["touched tree"]); // -0.081
复制代码
JavaScript提供了另外一种遍历对象属性的循环语句。它与通常的for循环看起来很像,只是咱们使用的关键字不是for而是in。
for(var event in map) {
console.log("The correlation for '" + event + "' is " + map[event]);
}
// The correlation for 'pizza' is 0.069
// The correlation for 'touched tree' is -0.081
复制代码
为了找出数据集中存在的全部事件类型,咱们只需依次处理每条记录,而后遍历记录中的全部事件便可。
function gatherCorrelations(journal) {
var phis = {};
for(var entry = 0;entry < journal.length;entry++){
var events = journal[entry].events;
for(var i = 0; i < events.length;i++){
var event = events[i];
if(!(event in phis)){
phis[event] = phi(tableFor(event,journal));
}
}
}
return phis;
}
var correlations = gatherCorrelations(JOURNAL);
console.log(correlations.pizza);
for(var event in correlations) {
console.log(event + ":" + correlations[event]);
}
for(var event in correlations) {
var correlation = correlations[event];
if(correlation > 0.1 || correlation < -0.1){
console.log(event + ":" + correlation);
}
}
for(var i = 0;i < JOURNAL.length;i++){
var entry = JOURNAL[i];
if(hasEvent("peanuts",entry) && !hasEvent("brushed teeth",entry)){
entry.events.push("peanut teeth");
}
}
console.log(phi(tableFor("peanut teeth",JOURNAL))); // 1
复制代码
一些实用的数组方法
push和pop,分别用于在数组末尾添加或删除元素。
unshift和shift,分别用于在数组的开头添加或删除元素。
var todoList = [];
function rememberTo(task) {
todoList.push(task);
}
function whatIsNext() {
return todoList.shift();
}
function urgentlyRememberTo(task) {
todoList.unshift(task);
}
复制代码
indexOf, 从数组第一个元素向后搜索。
lastIndexOf,从数组最后一个元素向前搜索。
indexOf和lastIndexOf方法都有一个可选参数,能够用来指定搜索的起始位置。
console.log([1,2,3,2,1].indexOf(2)); // 1
console.log([1,2,3,2,1].lastIndexOf(2)); // 3
复制代码
slice,该方法接受一个起始索引和一个结束索引,而后返回数组中两个索引范围内的元素。起始索引元素包含在返回结果中,但结束索引元素不会包含在返回结果中。若是没有指定结束索引,slice会返回从起始位置以后的全部元素。对于字符串来讲,它也有一个具备相同功能的slice方法供开发人员使用。
console.log([0,1,2,3,4].slice(2,4)); // [2,3]
console.log([0,1,2,3,4].slice(2)); // [2,3,4]
复制代码
concat 方法用于拼接两个数组,其做用相似于字符串的+运算符。
function remove(array,index) {
return array.slice(0,index).concat(array.slice(index + 1 ));
}
console.log(remove(["a","b","c","d","e"],2)); // ["a","b","d","e"]
复制代码
咱们能够调用字符串的length或toUpperCase这样的属性,但不能向字符串中添加任何新的属性。
var myString = "Fido";
myString.myProperty = "value";
console.log(myString.myProperty); // undefined
复制代码
字符串、数字和布尔类型的值并非对象,所以当你向这些值中添加属性时JavaScript并不会报错,但实际上你并无将这些属性添加进去。这些值都是不可变的,并且没法向其中添加任何属性。
但这些类型的值包含一些内置属性。每一个字符串中包含了若干方法供咱们使用,最有用的方法可能就是slice和indexOf了,它们的功能与数组中的同名方法相似。
console.log("coconuts".slice(4,7)); // nut
console.log("coconuts".indexOf("u")); // 5
复制代码
惟一的区别在于,字符串的indexOf方法可使用多个字符做为搜索条件,而数组中的indexOf方法则只能搜索单个元素。
console.log("one two three".indexOf("ee")); // 11
复制代码
trim方法用于删除字符串中开头和结尾的空白符号(空格、换行和制表符等符号)。
console.log(" okay \n ".trim()); // okay
复制代码
// 获取字符串中某个特定的字符
var string = "abc";
console.log(string.length); // 3
console.log(string.charAt(0)); // a
console.log(string[1]); // b
复制代码
每当函数被调用时,就会在函数体的运行环境当中添加一个特殊的变量arguments。该变量指向一个包含了全部入参的对象。在JavaScript中,咱们能够传递多于(或少于)函数参数列表定义个数的参数。
function noArguments(){};
noArguments(1,2,3); // This is okay
function threeArgumnents(a,b,c){};
threeArguments(); // And so is this
复制代码
arguments对象有一个length属性,表示实际传递给函数的参数个数。每一个参数对应一个属性,被命名为0,1,2,以此类推。
function argumentCounter() {
console.log("You gave me",arguments.length,"arguments.");
}
argumentCounter("Straw man","Tautology","Ad hominem"); // You gave me 3 arguments.
复制代码
function addEntry(squirrel){
var entry = {
events: [],
squirrel: squirrel
};
for(var i = 1; i < arguments.length; i++){
entry.events.push(arguments[i]);
}
journal.push(entry);
}
addEntry(true,"work","touched tree","pizza","running","television");
复制代码
Math对象简单地把一组相关的功能打包成一个对象供用户使用。全局只有一个Math对象,其对象自己没有什么实际用途。Math对象其实提供了一个“命名空间”,封装了全部的数学运算函数和值,确保这些元素不会变成全局变量。
过多的全局变量会对命名空间形成“污染”。全局变量越多,就越有可能一不当心把某些变量的值覆盖掉。
function randomPointOnCircle(radius) {
var angle = Math.random() * 2 * Math.PI;
return {
x: radius * Math.cos(angle),
y: radius * Math.sin(angle)
};
}
console.log(randomPointOnCircle(2));
复制代码
Math.random,每次调用该函数时,会返回一个伪随机数,范围在0(包括) ~ 1(不包括)之间。
console.log(Math.random());
console.log(Math.random());
console.log(Math.random());
复制代码
Math.floor,向下取整
Math.ceil,向上取整
Math.round,四舍五入
console.log(Math.floor(Math.random() * 10)); // 等几率地取到0~9中的任何一个数字。
复制代码
JavaScript全局做用域中有许多全局变量,均可以经过全局对象进行访问。每个全局变量做为一个属性存储在全局对象当中。在浏览器中,全局对象存储在window变量当中。
var myVar = 10;
console.log("myVar" in window); // true
console.log(window.myVar); // 10
复制代码
对象和数组(一种特殊对象)能够将几个值组合起来造成一个新的值。
在JavaScript中,除了null和undefined之外,绝大多数的值都含有属性。
在数组中有一些具名属性,好比length和一些方法。
对象能够用做映射表,将名称与值关联起来。咱们可使用in运算符肯定对象中是否包含特定名称的属性。咱们一样能够在for循环中(for(var name in object))使用关键字in来遍历对象中包含的属性。
// 1 编写一个range函数,接受两个参数:start和end,而后返回包含start到end(包括end)之间的全部数字。
function range(start,end){
var arr = [];
if(start > end){
for(var i = start; i >= end;i--){
arr.push(i);
}
}else{
for(var i = start; i <= end;i++){
arr.push(i);
}
}
return arr;
}
console.log(range(1,10));
console.log(range(5,2,-1));
// 2 编写一个sum函数,接受一个数字数组,并返回全部数字之和
function sum(arr){
var sum = 0;
for(var i = 0; i < arr.length;i++){
sum += arr[i];
}
return sum;
}
console.log(sum(range(1, 10)));
// 3 附加题修改range函数,接受第3个可选参数,指定构建数组时的步数(step)。
function range(start,end,step){
var arr = [];
if(step == undefined){
step = 1;
}
if(start > end){
for(var i = start; i >= end;i= i + step){
arr.push(i);
}
}else{
for(var i = start; i <= end;i = i + step){
arr.push(i);
}
}
return arr;
}
console.log(range(1,10,2));
console.log(range(5,2,-1));
// 官方正解
function range(start, end, step = start < end ? 1 : -1) {
let array = [];
if (step > 0) {
for (let i = start; i <= end; i += step) array.push(i);
} else {
for (let i = start; i >= end; i += step) array.push(i);
}
return array;
}
function sum(array) {
let total = 0;
for (let value of array) {
total += value;
}
return total;
}
// 逆转数组
function reverseArray(arr){
var output = [];
for(var i = arr.length-1;i >= 0;i--){
output.push(arr[i]);
}
return output;
}
console.log(reverseArray([1,2,3,4,5]));
// 逆转数组2
function reverseArrayInPlace(array){
for(let i = 0; i < Math.floor(array.length / 2);i++){
let old = array[i];
array[i] = array[array.length -1-i];
array[array.length-1-i]= old;
}
return array;
}
console.log(reverseArrayInPlace([1,2,3,4,5]));
// 实现列表
var list = {
value: 1,
rest: {
value: 2,
rest: {
value: 3,
rest: null
}
}
}
function arrayToList(array) {
let list = null;
for (let i = array.length - 1; i >= 0; i--) {
list = {value: array[i], rest: list};
}
return list;
}
console.log(arrayToList([1,2,3]));
// 列表转换成数组
function listToArray(list) {
let array = [];
for (let node = list; node; node = node.rest) {
array.push(node.value);
}
return array;
}
function listToArray(list){
let array = [];
for(let node = list;node;node = node.rest){
array.push(node.value);
}
return array;
}
console.log(listToArray(list));
// 建立一个新的列表
function prepend(value,list) {
return {
value,
rest: list
};
}
// 返回列表中指定位置的元素
function nth(list, n) {
if (!list) return undefined;
else if (n == 0) return list.value;
else return nth(list.rest, n - 1);
}
function nth(list,n) {
if(!list){
return undefined;
}else if(n == 0){
return list.value;
}else {
return nth(list.rest,n-1);
}
}
console.log(list,3);
// 深度比较 编写一个函数deepEqual,接受两个参数,若两个对象是同一个值或两个对象中有相同属性,且使用deepEqual比较属性值均返回true时,返回true
function deepEqual(a, b) {
if (a === b) return true;
if (a == null || typeof a != "object" ||
b == null || typeof b != "object") return false;
let keysA = Object.keys(a), keysB = Object.keys(b);
if (keysA.length != keysB.length) return false;
for (let key of keysA) {
if (!keysB.includes(key) || !deepEqual(a[key], b[key])) return false;
}
return true;
}
复制代码
让咱们简单回顾一下前言当中的两个示例。其中第一个程序包含了6行代码并能够直接运行。
var total = 0,count = 1;
while(count <= 10){
total += count;
count += 1;
}
console.log(total);
复制代码
第二个程序则依赖于外部函数才能执行,且只有一行代码。
console.log(sum(range(1,10)));
复制代码
第二个程序编写的代码很好地表达了咱们指望解决的问题。相比于将这些代码直接写到一块儿,这种表述方式更为简单,同时也易于避免错误。
在程序设计中,咱们把这种编写代码的方式称为抽象。抽象能够隐藏底层的实现细节,从更高(或更加抽象)的层次看待咱们要解决的问题。
做为一名程序员,咱们须要具有在恰当时候将代码抽象出来,造成一个新的函数或概念的能力。
// 1.将数组中的每一个元素打印到控制台
var array = [1,2,3];
for(var i = 0; i < array.length;i++){
var current = array[i];
console.log(current);
}
// 2.将1抽象成一个函数
function logEach(array){
for(var i = 0; i < array.length;i++){
console.log(array[i]);
}
}
function forEach(array,action){
for(var i = 0; i < array.length;i++){
action(array[i]);
}
}
forEach(["Wampeter","Foma","Granfalloon"],console.log);
// 3.一般来讲,咱们不会给forEach传递一个预约义的函数,而是直接新建一个函数值。
var numbers = [1,2,3,4,5],sum = 0;
forEach(numbers,function(number){
sum += number;
});
console.log(sum);
复制代码
实际上,咱们不须要本身编写forEach函数,该函数实际上是数组的一个标准方法。
function gatherCorrelations(journal) {
var phis = {};
for(var entry = 0;entry < journal.length;entry++){
var events = journal[entry].events;
for(var i = 0; i < events.length;i++){
var event = events[i];
if(!(event in phis)){
phis[event] = phi(tableFor(event,journal));
}
}
}
return phis;
}
// 使用forEach改写上面的代码
function gatherCorrelations(journal){
var phis = {};
journal.forEach(function(entry){
entry.events.forEach(function (event){
if(!(event in phis)){
phis[event] = phi(tableFor(event,journal));
}
})
})
return phis;
}
复制代码
若是一个函数操做其余函数,即将其余函数做为参数或将函数做为返回值,那么咱们能够将其称为高阶函数。
咱们可使用高阶函数对一系列操做和值进行抽象。
// 使用高阶函数新建另外一些函数
function greaterThan(n) {
return function(m){
return m > n;
}
}
var greaterThan10 = greaterThan(10);
console.log(greaterThan10(11)); // true
// 使用高阶函数来修改其余函数
function noisy(f) {
return function(arg){
console.log("calling with",arg);
var val = f(arg);
console.log("called with",arg,"-got",val);
return val;
}
}
noisy(Boolean)(0);
// calling with 0
// called with 0 -got false
// 使用高阶函数来实现新的控制流
function unless(test,then) {
if(!test){
then();
}
}
function repeat(times,body){
for(var i = 0;i < times;i++){
body(i);
}
}
repeat(3,function(n){
unless(n % 2,function (){
console.log(n,"is even");
})
})
复制代码
咱们在前面定义的函数noisy会把参数传递给另外一个函数使用,这会致使一个至关严重的问题。
function noisy(f) {
return function(arg){
console.log("calling with",arg);
var val = f(arg);
console.log("called with",arg,"-got",val);
return val;
}
}
复制代码
若是函数f接受多个参数,那么该函数只能接受第一个参数。函数f没有办法知道调用者传递给noisy的参数个数。
JavaScript函数的apply方法能够解决这个问题。
function transparentWrapping(f){
return function(){
return f.apply(null,arguments);
}
}
复制代码
这里的transparentWrapping函数没有什么实际用处,但该函数返回的内部函数能够将用户指定的参数所有传递给f。
[
{
"name": "Emma de Milliano",
"sex": "f",
"born": 1876,
"died": 1956,
"father": "Petrus de Milliano",
"mother": "Sophia van Damme"
},
{
"name": "Carolus",
"sex": "m",
"born": 1832,
"died": 1905,
"father": "Carel Haverbeke",
"mother": "Maria van Brussel"
}
... and so on
]
复制代码
这种格式是JSON格式,即JavaScript Object Notation的缩写。该格式普遍用于数据存储和web通讯。
全部属性名都必须用双引号括起来,并且只能使用简单的数据表达式,不能填写函数调用、变量以及任何含有实际计算过程的代码。
JavaScript提供了JSON.stringify函数,用于将数据转换成该格式。还有JSON.parse函数,用于将该格式数据转换成原有的数据类型。
var string = JSON.stringify({ name: "X",born: 1980});
console.log(string); // {"name":"X","born":1980}
console.log(JSON.parse(string).born); // 1980
复制代码
function filter(array,test){
var passed = [];
for(var i = 0; i < array.length;i++){
if(test(array[i])){
passed.push(array[i]);
}
}
return passed;
}
console.log(filter(ancestry,function(person) {
return person.born > 1990 && person.born < 1925
}))
复制代码
该函数使用test函数做为参数来实现过滤操做。咱们对数组中的每一个元素调用test函数,并经过返回值来肯定当前元素是否知足条件。
与forEach同样,filter函数也是数组中提供的一个标准方法。本例中定义的函数只是用于展现内部实现原理。
console.log(ancestry.filter(function (person){
return person.father == "Carel Haverbeke";
}))
复制代码
map方法能够对数组中的每一个元素调用函数,而后利用返回值来构建一个新的数组,实现转换数组的操做。
function map(array,transform){
var mapped = [];
for(var i = 0;i < array.length;i++){
mapped.push(transform(array[i]);
}
return mapped;
}
var overNinety = ancestry.filter(function(person){
return person.died - person.born > 90;
})
console.log(map(overNinety,function(person){
return person.name;
}))
复制代码
与forEach和filter同样,map也是数组中的一个标准方法。
根据整个数组计算出一个值。
reduce函数包含三个参数:数组、执行合并操做的函数和初始值。
function reduce(array,combine,start) {
var current = start;
for(var i = 0;i < array.length;i++){
current = combine(current,array[i]);
}
return current;
}
console.log(reduce([1,2,3,4],function(a,b){
return a+b;
},0))
复制代码
数组中有一个标准的reduce方法,固然和咱们上面看到的那个函数一致,能够简化合并操做。
console.log(ancestry.reduce(function(min,cur){
if(cur.born < min.born){
return cur;
}else{
return min;
}
}))
复制代码
// 在不使用高阶函数的状况下,实现以上示例
var min = ancestry[0];
for(var i = 1;i < ancestry.length;i++){
var cur = ancestry[i];
if(cur.born < min.born){
min = cur;
}
}
console.log(min);
复制代码
这段代码中多了一些变量,虽然多了两行代码,但代码逻辑仍是很容易让人理解的。
当你遇到须要组合函数的状况时,高阶函数的价值就突显出来了。举个例子,编写一段代码,找出数据集中男人和女人的平均年龄。
function average(array) {
function plus(a,b){
return a+b;
}
return array.reduce(plus) / array.length;
}
function age(p){
return p.died -p.born;
}
function male(p){
return p.sex == "m";
}
function female(p){
return p.sex == "f";
}
console.log(average(ancestry.filter(male).map(age)));
console.log(average(ancestry.filter(female).map(age)));
复制代码
这段代码并无将逻辑放到整个循环体中,而是将逻辑巧妙地组合成了咱们所关注的几个方面:判断性别、计算年龄和计算平均数。
咱们能够采用这种方式编写出逻辑清晰的代码。不过,编写这样的代码也是有代价的。
将函数传递给forEach来处理数组迭代任务的确十分方便并且易于阅读。但JavaScript中函数调用却比简单的循环结构代价更高。
当程序执行很慢的时候,问题每每只是嵌套最深的循环体中的一小部分代码引发的。
// 构建一个对象,将祖辈的姓名与表示人的对象关联起来
var byName = {};
ancestry.forEach(function(person){
byName[person.name] = person;
})
console.log(byName["Philibert Haverbeke"]);
// 编写reduceAncestors函数,用于从家谱树中提炼出一个值。
function reduceAncestors(person,f,defaultValue){
function valueFor(person){
if(person == null){
return defaultValue;
}else{
return f(person,valueFor(byName[person.mother]),valueFor(byName[person.father]));
}
}
return valueFor(person);
}
function sharedDNA(person,formMother,formFather){
if(person.name == "Pauwels van Haverbeke"){
return 1;
}else{
return (formMother + formFather) / 2;
}
}
var ph = byName["Philibert Haverbeke"];
console.log(reduceAncestors(ph,sharedDNA,0) / 4);
// 找出知足特定条件的祖先比例,好比能够查找年龄超过70岁的人
function countAncestors(person,test){
function combine(person,formMother,formFather){
var thisOneCounts = test(person);
return fromMather + formFather + (thisOneCounts ? 1 : 0);
}
return reduceAncestors(person,combine,0);
}
function longLivingPercentage(person){
var all = countAncestors(person,function(person){
return true;
})
var longLiving = countAncestors(person,function(person){
return (person.died - person.born) >= 70;
})
return longLiving / all;
}
console.log(longLivingPercentage(byName(["Emile Haverbeke"]));
复制代码
每一个函数都有一个bind方法,该方法能够用来建立新的函数,称为绑定函数。
var theSet = ["Carel Haverbeke","Maria van Brussel","Donald Duke"];
function isInSet(set,person){
return set.indexOf(person.name) > -1;
}
console.log(ancestry.filter(function(person){
return isInset(theSet,person);
}))
console.log(ancestry.filter(isInSet.bind(null,theSet))); // same result
复制代码
调用bind会返回一个新的函数,该函数调用isInSet时会将theSet做为第一个参数,并将传递给该函数的剩余参数一块儿传递给isInSet。
将函数类型的值传递给其余函数不只十分有用,并且仍是JavaScript中一个重要的功能。咱们能够在编写函数的时候把某些特定的操做预留出来,并在真正的函数调用中将具体的操做做为函数传递进来,实现完整的计算过程。
数组中提供了不少实用的高阶函数,其中forEach用于遍历数组元素,实现某些特定的功能。filter用于过滤掉一些元素,构造一个新数组。map会构建一个新数组,并经过一个函数处理每一个元素,将处理结果放入新数组中。reduce则将数组元素最终概括成一个值。
函数对象有一个apply方法,咱们能够经过该方法调用函数,并使用数组来指定函数参数。另外还有一个bind方法,它用于建立一个新函数,并预先肯定其中一部分参数。
// 1.数组降维
// 结合使用reduce与concat方法,将输入的二维数组(数组的数组)中的元素提取出来,并存放到一个一维数组中
var arrays = [[1, 2, 3], [4, 5], [6]];
console.log(arrays.reduce(function(flat, current) {
return flat.concat(current);
}, []));
// 2.计算母子年龄差
function average(array) {
function plus(a, b) { return a + b; }
return array.reduce(plus) / array.length;
}
var byName = {};
ancestry.forEach(function(person) {
byName[person.name] = person;
});
var differences = ancestry.filter(function(person) {
return byName[person.mother] != null;
}).map(function(person) {
return person.born - byName[person.mother].born;
});
console.log(average(differences));
// 3.计算平均寿命
function average(array) {
function plus(a, b) { return a + b; }
return array.reduce(plus) / array.length;
}
function groupBy(array, groupOf) {
var groups = {};
array.forEach(function(element) {
var groupName = groupOf(element);
if (groupName in groups)
groups[groupName].push(element);
else
groups[groupName] = [element];
});
return groups;
}
var byCentury = groupBy(ancestry, function(person) {
return Math.ceil(person.died / 100);
});
for (var century in byCentury) {
var ages = byCentury[century].map(function(person) {
return person.died - person.born;
});
console.log(century + ": " + average(ages));
}
// 4.使用every和some方法
function every(array, predicate) {
for (var i = 0; i < array.length; i++) {
if (!predicate(array[i]))
return false;
}
return true;
}
function some(array, predicate) {
for (var i = 0; i < array.length; i++) {
if (predicate(array[i]))
return true;
}
return false;
}
console.log(every([NaN, NaN, NaN], isNaN));
// → true
console.log(every([NaN, NaN, 4], isNaN));
// → false
console.log(some([NaN, 3, 4], isNaN));
// → true
console.log(some([2, 3, 4], isNaN));
// → false
复制代码
方法只是引用了函数值的属性。如下是一个简单的方法:
var rabbit = {};
rabbit.speak = function(line){
console.log("The rabbit says '" + line + "'");
}
rabbit.speak("I'm alive."); // The rabbit says 'I'm alive.' 复制代码
方法一般会在对象被调用时执行一些操做。将函数做为对象的方法调用时,会找到对象中对应的属性并直接调用。在调用object.method()时,对象中的一个特殊变量this会指向当前方法所属的对象。
function speak(line){
console.log("The " + this.type + " rabbit says '" + line + "'");
}
var whiteRabbit = { type: "white",speak: speak };
var fatRabbit = { type: "fat",speak: speak };
whiteRabbit.speak("Oh my ears and whiskers, " + "how late it's getting!");
fatRabbit.speak("I could sure use a carrot right now.");
// The white rabbit says 'Oh my ears and whiskers, how late it's getting!' // The fat rabbit says 'I could sure use a carrot right now.' 复制代码
这段代码使用了关键字this来输出正在说话的兔子的种类。咱们回想一下apply和bind方法,这两个方法接受的第一个参数能够用来模拟对象中方法的调用。这两个方法会把第一个参数复制给this。
函数有一个call方法,相似于apply方法。该方法也能够用于函数调用,但该方法会像普通函数同样接受参数,咱们不须要将参数放到数组中。和apply和bind方法同样,你也能够向call方法传递一个特定的this值。
speak.apply(fatRabbit,["Burp!"]); // The fat rabbit says 'Burp!'
speak.call({type: "old"},"Oh my."); // The old rabbit says 'Oh my.'
复制代码
咱们来仔细看看如下这段代码。
var empty = {};
console.log(empty.toString); // ƒ toString() { [native code] }
console.log(empty.toString()); // [object Object]
复制代码
每一个对象除了拥有本身的属性外,几乎都包含一个原型(prototype)。原型是另外一个对象,是对象的一个属性来源。当开发人员访问一个对象不包含的属性时,就会从对象原型中搜索属性,接着是原型的原型,以此类推。
那么空对象的原型是什么呢?是Object.prototype,它是全部对象中原型的父原型。
console.log(Object.getPrototypeOf({}) == Object.prototype); // true
console.log(Object.getPrototypeOf(Object.prototype)); // null
复制代码
JavaScript对象原型的关系是一种树形结构,整个树形结构的根部就是Object.prototype。Object.prototype提供了一些能够在全部对象中使用的方法。好比说,toString方法能够将一个对象转换成其字符串表示形式。
许多对象并不直接将Object.prototype做为其原型,而会使用另外一个原型对象,用于提供对象本身的默认属性。函数继承自Function.prototype,而数组继承自Array.prototype。
console.log(Object.getPrototypeOf(isNaN) == Function.prototype); // true
console.log(Object.getPrototypeOf([]) == Array.prototype); // true
复制代码
对于这样的原型对象来讲,其自身也包含了一个原型对象,一般状况下是Object.prototype,因此说,这些原型对象能够间接提供toString这样的方法。
var protoRabbit = {
speak: function(line){
console.log("The " + this.type + " rabbit says '" + line +"'");
}
}
var killerRabbit = Object.create(protoRabbit);
killerRabbit.type = "killer";
killerRabbit.speak("SKREEEE!"); // The killerrabbit says 'SKREEEE!'
复制代码
原型对象protoRabbit是一个容器,用于包含全部兔子对象的公有属性。每一个独立的兔子对象(好比killerRabbit)能够包含其自身属性(好比本例中的type属性),也能够派生其原型对象中公有的属性。
在JavaScript中,调用函数以前添加一个关键字new则表示调用其构造函数。构造函数中包含了指向新对象的变量this,除非构造函数显式地返回了另外一个对象的值,不然构造函数会返回这个新建立的对象。
经过关键字new建立的对象称之为构造函数的实例。
这里给出一个简单的用于建立rabbit的构造函数。构造函数的名称通常以大写字母开头。
function Rabbit(type){
this.type = type;
}
var killerRabbit = new Rabbit("killer");
var blackRabbit = new Rabbit("black");
console.log(blackRabbit.type); // black
复制代码
对于构造函数来讲(实际上,对全部函数适用),都会自动得到一个名为prototype的属性。在默认状况下,该属性是一个普通的派生自Object.prototype的空对象。全部使用特定构造函数建立的对象都会将构造函数的prototype属性做为其原型。所以,咱们能够很容易地为全部使用Rabbit构造函数建立的对象添加speak方法。
Rabbit.prototype.speak = function(line){
console.log("The " + this.type + " rabbit says '" + line +"'");
}
blackRabbit.speak("Doom...");
复制代码
构造函数其实就是函数,所以其实际原型是Function.prototype。而构造函数的prototype属性则是其所建立的实例的原型,而非构造函数自身的原型。
当你向一个对象添加属性时,不管该属性是否已经存在于对象原型中,该属性都会被添加到这个对象中去,并做为对象本身的属使用。若是原型中存在同名属性,那么在调用该属性时,就不会再调用原型中的那个属性了,转而调用咱们添加到对象中的属性。但原型自己不会被修改。
Rbbit.prototype.teeth = "small";
console.log(killerRabbit.teeth); // small
killerRabbit.teeth = "long, sharp, and bloody";
console.log(blackRabbit.teeth); // small
console.log(killerRabbit.teeth); // long, sharp, and bloody
console.log(Rabbit.prototype.teeth); // small
复制代码
覆盖原型中存在的属性是颇有用的一个特性。
咱们也能够为标准函数和数组原型提供一个不一样于Object原型的toString方法。
console.log(Array.prototype.toString == Object.prototype.toString); // false
console.log([1,2].toString()); // 1,2
复制代码
直接使用数组调用Object.prototype.toString则会产生一个彻底不一样的字符串。
console.log(Object.prototype.toString.call([1,2])); // [object Array]
ps:检测对象类型的最佳方式
复制代码
咱们随时均可以使用原型对象添加新的属性和方法。
Rabbit.prototype.dance = function(){
console.log("The " + this.type + " rabbit dances a jig.");
}
killerRabbit.dance(); // The killer rabbit dances a jig.
复制代码
回顾第4章中的示例:
var map = {};
function storePhi(event,phi){
map[event] = phi;
}
storePhi("pizza",0.069);
storePhi("touched tree",-0.081);
复制代码
咱们可使用for/in循环遍历对象中全部的phi系数,并使用in操做符测试对象是否包含对应的属性。但不幸的是,这种方式会到对象的原型中寻找属性。
Object.prototype.nonsense = "hi";
for(var name in map){
console.log(name);
}
// pizza
// touched tree
// nonsense
console.log("nonsense" in map); // true
console.log("toString" in map); // true
复制代码
toString并无出如今for/in循环中,而使用in运算符测试时则返回true。这是由于JavaScript会区分“可枚举(enumerable)”与“不可枚举(nonenumerable)”属性。
咱们建立并赋予对象的全部属性都是可枚举的。而Object.prototype中的标准属性都不可枚举,所以这些标准属性不会出如今for/in循环中。
咱们可使用Object.defineProperty函数定义本身的不可枚举属性。
Object.defineProperty(Object.prototype,"hiddenNonsense",{
enumerable: false,
value: "hi"
})
for(var name in map){
console.log(name);
}
// pizza
// touched tree
console.log(map.hiddenNonsense); // hi
复制代码
常规的in运算符会认为Object.prototype中的属性存在于咱们的对象中。而对象的hasOwnProperty方法会告知咱们对象自身是否包含某个属性,而不会搜索其原型。
console.log(map.hasOwnProperty("toString")); // false
复制代码
当你担忧某些人(装载到你程序中的某些其余代码)会干扰基础对象的原型时,我建议这样使用for/in循环:
for(var name in map){
if(map.hasOwnProperty(name)){
// ... this is an own property
}
}
复制代码
咱们可使用Object.create函数并根据特定原型来建立对象。你能够传递null做为原型,并建立一个无原型对象。
var map = Object.create(null);
map["pizza"] = 0.069;
console.log("toString" in map); // false
console.log("pizza" in map); // true
复制代码
当编写一段代码时,咱们可使用包含特定接口的对象进行工做。在这个例子中是toString方法,只要对象支持这些接口,咱们就能够将这些对象插入代码中,并保证代码正常工做。
咱们将这种技术称为多态(polymorphism)。虽然在整个过程当中没有修改任何东西的形状,但咱们仍是这么称呼这种技术。咱们能够利用多态来操做不一样类型的值,只要这些值支持所需的接口便可。
咱们来经过一个稍微复杂的例子来深刻了解一下多态和面向对象的编程思想。咱们编写一个程序,将一个由表格单元格组成的二维数组转化成字符串,该字符串包含了与二维数组对应且布局规整的表格,要求每一列笔直整齐,每一行也要保证对齐。
首先,程序会计算每列的最小宽度和每行的最大高度,并保存到数组中。变量rows是一个二维数组,其中的每一个数组元素用来表示一个单元格组成的行。
function rowHeights(rows){
return rows.map(function(row){
return row.reduce(function(max,cell){
return Math.max(max,cell.minHeight());
},0);
});
}
function colWidths(rows){
return rows[0].map(function(_,i){
return rows.reduce(function(max,row){
return Math.max(max,row[i].minWidth());
},0);
});
}
复制代码
下面是绘制表格的代码:
function drawTable(rows) {
var heights = rowHeights(rows);
var widths = colWidths(rows);
function drawLine(blocks,lineNo){
return blocks.map(function (block){
return block[lineNo]
}).join(" ");
}
function drawRow(row,rowNum) {
var blocks = row.map(function(_,lineNo){
return drawLine(blocks,lineNo);
}).join("\n");
}
return rows.map(drawRow).join("\n");
}
复制代码
如今,咱们来编写用于建立文本单元格的构造函数,实现表格的单元格接口。
function repeat(string,times){
var result = "";
for(var i = 0; i < times; i++){
result += string;
}
return result;
}
function TextCell(text){
this.text = text.split("\n");
}
TextCell.prototype.minWidth = function (){
return this.text.reduce(function (width,line){
return Math.max(width,line.length);
},0)
}
TextCell.prototype.minHeight = function (){
return this.text.length;
}
TextCell.prototype.draw = function(width,height){
var result = [];
for(var i = 0;i < height;i++){
var line = this.text[i] || "";
result.push(line + repeat(" ",width - line.height));
}
return result;
}
复制代码
让咱们来使用编写好的程序来建立一个5*5的棋盘。
var rows = [];
for(var i = 0; i < 5; i++){
var row = [];
for(var j = 0; j < 5;j++){
if((j + i) % 2 == 0){
row.push(new TextCell("##"));
}else{
row.push(new TextCell(" "));
}
}
rows.push(row);
}
console.log(drawTable(rows));
复制代码
在对象中,get或set方法用于指定属性的读取函数和修改函数,读取或修改属性时会自动调用这些函数。
var pile = {
elements: ["eggshell","orange peel","worm"],
get height(){
return this.elements.length;
},
set height(value){
console.log("Ignoring attempt to set height to",value"); } } console.log(pile.height); // 3 pile.height = 100; // Ignoring attempt to set height to",value
复制代码
天天不定时更新~,欢迎批评指正~