JavaScript编程精粹_基础篇

Z1_基本知识

注释

变量

JavaScript的变量名必须以字母、下划线(_)或是美元符($)开头

如果没有使用关键字var声明变量,这种变量会成为隐式全局变量(implict global)

常量

类型

Number

big.js解决精度问题

为一个通用规则,不要在任何表达式中使用NaN

使用 + 号将字符串 "42" 自动转换成数字 42

使用 isNaN() 处理parseInt() 的结果
var underterminedValue = "elephant";
if (isNaN(parseInt(underterminedValue,2)))
    {
        console.log("handle not a number case");
    }
else
    {
console.log("handle number case");
    }

Boolean

false、0、空串( "" )、NaN、null和undefined都被视为false

其他的都被视为true

当使用 new 操作符和 Boolean(value) 构造函数时,得到的并不是原始的 true 或 false

而是一个对象,而且不幸的是,JavaScript将对象视为真(truthy)

String

字符串是一个Unicode字符序列(每个字符占16位)
转义字符 \
模板字符串(template string)
  • Object (Function,Aarry,Date,RegExp)
  • Undefined
  • Null
  • Symbol 符号

操作符

instanceof 操作符

+ 操作符

当用作单目操作符的时候, + 操作符不会对Number类型产生影响
如果应用在字符串类型上,会将其转换成数字

++ 和 -- 操作符

当 ++ 作为前缀的时候,如 ++a ,会先增加变量的值,
然后再将该值从表达式返回

链式赋值

var a, b, c;
a = b = c = 0;

var a = (b = 0);
// 这个例子中,使用var声明的变量只有 a ,变量 b 意外地成为了全局变量

布尔操作符

AND(&)、OR(|)和NOT(!)
三元操作符

for 循环

三个表达式都是可选的。如果需要的话,是可以忽略不写的

var x = 0;
//忽略初始化
for (; x < 5; x++) {
  console.log("Hello");
}

//忽略退出条件
for (var j = 0; ; j++) {
  //退出条件
  if (j >= 5) {
    break;
  } else {
    console.log("Hello");
  }
}

//忽略修改循环变量
for (var k = 0; k < 5; ) {
  console.log("Hello");
  k++;
}

//一种常用的惯用写法是使用空循环体
var arr = [10, 20, 30];
// 给所有的数组元素赋值100
for (i = 0; i < arr.length; arr[i++] = 100);
console.log(arr);

相等

坚持使用 === ,避免使用 ==

Z2_代码风格指南

空白字符

  • 绝对不要混用空格和制表符

括号、换行符和大括号

// 更好的写法:
var i,
  length = 100;
for (i = 0; i < length; i++) {
  // 语句
}
// 或者……
var i = 0,
  length = 100;
for (; i < length; i++) {
  // 语句
}

引号

切记不要在同一个项目中混用这两种引号,请选择并坚持一种风格

行尾和空行

类型检查

//String:
typeof variable === "string";
//Number:
typeof variable === "number";
//Boolean:
typeof variable === "boolean";
//Object:
typeof variable === "object";
//null:
variable === null;
//null或undefined:
variable == null;

类型转换

在语句的一开始就执行强制类型转换

// 不良
const totalScore = this.reviewScore + '';
// 优良
const totalScore = String(this.reviewScore);
对Number类型使用 parseInt() 的时候,总是加入基

条件求值

// 当数组长度不为空时,
// 不良写法:
if ( array.length > 0 ) ...
// 测试逻辑真(优良的写法):
if ( array.length ) ...

// 当数组长度为空时,
// 不良写法:
if ( array.length === 0 ) ...
// 测试逻辑真(优良的写法):
if ( !array.length ) ...

命名

对于对象、函数和实例,使用驼峰命名法

Z3_函数、闭包与模块

函数

函数的字面形式由四部分组成:
1.function 关键字 2.函数名 3.参数名列表 4.花括号中的函数体

函数声明

