读完这本书,自己做个整理,也许以后看到相关的知识点在没有书本在手边的时候,可以来稍微回顾下。
第1章 精华
JavaScript是一门重要的语言,因为它是唯一的一门所有浏览器都可以识别的的语言。
JavaScript的函数是(主要)基于词法作用域的顶级对象。
JavaScript有非常强大的对象字面量表示法。这种表示法是JSON的灵感来源,它现在已经成为流行的数据交换格式。
原型继承是JavaScript中一个有争议的特性。
本书贯彻始终都会用到的一个method方法去定义新方法,作者将会在第4章解释它。
代码如下:
Function.prototype.method = function(name, func) {
this.prototype[name] = func;
return this;
}
第2章 语法
主要有:空白、标识符、数字、字符串、语句、表达式、字面量、函数。
typeof 运算符产生的值有’number’, ‘string’,‘boolean’,‘undefined’,‘function’,‘object’。
如果运算数是一个数组或者是null,那么结果是’object’,这其实是不对的。
语句通常按照从上到下的顺序被执行;也可通过条件语句,循环语句,强制跳转语句和函数调用来改变执行顺序。
语句主要有以下几种:
- 条件语句: if…[else…] switch…case
- 循环语句: while… for… do…while
- 强制跳转语句: break , return , throw
1、if 语句根据表达式的值改变程序的流程,表达式的值为真时执行跟在其后的代码块,否则执行可选的else分支。
下列值都为假:false、null、undefined、空字符串 ’ ’ 、 数字0 、NaN;其余为真(包括true,字符串’false’,以及所有的对象)。
2、switch语句执行一个多路分支,它把其表达式和所有知道的case条件进行匹配,当找到一个精准匹配时,执行匹配的case从句中的语句,如果没有找到匹配,则执行default语句。
//当条件体不是一个固定数值时,用if...else更好做判断
var score = " ";
if(score>=85){
console.log("优秀")
}else if(score>=75){
console.log("良好")
}else if(score>=60){
console.log("及格")
}else{
console.log("继续努力!")
}
//当有比较多的条件体,且是具体的内容,用switch性能比较好
var color = "yellow";
switch( color ){
case 'red' :
document.body.style.backgroundColor = "red";
break;
case 'blue' :
document.body.style.backgroundColor = "blue";
break;
case "yellow" :
document.body.style.backgroundColor = "yellow";
break;
default:
document.body.style.backgroundColor = "#fff";
}
3、循环语句:
假设要求 1到10的和:
//for语句
var num = 1,
sum = 0;
for (var num = 1; num <= 10; num++) {
sum += num;
};
console.log(sum) // 55
//while语句
var num = 1,
sum = 0;
while(num <= 10){
sum += num;
num++;
}
console.log(sum) // 55
//do..while语句
var num = 1,
sum = 0;
do{
sum += num;
num++;
}while(num <= 10)
console.log(sum) //55
// 当第一次进入循环就不满足条件时,while循环不往下执行,但是do......while会执行一次。【将条件改为 num<=0 就能明显的看出差别 】
4、for…in 会枚举一个对象的所有属性名(或键名);它会继承来着原型链的值,所有需要利用 object.hasOwnProperty(prop) 可以检测这个属性名是否是源自于原型链还是对象
第3章 对象
JavaScript简单数据类型包括数字、字符串、布尔值,null值和undefined值。其他所有值都是对象。
数组、字符串和布尔值“貌似”对象,因为它们拥有方法(包装对象),但它们是不可变的。
对象是属性的容器,其中每个属性都拥有名字和值。属性名可以是包括空字符串在内的所有字符串,属性值可以是除了undefined值之外的任何值。
JavaScript包含一种原型链的特性,允许对象继承到另一个对象的属性。正确地使用它能减少对象初始化时的消耗的时间和内存。
检索:
检索对象里的值,可以用[]或. ,若果不存在返回undefined,||可用来填充默认值,尝试从undefined的成员属性中取值会导致TypeError异常,可以用&&避免错误;
更新:
如果属性名已经存在对象里。那么属性的值会被替换。如果之前没有拥有那个属性名,那么该属性将被扩充到对象中。
引用
对象通过引用来传递。它们永远不会被复制。
原型
所有通过对象字面量创建的对象都链接到Object.prototype;create方法创建一个使用原对象作为其原型的新对象。原型连接在更新时是不起作用的。
if (typeof Object.create !== 'function') {
Object.create = function(o) {
var F = function () {};
F.prototype = o;
return new F();
};
}
原型连接只有在检索值时才被用到。如果尝试去获取对象的某个属性值,但对象没有此属性名,那么JavaScript会试着从原型对象中获取属性值。如果那个原型对象也没有该属性,那么再从它的原型中寻找,依此类推,直到该过程最后达到终点Object.prototype。如果想要的属性完全不存在原型链中,那么结果就是 undefined值。这个过程称为委托。
原型关系是一种动态的关系。如若添加一个新的属性到原型中,该属性会立即对所有基于该原型创建的对象可见。
反射
原型链上的所有属性都会产生值。
有两种方法处理掉不需要的属性:
- 程序做检查并丢弃typeof值为函数的属性
- 使用hasOwnProperty方法,如果是对象拥有独有的属性,则返回true。该方法不会检查原型链。
枚举
- for…in可以遍历一个对象中所有的属性名。但包含函数和一些不关心的原型中属性。而且顺序不确定,可以用hasOwnProperty方法和typeof排除函数。
- for 循环不会出现for…in那些情况。
删除
delete用来删除对象的属性,不会触及原型链,删除对象的属性可能会让原型链中的属性透现出来。
减少全局变量的污染
最小化使用全局变量的方法之一是为你的应用只创建一个唯一的全局变量;只要把全局性的资源纳入一个名称空间之下,这样做能减少冲突。
第4章、函数
JavaScript中的函数就是对象。对象字面量产生的对象连接到Object.prototype;函数对象连接到Function.prototype(该原型对象本身连接到Object.prototype)。prototype的值是一个拥有constructor属性且值为该函数的对象。每个函数创建时会附加两个隐藏属性:函数的上下文和实现函数行为的代码。
函数字面量
函数字面量包括4个部分: ⑴保留字function; ⑵函数名,可以省略; ⑶一组参数; ⑷一组语句。
例子:var add=function(a,b){…}
多个参数用逗号分隔,参数的名称将被定义为函数中的变量,参数不会初始化为undefined而是在函数被调用时初始化为实际提供的参数的值。
函数字面量可以出现在任何允许表达式出现的地方。一个内部函数除了可以访问自己的参数和变量,同时也可以自由访问把它嵌套在其中的父函数的参数和变量。通过函数字面量创建的函数对象包含一个连接到外部上下文的连接。这被称为闭包。
调用
除了声明时定义的形式参数,每一个函数还接收两个附加的参数:this和argument。参数this在面向对象编程中非常重要,它的值取决于调用的模式。(通俗的理解起来就是谁调用,this就指向谁。)
在JavaScript中一共有四种调用模式。⑴方法调用模式; ⑵函数调用模式; ⑶构造器调用模式; ⑷apply调用模式。
⑴方法调用模式
//方法调用模式 (方法可以使用this访问自己所属的对象)
var myObject = {
value: 0,
increment: function(inc){
this.value += typeof inc ==='number'? inc :1;
}
}
myObject.increment();
console.log(myObject.value) // 1
⑵函数调用模式
//函数调用模式【 这种this被绑定到全局对象(window)。】
function add(num1,num2){
return num1+num2;
};
var sum = add(2,2);
console.log(sum); // 4
//函数调用时,this被绑定到全局对象,如果当内部函数被调用时,常用的方法是 var that = this;把this存储下。
myObject.double = function(){
var that = this;
var helper = function(){
that.value = add(that.value, that.value);
console.log('这里的this是window'+ this.value); // undefined
console.log(myObject.value); // 2
}
helper();
}
myObject.double();
⑶构造器调用模式
//构造器调用 (此种方法不推荐)
var Quo = function(string){
this.status = string;
}
Quo.prototype.get_status = function(){
return this.status;
}
//构造一个Quo实例
var myQuo = new Quo("confused")
console.log(myQuo.get_status() ) //confused
一个函数,如果创建的目的就是希望结合new 前缀来调用。那么它就被称为构造器函数。按照约定,它们保存在以大写函数命名的变量里。
⑷apply调用模式
// apply(thisobj, arguments) 该方法接受两个参数,第一个是要绑定给this的值,第二个就是一个参数数组或者arguments
// call(thisobj, arg1,arg2....) 与call方法差不多,但是call接收的是参数列表
var array=[3,4];
sum = add.apply(null,array)
console.log(sum) // 7
sum = add.call(null,5,5)
console.log(sum) // 10
返回
return 可用来是函数提前返回。当return 被执行时,函数立即返回而不再执行余下的语句。
一个函数总会返回一个值,如果没指定,那就是返回undefined值。
如果函数调用时在前面加上了new 前缀,且返回值不是一个对象,则返回this(该新对象)。
异常
JavaScript提供了一套异常处理机制。
throw语句和try…catch, try…catch中finally是可选的。
//方法异常抛出异常信息
var adds = function(a,b){
if(typeof a !== 'number' || typeof b !== 'number'){
throw{
name: 'TypeError',
message: 'add needs numbers'
};
return a+b;
}
}
var try_it = function(){
try{
adds('seven');
}catch(e){
console.log(e.name+': '+e.message); // TypeError: add needs numbers
}
}
try_it()
扩展类型的功能
JavaScript允许给语言的基本类型扩充功能。在第3章中我们已经看到,可以通过Object.prototype添加方法,可以让该方法对所有对象都可用。这样的方式对函数、数组、字符串、数字、正则表达式和布尔值同样适用。
Function.prototype.methods = function(name, func) {
if (!this.prototype[name]) { // 此处做个判断是为了避免类库中已有此方法,所以要先判断后再添加
this.prototype[name] = func;
}
return this;
}
例子:提取数字中的整数部分,增加 integer方法,根据数字的正负来判断
Number.method('integer',function(){
console.log(this) // Number {-3.3333333333333335}
return Math[this <0 ? 'ceil' : 'floor'](this); //加(this)什么原因?==> 相当于先用if判断用哪个方法,在return Math.ceil(number) / Math.floor(number)
})
console.log((-10/3).integer()) //-3
递归
递归函数就是会直接或者间接地调用自身的一种函数。
尾递归是一种在函数的最后执行调用语句的特殊形式的递归。
可参考
//阶乘 n! = n*(n-1)*...2*1
function fact(num){
if (num<=1){
return 1;
}else{
return num*fact(num-1);
}
}
fact(5); // 120
//尾递归的阶乘
var factorial = function factorial(i,a){
a = a|| 1;
if(i< 2){
return a;
}
console.log(i,a) // 5 1, 4 5, 3 20, 2 60
return factorial(i-1, a*i);
}
console.log(factorial(5)) // 120
作用域
在编程语言中,作用域控制着变量与参数的可见性和声明周期。
书中指出当前JavaScript没有块级作用域。因为没有块级作用域,所以最好的做法是在函数体的顶部声明函数中可能用到的所有变量。但是ES6扩展了有块级作用域。
闭包
作用域的好处是内部函数可以访问定义它们的外部函数的参数和变量(除了this和arguments)。
//例子:点击节点弹出节点序号
html代码
<ul class="demo">
<li>0</li>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
JS代码
var add_the_handlers = function(nodes){
var helper = function(i){
return function(e){
alert(i)
};
};
var i;
for (i = 0; i < nodes.length; i++) {
nodes[i].onclick = helper(i)
};
}
add_the_handlers(document.querySelector("#demo").children)
回调
网络上的同步请求会导致客户端进入假死状态,如果网络传输或服务器很慢,响应会慢到让人不可接受。
更好的方式是发起异步请求,提供一个当服务器响应到达时随即出发的回调函数。异步函数立即返回,这样客户端就不会被阻塞。
模块
我们可以使用函数和闭包来构造模块。模块是一个提供接口却隐藏状态与实现的函数或对象。
模块模式的一般形式是:一个定义了私有变量和函数的函数;利用闭包创建可以访问私有变量和函数的特权函数;最后返回这个特权函数,或者把它们保存到一个可以访问的地方。
使用模块模式就可以摒弃全局变量的使用。它促进了信息隐藏和其他优秀的设计实践。对于应用程序的封装,或者构造其他单例对象,模块模式非常有效。
例子:构造一个用来产生序列号的对象
//产生唯一序列号方法
var serial_maker = function(){
var prefix= "";
var seq = 0;
return {
set_prefix: function(p){
prefix = String(p);
},
set_seq: function(s){
seq = s;
},
gensym: function(){
var result = prefix + seq;
seq += 1;
return result;
}
};
};
var seqer = serial_maker();
seqer.set_prefix('Q');
seqer.set_seq(1000);
var unique = seqer.gensym();
console.log(unique); // Q1000
级联
有一些方法没有返回值。如果我们让这些方法返回this而不是undefined,就可以启用级联。
在一个级联中,我们可以在单独一条语句中依次调用同一个对象的很多方法。比如jQuery获取元素、操作样式、添加事件、添加动画等。 例子:$(’#div’).height().color().on(‘click’,function(){…})
柯里化
函数也是值。柯里化允许我们把函数与传递给它的参数相结合,产生一个新的函数。
//函数扩展柯里化方法
Function.method('curry', function(){
var slice = Array.prototype.slice,
args = slice.apply(arguments),
that = this;
return function(){
return that.apply(null,args.concat(slice.apply(arguments)))
};
})
var add = function(){
var sum = 0;
for(var i=0; i<arguments.length; i++){
sum += arguments[i];
}
return sum;
};
console.log(add.curry(1,2)(3,4)); //10
记忆
函数可以将先前操作的结果记录在某个对象里,从而避免无谓的重复运算。这种优化称作记忆。
//斐波那契数列
var fibonacci = function(){
var memo = [0,1]; // 存放计算后的数据
var fib = function(n){
var result = memo[n];
// 如果不存在被计算过的数据,则直接计算。然后在将计算结果缓存
if(typeof result !== 'number'){
result = fib(n-1) + fib(n-2);
memo[n] = result;
}
return result;
};
return fib;
}();
console.log(fibonacci(10)) // 55
for(var i=0; i<=10; i++){
console.log(i + ": " + fibonacci(i));
}
//创建一个具有记忆的函数
varmemoizer = function(memo, formula){
var recur = function(n){
var result = memo[n];
if(typeof result !=='number'){
result = formula(recur,n);
memo[n] = result;
};
return result;
};
return recur;
}
//通过记忆函数memoizer完成斐波那契数列
var fibonacci = memoizer([0,1],function(recur,n){
return recur(n-1) + recur(n-2);
})
console.log(fibonacci(10)) // 55
//通过记忆函数memoizer完成阶乘函数
var factorial = memoizer([1,1],function(recur,n){
return n * recur(n-1);
})
console.log(factorial(5)) //120