JavaScript高级程序设计(一~五章)——读书笔记

第一章:JavaScript简介

1、JavaScript组成:核心(ECMAScript)、文档对象模型(DOM)、浏览器对象模型(BOM)。ECMAScript是JavaScript的核心,它由语法、类型、语句、关键字、保留字、操作符、对象组成。

2、ECMAScript提供核心语言功能。DOM提供访问和操作网页内容的方法和接口。BOM提供与浏览器交互的方法和接口。

3.DOM包含DOM1,DOM2,DOM3。DOM1由两个模块构成DOM核心和DOM HTML;DOM2引入了DOM视图,DOM事件,DOM样式,DOM遍历和范围;DOM3新增了验证文档的方式,并引入了已统一的方式加载和保存文档的方法。

第二章:在HTML中使用JavaScript

第三章:基本概念

1、数据类型:
  • 基本数据类型:Undefined、Null、Boolean、Number、String
    • 基本类型值是指简单的数据段,5种基本类型是按值访问的,因为可以操作保存在变量中的实际值
    • 基本类型的值在内存中占据固定大小的空间,被保存在栈内存中。从一个变量向另一个变量复制基本类型的值,会创建这个值的一个副本。
  • 引用类型:Object

    • 引用类型值是指可以由多个值构成的对象。js不允许直接访问操作对象的内存空间。在操作对象时,实际上是在操作对象的引用而不是实际的对象。
    • 引用类型的值是对象,保存在堆内存中,包含引用类型值的变量实际上包含的并不是对象本身,而是一个指向该对象的指针。从一个变量向另一个变量复制引用类型的值,复制的其实是指针,因此两个变量最终都指向同一个对象。
    • 对于引用类型的值,可以为其添加属性和方法,也可以改变和删除其属性和方法

    String 类型:

    • 转换字符串toString():数值、布尔值、对象和字符串值都有toString() 方法,但null和undefined没有这个方法
    • 转换字符串String():在不知道要转换的值是不是null或undefined的情况下,可以使用转型函数String()

      • 如果值有toString()方法,则调用该方法并返回相应的结果
      • 如果值是null,则返回"null"
      • 如果值是undefined,则返回"undefined"

    Object类型:

    Object的每个实例都具有下列属性和方法:

    • constructor:保存着用于创建当前对象的函数
    • hasOwnProperty( propertyName ):用于检查给定的属性在当前对象实例中是否存在
    • isPrototypeOf( object ) :用于检查传入的对象是否是当前对象的原型
    • propretyIsEnumerable( propertyName ):用于检查给定的属性是否能够用for-in 来遍历
    • toLocaleString():返回对象的字符串表示,改字符串与执行环境的地区对应
    • toString():返回对象的字符串表示
    • valueOf:返回对象的字符串、数值或布尔值表示。通常与toString()方法的返回值相同
    • -

第四章:变量、作用域和内存问题

1. 基本类型和引用类型的值

1.1 ECMAScript变量可能包含两种不同类型的值:基本类型值和引用类型值,基本类型值指的是一个简单的数据段,五种基本数据类型Undifined,Null,Boolean,String,Number是按值访问的,因为可以操作保存在变量中的实际的值。引用类型的值是保存在内存中的对象,JS不允许直接访问内存中的位置,所以引用类型的值是按引用访问的。只有引用类型值可以动态地添加属性。

1.2 访问变量有按值和按引用两种方式,而参数只能按值传递。

1.3 检测类型,typeof,用于确定一个变量是字符串,数值,布尔值,对象还是Undefined的最佳工具。如果一个变量的值是Null或者是Object,则typeof都会返回“Object”。

1.4 instanceof用于确定一个值是哪种引用类型。若使用instanceof操作符检测基本类型值,则该操作符始终会返回false。

alert(person instanceof Object); 
alert(person instanceof Array); 
alert(person instanceof RegExp);
2、执行环境及作用域

函数的每次调用都有与之紧密相关的作用域和执行环境。从根本上来说,作用域是基于函数的,而执行环境是基于对象的(例如:全局执行环境即window对象)。
换句话说,作用域涉及到所被调用函数中的变量访问,并且不同的调用场景是不一样的。执行环境始终是this关键字的值,它是拥有当前所执行代码的对象的引用。每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中。虽然我们编写的代码无法访问这个对象,但解析器在处理数据时会在后台使用它。

执行环境

当JavaScript解释器初始化执行代码时,它首先默认进入全局执行环境,从此刻开始,函数的每次调用都会创建一个新的执行环境。
每个函数都有自己的执行环境。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中(execution stack)。在函数执行完后,栈将其环境弹出,把控制权返回给之前的执行环境。ECMAScript程序中的执行流正是由这个便利的机制控制着。
执行环境可以分为创建和执行两个阶段。在创建阶段,解析器首先会创建一个变量对象(variable object,也称为活动对象 activation object),它由定义在执行环境中的变量、函数声明、和参数组成。在这个阶段,作用域链会被初始化,this的值也会被最终确定。在执行阶段,代码被解释执行。

 function Fn1(){
        function Fn2(){
            alert(document.body.tagName);//BODY
            //other code...
        }
        Fn2();
    }
    Fn1();