函数声明以关键字 function 开头,后面是函数名称,这种声明函数的方法也叫作函数语句(function statement)

函数表达式

创建了一个匿名函数并将其赋给一个变量,该变量随后可用于调用函数,但是无法递归调用这种函数

具名函数表达式

var facto = function factorial(n) {
  if (n <= 1) {
    return 1;
  }
  return n * factorial(n - 1);
};
console.log(facto(3)); //6

作用域

作用域指的是代码当前的上下文,一个变量的作用域就是该变量所在的上下文

全局作用域

第一种方法是在所有函数外部使用 var 语句;
第二种方法是在声明变量的时候忽略 var 语句(也称为隐式全局变量)

局部作用域

var scope_name = "Global";
function showScopeName() {
  // 局部变量,只能在本函数中访问
  var scope_name = "Local";
  console.log(scope_name); // 打印出Local
}
console.log(scope_name); // 打印出Global
showScopeName(); // 打印出Local

函数作用域

函数在作用域链中的位置与其出现在源代码中的位置是一致的,在解析一个变量时,JavaScript 从最内的作用域开始向外搜索

IIFE(立即执行的函数)

一些程序员在使用 IIFE 时会省去函数名。IIFE 的主要用法是引入函数作用域,其实并不需要
给函数命名。把之前的例子改写如下

var a = 1;
(function() {
  var a = 2;
  console.log(a); // 2
})();
console.log(a); // 1

(function() {
  /* 代码 */
})(); //一种形式略异的IIFE

行内函数表达式

行内函数表达式取一个名字,以便在调试代码时能够进行正确的栈跟踪

function setActiveTab(activeTabHandler, tab) {
  // 设置活动标签
  // 调用处理函数
  activeTabHandler();
}
setActiveTab(function() {
  console.log("Setting active tab");
}, 1);
// 打印出Setting active tab

块作用域

JavaScript 会把 var a = 1 划分成两个语句: var aa = 1 。第一个语句(也就是声明)是在编译阶段处理的,第二个语句(赋值)是在执行阶段处理的

a = 1;
var a;
console.log(a); //输出结果自然应该是undefined,但结果其实是 1

实际执行过程

var a; //----编译阶段
a = 1; //------执行阶段
console.log(a);

变量和函数声明在编译阶段被移到了代码的顶部——这就是常说的提升
(hoisting)。一定要记住,只有声明才会被提升,而赋值或其他可执行的逻辑依然保留在原位置

//函数 foo() 的声明会被提升,所以我们可以在其定义之前就调用该函数
foo();
function foo() {
  console.log(a); // undefined
  var a = 1;
}
//在函数 foo() 中,变量声明会被提升到函数内部的顶部,而不是整个程序的顶部

函数表达式并不会像函数声明那样被提升,下面来讲讲为什么会这样

函数声明与函数表达式

函数声明是在执行流程进入到该函数所处的上下文时处理的,这个处理过程在代码实际执行
前进行。对于带有函数名的函数(在上面的例子中是 functionTwo() ),其名称会被保存在函数声明所在的作用域中。该过程是在作用域中代码被执行前完成的,因此在定义前调用 functioTwo() 不会产生错误

函数声明只允许出现在程序或者函数体中,不能出现在块结构( {...} )中。块只能包含语
句,不能包含函数声明

var sayMoo;
if (true) {
  sayMoo = function() {
    return "trueMoo";
  };
} else {
  sayMoo = function() {
    return "falseMoo";
  };
}
foo();

arguments

arguments 参数并不是真正的数组,可以按照下面的方法将其转换成数组

var args = Array.prototype.slice.call(arguments);

this 参数

1.作为函数调用

如果函数不是以方法、构造函数或通过 apply() 、 call() 调用,那么它只是作为一个函数
被调用

//当函数以这种模式调用时, this 被绑定在全局对象上
function add() {}
add();
var substract = function() {};
substract();

2.作为方法调用