当javascript代码被浏览器载入后,默认最先进入的是一个全局执行环境。当在全局执行环境中调用执行一个函数时,程序流就进入该被调用函数内,此时JS引擎就会为该函数创建一个新的执行环境,并且将其压入到执行环境堆栈的顶部。浏览器总是执行当前在堆栈顶部的执行环境,一旦执行完毕,该执行环境就会从堆栈顶部被弹出,然后,进入其下的执行环境执行代码。这样,堆栈中的执行环境就会被依次执行并且弹出堆栈,直到回到全局执行环境。
此外还要注意一下几点:

  • 单线程
  • 同步执行
  • 唯一的全局执行环境
  • 局部执行环境的个数没有限制
  • 每次某个函数被调用,就会有个新的局部执行环境为其创建,即使是多次调用的自身函数(即一个函数被调用多次,也会创建多个不同的局部执行环境)。

作用域

JS没有块级作用域,即在if和for语句中声明的变量,在其语句结束后不会销毁,会依旧存在于循环外部的执行环境中。

第五章:引用类型

引用类型的值(对象)是引用类型的一个实例。在ECMAScript中,引用类型是一个数据结构,用于将数据和功能组织在一起

5.1 Object类型

创建Object实例的方式有两种,第一种是使用new操作符后跟object构造函数:

var person = new Object;
    person.name = "Tony";
    person.age = "20"

另一种是使用对象字面量,这种方法简化创建包含大量属性的对象的过程

var person = {
    name : "Tony",
    age : 20
}

访问对象属性:点表示法和方括号表示法

console.log(person.name); // "Tony"
console.log(person["name"]); // "Tony"

从功能上来,这两种访问对象属性的方法没有任何区别。但方括号语法的主要优点是可以通过变量来访问属性,例如:

var propertyName = "name";
console.log(person[propertyName]); //"Tony"

注意:通常,除非必须使用变量来访问属性,否则我们建议使用点表示法

5.2 Array类型

创建数组基本方式有两种。第一种是使用Array构造函数,如下:

var colors = new Array();
var colors = Array()

第二种方式是数组字面量表示法:

var colors = ["red","blue","green"];
var names = [];

设置和读取数组的值时,要使用方括号并提供相应值的基于0的数字索引,如下所示:

var colors = ["red","blue","green"]; //定义一个字符串数组
console.log(colors[0]);//显示第一项
colors[2] = "black"; //修改第三项
colors[3] = "brown"; //新增第四项

5.2.1、检测数组

方法一: value instanceof Array
方法二: Array.isArray(value)

5.2.2、转换方法

toString(),valueOf(),前者返回由数组中的每个值的字符串形式拼接而成的一个已逗号分隔的字符串。而后者还是返回一个数组。

5.2.3、栈方法和队列方法

1、push()和pop()使用的是栈方法(后进先出);shift()和push(),pop()和unshift()使用的是队列方法(先进先出)。

2、 其中push()和unshift()是进入数组的方法,其中push()是从后面加入,unshift()是从前面加入。
比如说数组[“green”,”red”,”orange”],对于一个元素,若想加入“blue”,使用push(“blue”),结果是[“green”,”red”,”orange”,”blue”],使用unshift(“blue”),结果是
[“blue”,”green”,”red”,”orange”]

3、对于多个元素进入数组,push()和unshift()的结果一样,都是他们元素本来的排列顺序!使用push(“blue”,”purple”),结果是[“green”,”red”,”orange”,”blue”,”purple”],使用
unshift(“blue”,”purple”),结果是[“blue”,”purple”,”green”,”red”,”orange”]

4、push()和unshift()返回的是数组的长度,shift()和pop()返回的是弹出的元素,其中shift()返回的是前面的元素,pop()返回的是后面的元素,比如
[“green”,”red”,”orange”,”blue”],使用shift(),弹出的是“green”,使用pop(),弹出的是“blue”。

5.2.3、 重排序方法

数组中直接用来重排序的方法:reverse() 和 sort()
sort()方法会调用每个数组项的toString()转型方法,只能按字符串升序排序,如下:

var a = [0,1,5,10,15];
a.sort();  //[0,1,10,15,5];

很多情况下这种排序都不是最佳方案,因此sort()方法可以接收一个比较函数作为参数,如以下的简单比较函数:

function compare1(a,b){return a - b};//升序
function compare2(a,b){return b - a};//降序

将比较器传入sort()方法,a.sort(compare)

var a = [0,1,5,10,15];
a.sort(compare1);  //[0,1,5,10,15];
a.sort(compare2);  //[15,,10,5,1,0];

5.2.4、操作方法

1、拼接和选取:不直接修改原数组,而是返回新数组
拼接:
var newArr = arr1.concat(值1,值2,arr2,值3,…)
将值1,值2和arr2中每个元素,以及值3都拼接到arr1的元素之后,返回新数组
其中: arr2的元素会被先打散,再拼接

var arr1 = [4,8,9];
var arr2 = ["a","b","c"];
var newArr = arr1.concat("g",arr2,"y");
console.log(newArr);//[4, 8, 9, "g", "a", "b", "c", "y"]

选取:
var subArr=arr.slice(starti,endi+1)
选取arr中starti位置开始,到endi结束的所有元素组成新数组返回——原数组保持不变
强调: 凡是两个参数都是下标的函数,都有一个特性:
含头不含尾
简写:

  • 一直选取到结尾: 可省略第二个参数
  • 如果选取的元素离结尾近: 可用倒数下标:arr.slice(arr.length-n,arr.length-m+1)可简写为:arr.slice(-n,-m+1);
  • 复制数组: arr.slice(0,arr.length); 可简写为:arr.slice();

2、修改数组:splice直接修改原数组

删除: arr.splice(starti,n);

删除arr中starti位置开始的n个元素
不考虑含头不含尾
其实: var deletes=arr.splice(starti,n);
返回值deletes保存了被删除的元素组成的临时数组

插入: arr.splice(starti,0,值1,值2,…)

在arr中starti位置,插入新值1,值2,…
原starti位置的值及其之后的值被向后顺移
强调: 不支持打散数组类型参数

替换: 其实就是删除旧的,插入新的

arr.splice(starti,n,值1,值2,…)
先删除arr中starti位置的n个值,再在starti位置插入新值
强调: 删除的元素个数和插入的新元素个数不必一致。

5.2.5、位置方法
indexOf() 和 lastIndexOf()两个方法都接收两个参数:要查找的项和表示查找起点位置的索引(可选的)。其中,indexOf()方法从数组的开头开始向后查找,lastIndexOf()方法则从数组的末尾开始向前查找
找到返回查找的项在数组中的位置,找不到返回-1

5.2.6、迭代方法:

  1. every(): 对数组中的每一项运行给定的函数,如果该函数对每一项都返回true,则结果返回true。
  2. filter(): 对数组中的每一项运行给定函数,返回该函数会返回true的项组成的数组。
  3. forEach(): 对数组中的每一项运行给定函数,这个方法没有返回值。
  4. map(): 对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组。
  5. some(): 对数组中的每一项运行给定函数,如果该函数任意一项返回true,则返回true。

以上的方法都不会修改数组中包含的值。

在这些方法中,最相似的是every()和some()。他们都用于查询数组中的项是否满足某个条件。对every来说,传入的函数必须对数组中的每一项都返回true,这个方法才返回true;否则,他就返回false。在这里我们总结为有假则假,都真才真。而some()方法则是只要传入的函数对数组中的任何一项返回true,就返回true。请看下面例子:

var numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];

var everyResult = numbers.every(function(item, index, array) {
    return (item > 2);
});

var someResult = numbers.some(function(item) {
    return (item > 2);
});

console.log(everyResult); // false
console.log(someResult);  // true

filter()方法,利用指定的函数确定是否在返回的数组中包含某一项,就是说根据指定的函数来筛选符合条件的项组成新的数组并返回。例如,要返回一个所有值都大于2的数组,可以使用下面的代码:

var numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
var filterResult = numbers.filter(function(item) {
    return (item > 2);
});
console.log(filterResult);  // [3, 4, 5, 4, 3]

map()方法也返回一个数组,而这个数组的每一项都是在原始数组中的对应项上运行传入函数的结果。例如,可以给数组中的每一项乘以2,然后返回这些乘积组成的数组,如下所示:

var numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
var mapResult = numbers.map(function(item) {
    return (item * 2);
});
console.log(mapResult);     //[2, 4, 6, 8, 10, 8, 6, 4, 2]

以上代码返回的数组中包含给每个数乘以2之后的结果。这个方法适合创建包含的项与另一个数组一一对应的数组。
最后一个方法就是forEach()。它只是对数组中的每一项运行传入的函数。这个方法没有返回值,本质上与用for循环迭代数组一样,来看一个例子:

var numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
numbers.forEach(function(item, index, arr) {
    //这里执行一些操作
});

这些数组方法通过执行不同的操作,可以大大方便处理数组的任务。还是那句话,在自己的脚本里面写一些有脚本味道的代码,能够提高代码的质量和可读性,甚至减少代码量。支持这些迭代方法的浏览器有IE9+、FireFox 2+、Safari 3+、Opera 9.5+和chrome。