方法是作为对象属性的函数。对于方法来说, this 被绑定在方法被调用时所在的对象上:

3.作为构造函数调用

要以构造函数的方式调用函数,需要在函数调用前加上关键字 new 。这样的话, this 就被绑
定在新创建的对象上了

4.通过 apply() 和 call() 方法调用

JavaScript 函数也是对象。和其他对象一样,函数也具备方法。要想使用 apply()
方法调用函数,需要传入两个参数:作为函数上下文的对象以及作为调用参数的数组。 call()
方法的用法也差不多,除了调用参数不能作为数组传入,而是要直接写成参数列表的形式传入

匿名函数(重点)

匿名函数是 JavaScript 中重要的逻辑与结构构件

1.对象创建过程中的匿名函数

var santa = {
  //该属性值是一个匿名函数,这种情况下,这个属性不再叫作函数,而是被称为方法
  say: function() {
    console.log("ho ho ho");
  }
};
santa.say();

2.列表创建过程中的匿名函数
创建了两个匿名函数,并将它们添加到了一个数组里

var things = [
  function() {
    alert("ThingOne");
  },
  function() {
    alert("ThingTwo");
  }
];
for (var x = 0; x < things.length; x++) {
  things[x]();
}

3.作为函数参数的匿名函数

// 函数语句
function eventHandler(event) {
  event();
}

eventHandler(function() {
  // 执行事件相关的操作
  console.log("Event fired");
});
//匿名函数的语法要比两步走的函数声明(先声明函数,然后描述函数操作)更紧凑

4.出现在条件逻辑中的匿名函数

根据不同的条件为 shape 变量赋以不同的函数。善加利用的话,这种模式大有用处。如果不加节制,则会产生难以阅读及排错的代码

var shape;
if (shape_name === "SQUARE") {
  shape = function() {
    return "drawing square";
  };
} else {
  shape = function() {
    return "drawing square";
  };
}
alert(shape());

闭包(重点)

闭包是在函数声明时所创建的作用域,它使得函数能够访问并处理函数的外部变量

闭包可以让函数访问到在函数声明时处于作用域中的所有变量以及其他函数

当我们在 outerFn() 中声明 innerFn() 时,不仅定义了函数,还创建了一个闭包,其中包含了所声明的函数以及在声明时处于作用域中的所有变量。在执行 innerFn() 时,尽管声明该函数的作用域已经不在了,但是它仍能够通过闭包访问当初声明时所在的作用域。

var outer = "Outer"; // 全局变量
var copy;

function outerFn() {
  // 全局函数
  var inner = "Inner"; // 该变量只有函数作用域,无法从外部访问
  function innerFn() {
    // outerFn()中的innerFn()
    // 全局上下文和外围上下文都可以在这里使用,
    // 因此可以访问到outer和inner
    console.log(outer);
    console.log(inner);
  }
  copy = innerFn; // 保存innerFn()的引用
  // 因为copy是在全局上下文中声明的,所以在外部可以使用
}
outerFn();
copy(); // 不能直接调用innerFn(),但是可以通过在全局作用域中声明的变量来调用

计时器和回调函数

将内部的函数 timerFn() 传递给内建的库函数 setTimeout() 。但是 timerFn() 有一个覆盖了 delay() 作用域的作用域闭包,因此它能够访问到变量 message 。

function delay(message) {
  setTimeout(function timerFn() {
    console.log(message);
  }, 1000);
}
delay("Hello World");

私有变量

在 JavaScript 中无法使用 Java 或 C++中的类似封装方法,但通过利用闭包,我们也可以实现类似的效果

function privateTest() {
  var points = 0;
  //访问器:允许从 privateTest()外部只读取变量points的值
  this.getPoints = function() {
    return points;
  };
  //允许在不直接从外部访问的情况下修改私有变量points的值
  this.score = function() {
    points++;
  };
}
var private = new privateTest();
private.score();
console.log(private.points); // undefined
console.log(private.getPoints());

循环与闭包