5.5、Function类型

函数实际也是对象,每个函数都是Function类型的实例。由于函数是对象,所以函数名实际上也是一个函数对象的指针

创建方式:

1、声明方式:function sum(num1,num2){
    return num1 + num2;
}
2、函数表达式:var sum = function(num1,num2){
    return num1 + num2;
}
3Function构造函数:var sum = new Function("num1","num2","return num1 + num2"); //不推荐

js解析器对函数声明和函数表达式解析不同,解析器会优先读取函数声明,执行函数表达式时,才被真正解析执行
构造函数这种创建方法会导致解析两次代码(第一次解析常规ECMAScript代码,第二次是解析传入构造函数中的字符串),从而影响性能

5.5.1 作为值的函数

因为ECMAScript中的函数名本身就是变量,所以函数也可以作为值来使用,也就是说,不仅可以像传递参数一样把一个函数传递给另一个函数,而且可以将一个函数作为另一个函数的结果返回。

function callSomeFunction(someFunction,someArgument){
    return someFunction(someArgument);
}
这个函数接收两个参数,第一个参数应该是一个函数,第二个参数应该是要传递一个值,然后就可以像下面的例子一样传递函数了
function add10(num){
    return num + 10;
}
var result = callSomeFunction(add10,10);
console.log(result); //20

要访问函数的指针而不执行函数的话,必须去掉函数后面的那对大括号,因此上面的例子中传递给callSomeFunction()的是 add10 ,而不是它们执行后的结果

当然,可以从一个函数中返回另一个函数

function createComparisonFunction(propertyName){
  return function(object1,object2){
    var value1=object1[propertyName];  
    var value2=object2[propertyName];  
    if(value1<value2){
      return -1;
    } else if(value1>value2){
      return 1;
    } else{
      return 0;
    }
  };  
}

var data = [{name:"Mady",age:20},{name:"Tony",age:19}];
data.sort(createComparisonFunction("name"))

5.5.2、函数内部属性 (不太懂)
在函数内部,有两个特殊的对象:arguments 和 this,arguments 是一个类数组对象,包含着传入函数的所有参数,arguments对象还有一个属性callee,该属性是一个指针,指向拥有这个arguments对象的函数请看下面这个非常经典的阶乘函数:

function factorial(num){
    if(num<=1){
        return 1;       
    }else{
        return num *  factorial(num - 1);
    }
}

定义阶乘函数一般都要用到递归算法,如上面的代码所示,在函数有名字,而且名字以后也不会变的情况下,这样定义没有问题。但问题是这个函数的执行与函数名factorial 紧紧耦合在了一起,为了消除这种紧密耦合的现象,可以像下面这样使用arguments.callee

function factorial(num){
    if(num <= 1){
        return 1;
    }else{
        return num * arguments.callee(num-1)
    }
}

在这个重写后的factorial()函数的函数体内,没有再引用函数名factorial。这样,无论引用函数时使用的是什么名字,都可以保证正常完成递归调用。例如:

var trueFactotial = factorial;
factorial = function(){
    return 0;
};
console.log(trueFactorial(5)); //120(5 * 4 * 3 * 2 * 1)
console.log(factorial (5)); //0

函数内部另一个特殊对象是 this, this 引用的是函数执行的环境对象 —— 或者也可以说是this值

window.color = "red";
var o = {color : "blue"};

function sayColor(){
    console.log(this.color); //this——window
}   
sayColor(); //red

o.sayColor = sayColor;
o.sayColor();  //blue this——o 

ECMAScript 5 也规范了另一个函数对象的属性: caller,这个属性中保存着调用当前函数的引用,如果是在全局作用域中调用当前函数,它的值为null。

function outer(){
    inner();
}
function inner(){
    alert(inner.caller);    
}
outer();

以上代码会导致警告框中显示outer()函数的源代码,因为outer()调用了inner(),所以inner.caller就指向outer(),为了实现更松散的耦合,也可以通过arguments.callee.caller来访问相同的信息

function outer(){
    inner();
}
function inner(){
    alert(arguments.callee.caller); 
}
outer();

5.5.3、函数的属性和方法
函数是对象,因此函数也有属性和方法,每个函数都包含两个属性length 和 prototype,其中length 属性表示函数希望接受的命名参数的个数

每个函数都包含两个非继承而来的的方法,apply() 和 call() 这两个方法的用途都是在特定的作用域中调用函数,实际上等于设置函数体内this对象的值
apply()方法接收两个参数:1、运行函数的作用域 2、参数数组(可以是Array的实例,也可以是arguments对象)

call()方法与apply()方法的作用相同,区别在于接收参数方式不同,第一个参数一样,第二个传递给函数的参数要一个一个列举出来

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值