//实际上, setTimeout 的回调函数是在循环结束后才执行的
for (var i = 0; i < 10; i++) {
  setTimeout(() => {
    console.log(i);
  }, i * 1000);
}

//我们可以引入一个函数作用域,并在该作用域中建立变量 i 的本地副本
for (var i = 0; i < 10; i++) {
  (function(x) {
    setTimeout(() => {
      console.log(x);
    }, x * 1000);
  })(i);
}
//将变量 i 作为参数传入,并将其复制到IIFE的局部变量 j 中。IIFE会针对每次迭代创建一新的作用域,使用正确的值来更新局部变量

模块

模块可用于模拟类,强调的是对变量和函数的公共及私有访问

模块有助于减少全局作用域污染

有效地使用模块能够降低大型代码基础库之间的名称冲突

var moduleName = function() {
  // 私有状态
  // 私有函数
  return {
    // 公共状态
    // 公共变量
  };
};

要求

  1. 必须有一个外围函数,至少需要执行一次
  2. 外围函数必须返回至少一个内部函数(inner function)。这需要创建一个涵盖了私有状态的闭包,否则无法访问私有状态
//创建了一个IIFE(具名函数也可以)作为外围(outer enclosure)
var appModule = (function() {
  var a, b;
  a = b = 1;
  function getA() {
    console.log(a);
  }
  function getB() {
    console.log(b);
  }
  return {
    getA: getA,
    getB: getB
  };
})();

appModule.getA();
appModule.getB();

Z4_数据结构和相关操作

正则表达式

正则表达式是一种表示匹配字符串的模式的方法。表达式本身是由项(term)和操作符(operator)组成的,它们让我们得以定义这些模式

创建

  1. 通过正则表达式字面量
  2. 构建RegExp对象创建
//正则表达式使用斜杠作为分隔符
var pattern = /test/;
//或者
var pattern = new RegExp("test");

标志

i, 可以使正则表达式忽略大小写
g 标志可以使正则表达式匹配模式的所有实例
m :可以使正则表达式跨多行(例如 textarea 元素的值)进行匹配

严格匹配

任何非正则表达式字符或操作符的字符序列,代表的都是该字符本身

//严格匹配有时候也叫作简化模式(simple pattern)
var parttern = /orange/;  //这里要表示的是 o 后面跟着 r , r 后面跟着 a , a 后面跟着 n ……

//在使用正则表达式的时候,我们很少采用严格匹配,因为这和直接比较两个字符串没什么分别

匹配字符串

var pattern = /[abc]/; // [abc] 表示 a 、 b 或 c 中任意一个字符
console.log(pattern.test('a')); //true
console.log(pattern.test('d')); //false

可以在模式开头加上一个 ^ (脱字符)来指定不想匹配到的字符

var pattern = /[^abc]/;
console.log(pattern.test('a')); //false
console.log(pattern.test('d')); //true

这种模式还有另一种很重要的用法是用来指明值的范围,如果想匹配字符或数字的某个连续范围,可以使用下面的模式

var pattern = /[0-5]/;
console.log(pattern.test(3)); //true
console.log(pattern.test(12345)); //true
console.log(pattern.test(9)); //false
console.log(pattern.test(6789)); //false
console.log(/[0123456789]/.test("This is year 2015")); //true

test() 方法可以根据模式是否匹配来返回true或false。但有时候需要访问特定模式所出现的位置,而 exec() 方法可以解决这个问题

var strToMatch = 'A Toyota! Race fast, safe car! A Toyota!';
var regExAt = /Toy/;
var arrMatches = regExAt.exec(strToMatch);
console.log(arrMatches);

/*
0: "Toy"
groups: undefined
index: 2
input: "A Toyota! Race fast, safe car! A Toyota!"
length: 1

*/

数组

创建

  1. 数组字面量语法
  2. 如果将单个数字值给Array() 构造函数或函数,JavaScript会将这个数字作为数组的长度
var arr = [10];

var arr = Array(10); // 创建一个没有实际元素的数组,将arr.length设为10
// 上面的代码等价于
var arr = [];
arr.length = 10;

JavaScript允许数组中包含各种类型的值

注意

  1. length 属性值要比存储在数组中最高的索引值还要大1
  2. 给 length 属性赋值。如果赋予的值小于数组元素个数,数组会被截断;赋值 0 的话,会清空整个数组
  3. 如果查询的数组索引不存在,会返回 undefined 。

迭代

  1. for循环
  2. forEach
//会针对数组中的每一个元素执行一次,在执行过程中,当前的数组元素会作为参数传入给该函数
var colors = ['red', 'green', 'blue'];
 colors.forEach(function(color) {
 console.log(color);
});

常见方法

let arr_one=[1,2,3,4];
let arr_two=[11,22];
let arr_thr=['a','b','c']
console.log(arr_two.concat(arr_one)) //[ 11, 22, 1, 2, 3, 4 ]
console.log(arr_one.join("~")) // 1~2~3~4

console.log(arr_one.pop())  //4   删除数组最后一个元素并将其返回 类似于栈的 pop() 方法
console.log(arr_one.length) //3
console.log(arr_one.push(5)); //4  在数组尾部添加一个或多个元素,并返回最终的数组长
console.log(arr_two.shift()) // 11 删除数组第一个元素并将其返回
console.log(arr_two.unshift(33,44,55,66)) //5   在数组的头部添加一个或多个元素,并返回数组新的长度
console.log(arr_thr.reverse()) //[ 'c', 'b', 'a' ]
console.log(arr_two.sort())  //[ 22, 33, 44, 55, 66 ]


// indexOf(searchElement[,fromIndex])
var a = ['a', 'b', 'a', 'b', 'a','c','a'];
console.log(a.indexOf('b')); // 1
console.log(a.indexOf('b', 2)); // 3 // 现在再从上一次匹配位置开始向后搜索
console.log(a.indexOf('1')); // -1, 'q' is not found

// lastIndexOf(searchElement[,formIndex])
var a = ['a', 'b', 'c', 'd', 'a', 'b'];
console.log(a.lastIndexOf('b')); // 5
// 现在再从上一次匹配位置开始向前搜索
console.log(a.lastIndexOf('b', 4)); // 1
console.log(a.lastIndexOf('z')); // -1

map类型

map是一个简单的键值映射,可以依据元素的插入顺序进行迭代


var founders = new Map();
founders.set("facebook", "mark");
founders.set("google", "larry");
founders.size; // 2
founders.get("twitter"); // undefined
founders.has("yahoo"); // false
console.log(founders) //Map { 'facebook' => 'mark', 'google' => 'larry' }

for (var [key, value] of founders) {
    console.log(key + " founded by " + value);
    // "facebook founded by mark"
    // "google founded by larry"
}

set类型

set是值的集合,可以依据其插入顺序进行迭代,其中的值只能出现一次

var mySet = new Set();
mySet.add(1);
mySet.add("Howdy");
mySet.add("foo");
mySet.has(1); // true
mySet.delete("foo");
mySet.size; // 2
for (let item of mySet) console.log(item);
// 1
// "Howdy"

重点

JavaScript的数组并非传统意义上的数组

length 属性
从 Array.prototype 处继承函数
对数字类型的键会做特殊处理

当我们将数组索引写成数字时,索引会被转换成字符串—— arr[0] 在内部会变成arr["0"] 。因此,在使用JavaScript数组时,有几件事要注意

  1. 通过索引访问数组元素并不像在C语言中那样是一种常数时间操作。因为数组实际上就是键值的映射,所以具体的访问要依赖于映射的分布(layout)及其他因素(冲突等)。
  2. JavaScript数组是稀疏的(大部分元素都有默认值),这意味着数组中可以存在间隙

你可能感兴趣的:(JavaScript编程精粹_基础篇)