Javascript知识整理

复习:
1.1. 数组
1.1.1. 为什么要用数组
相当于是批量定义变量。
并且这些变量是有规律,它们是线性的。有顺序的。通过数组的下标来访问。
1.1.1.1. 二维数组

数组的元素仍是一个数组。
1.1.1.2. 多维数组

  1. 错误与调试 (重点,掌握)
    2.1.1.1. syntaxError 语法错误
    致命的错误。一旦出现语法错误则整个代码块都不会被执行。

第9,10句没有执行。因为这个代码中有语法错误。
最容易处理的错误,大多数的可能性是你不小心写错了。
2.1.1.2. referenceError 引用错误
ref
href
这个错误是最常见的。

引用错误,大多数的可能性是你引用了一个没有定义的变量。
后果是:这个错误之前的代码可以正确运行,这个错误之后的代码不能运行。
解决方法:把变量先声明一下。
2.1.1.3. typeError 类型错误

错误原因是:把原本应该用到数组上的函数用到了一个基本数据类型上。 所以是类型错误。

解决错误:你要注意上下文,这个提示信息不一定准确。给变量或者是后面的函数更正一下。
后果是:这个错误之前的代码可以正确运行,这个错误之后的代码不能运行。

2.1.1.4. rangeError 范围错误

原因:可能是数组的长度给错了。
解决:找到这个长度的值,看一下。
后果是:这个错误之前的代码可以正确运行,这个错误之后的代码不能运行。
注意:不同的浏览器报的错误提示可能不同。

不要完全相信提示的内容;
找到关键字。
总结
(1) syntaxError :与其它三个不一样。一旦有语法错误则整个代码都不执行。而其它三个错误的影响要小一些,出现了,则之后的代码不执行。
(2) 注意收集错误。把错误的提示保存下来。不超过15个。
(3) 有正确的“错误观”
2.1.2. 逻辑错误(掌握)
浏览器不会报错,程序正常运行,但结果不是我们想要的。
2.1.2.1. 方法一 通过输出变量的方式去检查

2.1.2.2. 使用debugger语句 (必须要掌握)
Bug // 虫子
debugger把虫子挖出来的人
一个高大上的名字:调试器
它有另一个名字:断点。

Debugger;这个功能是浏览器给的。不是js自带的。作用是深入到代码内部,检查每一句代码的执行过程。理解一个高速的摄像头:可以回放整个过程。
你可以在任何的地方添加一句debugger;程序会在这个地方停下来,等你的进一步的指令。

2.1.2.3. 有四个按钮
1.进入到下一个debugger处

如果 没有下一个断点,则整个程序结束。

2.执行当前这句代码

3.进入函数的内部

从函数中跳出来
2.1.2.4. 监视变量的变化 watch面板

2.1.2.5. 格式化代码

在调试别人的代码时,特别是网络上的代码时,可以通过”格式化”让代码的可读性好一些。
www.kankan.com
2.2. 推荐使用chrome浏览器
http://note.youdao.com/share/?id=ad2239198d1517b708af79368163b8a3&type=note#/
2.2.1. 可以当一个计算器

2.2.2. Console.count() 计数
2.2.3. Console.time() console.timeEnd() 计时
3. javascript解析与执行(掌握)
3.1. 面试题

3.2. 与c语言的不同

JavaScript是一种描述型脚本语言,它不同于java或C#等编译性语言。

C语言源代码文件.c —> .exe文件 —> 双击执行

js源代码文件 —> 浏览器解析执行

 它不需要进行编译成中间语言,而是由浏览器进行动态地解析与执行。

 按代码块顺序进行。
3.3. 代码块
3.3.1. 定义

3.3.2. 特点 :独立

语法错误只影响当前的代码块。
3.3.2.1. 共享

3.4. 预编译
js源代码 --> 预编译 -->执行
js代码在执行之前,会有一个预编译(预处理)的过程。
预编译不会产生额外的文件
3.4.1. 小题目
Console.info(a) //undefined
Var a = 1;
Console.info(a) // 1
3.4.2. 预编译

  1. 扫描代码块,发现语法错误:停止一切工作,报错!
  2. 没有语法错误:做两件事:
    (1)变量提升。把所有的var变量,提升到代码块的开始(初始为undefined)
    (2)函数提升。解析定义式函数语句,提升到代码块的开始
    3.4.3. 使用debugger观察预编译

理解:debugger;语句在代码运行时起作用,而变量的提升是发生在运行之前。
3.5. 变量提升
3.5.1.1. 只提升加var的变量
只写Console.log (a); //会报错,is not defined

把var 修饰的变量提升到当前代码段的开始。

3.5.1.2. if结构中的变量提升

变量提升是在预编译阶段(在代码的运行之前进行了。)与具体的执行的结果无关。这里,虽然在代码执行时 var b = 11不会被执行时,但提升是发生执行之前。a,b都会被提升。
3.5.1.3. for循环中的变量提升

3.5.1.4. 函数内部的变量提升

函数可以划分作用域,在函数内部定义的变量不能被提升到函数的外部。

对比for的花括号

for循环只是一个语句,它不能划分作用域。所以,定义在循环内部的变量被提升到循环的外部。

注意:
不加var关键字的变量不会被提升!
3.6. 函数提升

函数是js中的“一等公民”,它只会提升。所以你可以先调用函数,后定义。

3.6.1. 函数表达式定义的函数不会提升

原因是:第22行中var 被提升了。当作一个普通的变量来提升,也就是说,只提升了函数名,没有提升函数内容。它被当作一个变量来对待,值是 undefined,给undefined加(),所以会报typeError错误。
3.7. 函数与变量同时提升

以函数提升为准。
3.8. 按代码块提升

3.9. 代码的执行顺序
3.9.1. 从上向下,按代码块的顺序依次执行

3.9.2. 事件会改变代码执行的顺序

3.10. 小结
 js代码在执行之前有一个预编译的过程。
 预编译主要的工作是提升:加var的变量 和 function
4. 数据的存储与赋值(掌握)
4.1. 复习数据类型
4.2. 判断数据的类型的方法 typeof

4.2.1. 判断是不是一个数组

4.2.2. jquery中的判断函数

4.2.3. jquery中的判断函数

4.3. 基本类型数据的存储与赋值操作
4.3.1. 存储

直接存在栈区。
4.3.2. 赋值

4.4. 引用类型数据的存储与赋值操作
4.4.1. 存储
第一步:在堆区保存数据
第二步:栈区保存变量名和数据在堆区中的地址。

4.4.2. 赋值

把a的值保存到b当中。也就是说b 中也保存了[1,2]在堆中的地址。所以通过b也可以访问[1,2] a,b的地址相同
所以 console.info( a,b ) 结果是一样的。就是这个数组

b[0] =10 .
把b指向这个数组的第一个元素改成10。
由于a,b 指向的是同个数组,所以,console.info(a,b) 结果都是[10,2]
4.4.3. 另一个例子1

由于b=[1];是一个赋值。把一个引用类型的数据保存变量b中。
第一步:先在堆区开辟一个空间把[1]放进去。
第二步:把这个空间的地址保存到栈区变量b的值中。
所以 ,b会指向[1]
到此,a,b指向完全不同的数组。
4.4.4. 另一个例子2

b = 1;
把一个基本的数据类型的数据保存在变量b中,直接保存值。
所以变量b中保存的值就是1,而不再是[1,2]的地址了。所以b与a再也没有关系了
4.5. 进一步理解==,===
4.5.1. 基本数据类型

在做 == 时,由于两边的数据类型不一样,会把字符串先转成数字,再进行比较。

在做 === 时,先判断类型,如果类型不一样,则是false; 类型是一样的再做==。
4.5.2. 引用数据

Var a = [1,2] 会在堆中占一区域,保存数据是1,2
Var b = [1,2] 也会在堆中占一个区域,恰好保存数据也是1,2.
实际上,a,b完全没有关系。在栈区中,a,b的值肯定是不样的。
== 实际上取变量在栈区的值进行比较,所以 a == b是false;
a == b false

对于引用类型来说,== 与 === 效果是一样的。 因为,类型本来就一样(引用类型)。

这个不同于上面, 它把a的地址给了b,故地址相同,指向堆中的数据也相同。
4.5.3. 引用类型的数据与基本类型的类型 ==

先把 引用类型 的数据 自动转成 基本数据类型,再与基本数据类型进行比较。

4.5.4. 总结
 对于基本数据类型而言,=结果不是一样的。
 对于引用数据类型而言,=结果是一样的。原因是引用数据类型只有一种类型object
4.5.5. 在==中的,把常量在左边

写程序时,最好把变量放到==的后面,好处在于,你不会犯 把 == 写在 =的这个错误。
4.6. 小例子
函数的存储和赋值类似于数组,其均为引用类型

  1. 变量
    5.1. var和不加var的区别
    Var a; 声明变量
    a; 遗漏声明
    5.1.1. 区别一:提升与不提升

5.1.2. 区别二:局部与全局变量
在函数的内部,加var关键字的是局部变量。

这里会报错。原因是:var a;是函数f的局部变量,不能在函数的外部被访问。
不加var的变量,相当于是一个全局的变量

出了函数f,还是可以被访问。
5.1.3. 区别三: 删除与不可删除

加了var关键字的变量是不能通过delete删除的。
不加var的变量可以删除。

delete用于删除对象的属性。
说明:不加var的变量,是作为window对象的一个属性存在的,所以可以删除。

5.1.4. 小结
使用变量一定要var;
在es的严格模式下,不加var就是一个错误;(大家在学es6时)
5.2. 变量的作用域 global
 全局的
 局部的

在js中的变量的作用域一定是与函数相关的。因为,只有函数能够划分作用域。

作用域:起作用的范围。
5.2.1. 全局作用域–普通话
一个变量它定义的地方,不属于任何一个函数,也就是说,它的定义不是在任意一个函数内部,这时,称变量为全局变量,这个变量拥有全局的作用域。
在函数外部定义的变量,在函数内部可以访问。
5.2.2. 局部作用域–方言
对于var 关键字定义的变量。在函数内部的定义的变量,只能在函数的内部使用,不能在外部使用。
5.3. 在js中不存在块级作用域 只有函数能够划分作用域
在c中的就用块级作用域。
如果是在c中
for(j = 0; j<10;J++){
Int i = 1; //这里的i就是一个局部变量
}
也就是块级作用域。

在js中,只有function才可以产生作用域。只有写在function中的变量,才是局部变量。

5.4. 在函数的内部,局部与全局同名

解释:
1.提升

2.就近的原则
在函数的内部,如果全局变量与局部变量同名,则先使用局部变量。

5.5. 练习 (一定要会)
5.5.1. 1

5.5.2. 2

5.5.3. 5

在第15行,b不是局部变量,所以在函数f的内部没有找到这个变量的定义,去全局栈中去找b。
第16行,更改全局变量的值。
第17行,悄悄地产生地一个全局变量。
5.6. 全局的变量的坏处(理解)
5.6.1. 会覆盖其他的函数或者是变量 – 污染

5.6.2. 在不同的代码段中使用相同的名称
会产生冲突,如下,都有变量a, 因此,后者起效

5.7. 产生额外的全局变量
5.7.1. 情况1:少写 var

f()调用时,sum就被产生了。它就是一个隐式的全局变量。
5.7.2. 情况2:连等赋值

由于b,c没有写var。所以它们也是隐式的全局变量。
5.8. 解决全局变量坏处的办法
5.8.1. 所有的变量都加var
5.8.2. 把变量的定义在写代码段(或者是函数)的最前面。
5.8.3. 采用单var定义多个变量

如jqury中,就是这样定义的:

5.8.4. 如果由于程序的需要一定要使用全局的话,则可以在代码的外面再套一层立即执行的函数表达式

  1. 函数参数与返回值
    6.1. 创建函数
    6.1.1. 函数声明
    function f(){}
    6.1.2. 函数表达式
    var f = function(){}
    6.1.3. 使用Function构造器(了解)

形参列表中,最后一个参数是函数体。前面的参数是这个函数的形参。
6.1.4. 小结
在具体的开发中:函数表达式的方式是用的最多。而构造器只是有理论的意义的,实际中不会用。
6.2. 参数类型
6.2.1. 形参
6.2.1.1. 如何理解形参

形参就是函数的局部变量。

通过debugger调试。

6.2.2. 实参
在函数的调用中,把值传给形参。一般理解,实参是一种变量的形式传递的。

6.3. 参数传递(重点掌握)

6.3.1. 形参的个数大于实参的个数

6.3.2. 形参的个数小于实参的个数

6.3.3. 形参的默认值(ES6)

注意:
设置函数的形参默认值时,从右向左设置()。
调用函数时,实参不能跳过去。实参必须是从左向右设置。

默认参数的好处是:
一看到的函数的定义就可以知道哪些参数是可以省略的。

这个说明第二,三个参数是可以省略的。

6.4. 基本数据类型参数传递
6.4.1.1. 1

第15行修改了全局变量的值。

6.4.1.2. 2.

输出的是:全局的a
f(a); //调用 函数

发生把实参的值传递给形参,而形参相当于函数的局部变量。也就是说,在f的内部定义了一个变量,变量名叫a,它的值就是实参的值。

在函数的内部 a = 3;
把3保存到a中,这里的a是函数局部变量(也就是在形参列表中定义的a)。所以你改是是函数的局部变量,与外面的全局变量没有关系。
所以console.info(a);访问的是全局变量,所以 是1 。

在14行,修改的a是全局变量,所以第18得到的结果是修改的3.
在15行,修改的b是函数的局部变量,出了f就不能访问,所以第19报引用错误。
6.5. 引用数据类型参数传递
6.5.1. 练习
6.5.1.1. 1

分析:

第二步,调用f(a).会进行参数值的传递。
从实参传递给形参
把实参的值(地址1)保存到形参(是f的形参,所以它与全局的变量保存在不同的栈中)的值中。
所以,结果是f-栈中的变量a也保存是 数组[1] 的地址,换句话说,f的局部变量也指向这个数组。

第三步:a[100] = 3;
把3保存到数组第101个元素中,由于a也指向这个数组[1],所以最后的结果 就是
Console.info(a),这个数组已经被修改了。
6.5.1.2. 2.

F(a):
做下面的事情:
实参–>形参
全局变量a —> 局部变量a
导致 局部变量 a 与全局变量a指向同一个数组[1]

a=[2]
是一个赋值语句,把右边的值存到左边。右边是一个引用数据类型的。
所以先要在堆区开辟空间,保存数据本身。注意,理解为在函数的堆区(不要与全局代码的堆区混在一起。)当函数调用完成后,这个空间就会被系统回收,里面的数据也会消失(为什么 局部变量在函数外部不能访问。)

此时,局部变量a保存是数组[2]的地址,此时,与全局变量a=[1]没有关系。

最终 console.info(a) 访问是全局变量a,结果 是[1].

6.5.1.3. 3

a[100] = 100;这里的a是局部变量(就是形参)

6.6. 传参的小结
 从实参到形参,传递是栈中的变量保存的值。
 对于基本类型的数据来说,这个值就是数据本身。
 对于引用类型来说,这个值就是地址(指向真实数据的地址。这个真实数据在堆里面。)。
 理解传值过程就是赋值
 js当中,实参到形参是按值传递的.
6.7. 函数的参数是函数( 理解 )

6.7.1. 复习setInterval与sort的用法

 定制排序的条件。
 把这个函数当作一个参数传递给另一个函数。

6.7.2. 示例:计算器

add理解是一个变量,这个变量有点特殊,它是保存的是一个函数。
所以在f(add,1,2) ---- > fun :add
X :1
Y :2
Fun(x,y) —> add(1,2)

把上面的函数表达式的定义改成函数声明式的定义。

6.8. 把函数当做参数传递的例子
6.8.1. 计算函数执行耗费的时间

特别地,可以使用 console.time() console.timeEnd()来计算花费的时间

6.8.2. 对数组进行排序,按 dx = 绝对值(数组元素的值-5)进行排序。

也可以直接把函数体写在实参的位置上。

6.9. 函数返回值
6.9.1. 每一个函数都有一个返回值,默认是undefined

上面效果说明: console.log( f() ); 是先执行函数,执行完函数后,返回函数值。
故:第一次输出 f ,第二次输出 undefined

如果一个函数没有明确的写return,则这个函数的返回值是undefined.

回顾:在什么时候会出现undefined:
 定义变量没有给初值,则是undefined
 定义数组没有给初值,则是undefined
 如果形参的个数大于实参的个数,则多出的部分值是undefined
 如果一个函数没有明确的写return,则这个函数的返回值是undefined.
6.9.2. 函数的返回值由return来指定

return会结束这个函数。也就是说,后面的代码不会执行。
6.9.3. return只能出现在函数内部

6.9.4. 返回多个数据 推荐通过对象来保存 (掌握)
在一个函数中,我们往往会需要计算多个信息,并且希望能够把这多个信息一起返回。

如上:我们把两个结果都放在一个数组中,通过不同的下标去访问。
缺点:用数组来保存结果,我们需要记住具体的信息是第几个,就是说这个下标是固定,并且也没有实际的意义。

可以说用对象来存数据

更进一步:

省略掉中间变量result:

6.10. js中没有函数的重载
重载:是指一种一系列函数,共用一个函数名的机制。
例如:有10个函数名字都是f, 但,它们的形参列表是不一样。
在c系列的语言中,认为这是10个单独的函数。
工作的原理是:根据参数的个数不一样,数据类型不一样,去自动选择函数。

但,在js中,只要函数名是一样,就会后定义的,把前面定义覆盖掉!

所以说:js没有函数的重载。

由于js没有重载的机制,所以如下问题就会变得很难。
我们定义一系列的函数,分别用于计算2,3,4,…个参数的和。由于名字不能取一样的,所以我们可能会需要定义多个函数。

6.11. arguments对象
arguments :参数
6.11.1. 认识(掌握)

1.arguments是每个函数中都有的一个对象。
2.与return类似的,它只能出现在函数的内部。

3.作用:调用函数时,arguments中会自动保存实参的数据。好像这里把形参完全跳过去。

6.11.2. 简单的应用:求均值,求和

6.11.3. arguments不是数组,是一个类数组对象

如何判断是不是一个数组:

还有一个类数组对象:

它们都不是数组,但可以用for循环。
6.11.4. arguments与形参的关系(理解)

如上,可以证明,形参列表与arguments之间有一一对应关系。改一个,另一个也跟着变化。

6.11.5. arguments与形参的关系(理解)

如上,可以证明,形参列表与arguments之间有一一对应关系。改一个,另一个也跟着变化。

如果形参列表与实参列表没有一一对应关系,则这种关联性就没有了。如下:
形参有3个,实参有2个。所以x,与arguments[0]; y与arguments[1]之间有一一对应的关联性,即修改一个,另一个也会会跟着变。
但是:
Z与arguments[2]没有关联性。修改z与修改arguments[2]是不会相互影响的。

6.11.6. 总结
(1)如果形参列表与实参列表有一一对应关系,则形参与arguments之间有一一对应的关联性。
(2)如果形参列表与实参列表没有一一对应关系,则形参与arguments之间没有对应的关联性。

(3)arguments对象在你不确定这个函数需要多少参数时,非常有用。参考jquery源码:

6.11.7. 讨论:是否完全可以用arguments对象来代替形参列表呢?
Function getMax (x,y){
Return x > y ? X: y;
}
Function getMax(){
Return arguments[0] > arguments[1] ? arguments[0] : arguments[1];
以上两种写法功能是一样的:返回两个数的最大值。
如果形参具有明确的意义,还要写清楚形参。很明显getMax(x,y)就要比getMax()表达意义更清楚些。

所以 ,这个问题结论是:不行!。
7. 执行上下文(理解)
7.1. 什么是执行上下文
理解一下代码:

(1)它是一个对象
(2)全局的代码块的执行会产生一个全局执行上下文。
(3)全局执行上下文只有一个。
(4)函数的每一次调用(执行一个函数)会产生一个函数的执行上下文。
(5)执行上下文提供代码运行时需的各种资源(例如:变量,函数…)。它相当于总管,函数要运行,需要访问变量,去哪里找变量呢?就是在这个函数的执行上下文中去找。
(6)通过debugger查看全局执行上下文

7.2. 全局执行上下文和函数执行上下文
对于全局执行上下文:
(1)js一旦开始运行,则首先全局执行上下文入栈

它只有一份,最先进来 ,最后出去。一旦它出栈,意味整个js代码执行完成。
(2)函数执行上下文
函数一旦被调用,则函数的执行上下文入栈,调用完成后就出栈。

7.3. 函数嵌套调用形成执行上下文栈(栈:stack)

f1 f2 f3依次进栈, 然后从f3 f2 f1 依次出栈
7.4. 执行上下文会提供代码运行时需要的各个数据的值
全局的执行上下文是:

它没有父级函数的概念。
A ,f1,f2是全局代码定义的变量。

函数的执行上下文的内容由两部分组成:

父级函数定义:函数里面再定义函数。如下:f1是f2是父级函数函数。
Function f1(){
Function f2(){
}
}
但是下面: f2()的父级就不是f1()
Function f2(){
}
Function f1(){
f2();
}
如何去找当前的函数在执行的过程中所需要的变量呢?
在自己的函数执行上下文中去找!
7.5. 为什么函数内部的变量,在函数的外部无法访问

在全局的代码中去找a,需要在全局执行上下文中去找,很明显,没有这个变量。

另外:在函数被调用时,它内部变量就会被创建出来,当这个调用结束后,这个函数的执行上下文就会 出栈 ,也就是说,它分配变量就会被 回收,所以你就访问不到这个变量了。
7.6. 过关练习(掌握,你要能够解释)
请你解释一下,这个结果的原因。
7.6.1. 1.

解释:
目标是在f1这个函数中去访问a。
按分析,应该在在f1的执行上下文中去找a.
F1的执行上下文有两部分:
(1)自己定义的部分。没有。
(2)父级的。就是全局的执行上下文。所以能够在全局的执行上下文中找到a。这里a=1.
2

解释:

注意:如果在执行上下文的第一部分找到了所要的变量,则不会再去第二部分去找。

js具备词法作用域的特点:
这个函数一旦定义(并不是调用)出来,则它能够访问的变量就已经定下来了。
8. 函数的嵌套定义(习惯,理解)
8.1. 基本格式
父级函数定义:函数里面再定义函数。如下:f1是f2是父级函数函数。
function f1(){
function f2(){

}
}
8.2. 作用域 “链”
js两条链:
 作用域链
帮助我们去找变量的值。

 原型链
帮助我们去找属性的值。

8.3. 示例

8.4. 把函数定义在函数内部的原因(掌握)

方法一:

缺点:
(1)你有四个函数名暴露在全局代码中,增加了被“污染”的风险。
(2)这个四个函数之间,没有什么任何的关联性。

方法二:把函数定义在函数的内部

 把自己的核心代码封装起来,只留一个入口 getC
 在函数的内部定义函数。这里通过函数表达式来定义,或者是函数声明来定义都可以。
 return 是通过对象来返回多个数据。
 第36行,result是一个对象。这个对象 当中有四个属性。
 Result.add 是一个函数。

 Result.add(2,3)就是在调用函数。
 更进一步简化:

8.5. 把函数当作返回值(掌握)

原理是:

8.6. 总结

函数当作返回值是一个非常常见的应用。它主要的好处在于:可以实现代码的封装。
8.7. 案例应用
目标:
给了一个数组,要对它排序,排序的依据是元素到某个数字之间的距离。

问题在于:
每个排序的方法,都需要我们去定义一个函数。

解决办法 :
通过一个函数接收一个参数,在这个函数的内部产生一个新的函数,把这个新的函数返回。

(1) 为什么 第31行可以访问number?原因是对于mysort函数来说,它的执行上下文中有两部分:
第一部分:形参 a,b
第二部分:父级函数getSort的执行上下文,这里就有形参number.
(2) 注意空间释放的问题:

如果了一个变量f0:
好处是:在第39行之后,你仍然可以使用f0.
坏处是:f0所指向的那个空间(这个空间有函数体 function(a,b){ return … (a-0) … (b-)} 这个函数体就不能释放)是全局的
(3)调试

进一步优化代码

  1. 立即执行的函数表达式IIFE(掌握)
    Immediately-Invoked Function Expression
    9.1. 需求
    你要执行一个函数:
    (1)这个函数只需要在当前的情况下调用一次。所以你没有必须给它取个名字。
    (2)这个函数要立即执行。
    9.2. 理解
    9.2.1. 立即执行

类似于[].sort()我们希望在function(){}直接加()。实现函数的调用,但是,不行!

9.2.2. 函数表达式

9.3. 为什么 function(){}()会出错?

Function(){} 它只有出现在=的右边时,才称为一个函数表达式。
只有它是一个函数表达式时,才可以加()。
这里 funtion(){}() 里面的function(){} 不是一个表达式,是一个语句。所以它的后面不能加().

改进:
把它变成一个函数表达式即可。
方法很多:
 +function(){console.info(“F”);}();//bootstrap.js
 -function(){console.info(“F”);}();
 !function(){console.info(“F”);}();
 ( function(){console.info(“F”);}() );
 (function(){console.info(“F”);}) ();
var xxxxx = function(){console.info(“F”);}();

 这里,function(){}()相当是执行这个函数表达式,由于没有明确指定return,所以默认是undefined
+取正,-取负 !取反 这三个就是单目(一元)运算符
加()的方式,利用了( )具有强制运算的能力,把function(){}变成一个函数表达式。
9.4. 三种实现方式(掌握)
 +function(){console.info(“F”);}(); //bootstrap.js
 ( function(){console.info(“F”);}() );
 (function(){console.info(“F”);}) ();
9.5. 带参数传递的立即执行的函数表达式(掌握)

匿名函数(没有名字,不需要名字)。

9.5.1. 小细节

出现错误的原因是:js在运行之前,代码被优化了一下,把第14行换行,及空格都去掉了,则导致第13行的()与第15的()合在一起,出现一个语法错误!

改正:人为加 ;

如果是单独在一个文件中写立即执行函数表达式,建议在前后都加 ; 如下:

9.6. 作用:模拟块级作用域 不污染全局变量

这样写代码会有可能造成变量的污染。 改进如下:

把你的代码写在 ( function(){ // 你的代码} ) ()
好处:
(1)函数是立即执行的,与你自己直接写代码运行没有区别。
(2)这个匿名的函数没有名字,没有被别人污染的可能性。
(3)由于函数有划分作用域的能力,
所以,它们各自是独立的:
一个立即执行的函数表达式中定义的变量就是这个匿名函数的局部变量。
10. 闭包 closure(理解 加分项)
10.1. 闭包定义closure

定义:

 在函数f的内部又定义了函数f1
 并且f1还使用了f的局部变量
说f1是一个闭包,或者说,这里形成一个闭包的结构。

10.2. 延长变量的生命周期
改进一下代码:

说明:
(1)var a = 0 ;变量a是函数f()的局部变量
(2)F1是定义 在f内部的函数,所以它能够访问a。
(3)第24行结束后,相当于是f调用完成。
(4)第28-31行,我们能还可以通过r 访问a这个变量。
这个例子当中 ,但f被调用完成后,其中变量a仍然可以被访问,换句话,f的局部变量在结束f的调用后,没有被回收!

对于变量a而言,正常情况下:因为这是一个局部变理,它只能在存在于第12-22行。
这个例子中,我们通过一个闭包结构,让它的生命周期延长了,现在在f()的外部也可以访问a。当然,这个访问是间接的,不能直接console.info(a)。

10.3. 通过debugger查看

10.4. 图示

10.5. 例子

(1)为什么是5?
Lis.length = 5;
对于for循环,当i = 5时,结束循环。所以这个值是5.
(2)为什么总是5?
当整个for循环结束后,用户才可能去通过点击鼠标去触发onclick事件。
换句话说:for执行在前面;然后才是事件响应函数执行。

当for循环完成后,这个i就是5 。所以你每次点击都是5.

10.5.1. 解决方法 1

10.5.2. 解决方法 2

(1)闭包:function(){console.info(index)} 就一个闭包。满足:
a.它定义在立即执行的函数表示式内部。相当于在函数的内部定义了一个函数。
b.它用到了父级函数的局部变量index.
(2)带参数的立即执行函数表达式。
形参是index.实参是i.
把i的值传递到index中。

调试

10.6. 缺点:闭包会产生内存泄漏

第24行结束后。

全局代码:我调用了f()把结果保存在r中,我准备要求函数f把它自己定义的变量a,f1删除。

r说:不行!你不能不删除!。我要用f1.
全局代码: 我让f把a给删除了?
r说:不行,因为f1要用a!
全局代码:我不管了。反正f我已经调用完了。我下班了。
第28行:
r()执行完后,我没有创建任何变量, 我不能做回收的工作,我也下班了。

。。。。。。。

a,f1没有人管了。它们会一直存在,直到这个html文件关闭。

10.7. 总结
你要正确地使用闭包:
(1)可以不用就尽量不用。
(2)你一定要去理解它的原理,能够正确的写出闭包结构。
10.8. 问题
10.8.1. f()()与r()的区别

F()() :先执行f()。Var a = 0 ,然后再执行f1(),a = a+1。所以结果a = 1;
F()() : 先执行f()。Var a = 0 ,然后再执行f1(),a = a+1。所以结果a = 1;
11. 递归函数(理解)

三个盘子在 A上。目标是把它们移动到C上。
要求:
(1)一次只能移动一个盘子。
(2)在移动的过程,必须要保证小盘子在上面,大盘子在下面。
第一步: 从

过关的考核:汉诺塔。
递归的实际应用:
 无限级分类
 目录遍历

11.1. 定义
在函数的内部调用自己。(自己调用自己)

进入死循环,原因是没有结束条件。

11.2. 例子:求累加
11.2.1. 用循环来求

11.2.2. 用递归
Sum(n) : 表示从1-n的和
Sum(n-1): 表示从1-n-1的和

求1-10的和。
我想:
让一个名为p9的人, 给我算1+…+9 假设这个值是s(9) ,那么我就算 s(9) + 10;

P9就想:
让一个名为p8的人, 给我算1+…+8 假设这个值是s(8) ,那么我就算 s(8) + 9;
P8就想:
让一个名为p7的人, 给我算1+…+7 假设这个值是s(7) ,那么我就算 s(7) + 8;


P2就想:
让一个名为p1的人, 给我算1+…+1 假设这个值是s(1) ,那么我就算 s(1) + 2;

去哪里找p1 呢,后来想,不用找,直接 s(1) = 1.

在else分支中Sum()里面又调用了sum()
没有死循环的原因是: if()分支中是没有调用sum.
11.3. 递归的思想
前提:
 问题的性质是一样的。它会有一个类似于公式的东西。
 问题可以把规模慢慢变小。
 问题的规模足够小之后,就可以直接给出答案。
思想:
递:递近。把问题的规模不断变小,慢慢靠近那个最小问题的答案
归:回归。把小问题的答案向上汇总,最后,得到大问题的答案。
11.4. 以sum(4)为例,分析

11.5. 理解:递
递进:把大问题分成小问题,一层一层向我已经知道的那个答案靠近。

11.6. 理解:归
回归:小问题都已经得到了解决,现在从最小的问题向最大的问题回归,一个个解决。

11.7. 求多维数组中元素的个数

总结:
 画流程图
 调试
 尝试实现:
 汉诺塔 【一个函数,三行代码】
 菲波那锲数列f(n) = f(n-1) + f(n-2).当前项是前两项的和。已知前两项是1 , 1
1,1,2,3,5,8,13,21,34,55,89…
求f(1000)是?

JS面向对象
11.8. js中的对象(object)(掌握)
ECMA-262 规定:

对象是“属性 的 无序 集合,每个属性存放一个原始值,对象或函数。”。
每一个属性的值是基本数据类型,或者是引用数据类型。
11.8.1. 属性
Console.info( console); //要把console这个值打印出来。
console是一个对象。

在这个对象中有很多个属性;如assert,clear,count…
属性有两个部分:
属性名:属性值。
以info为例:
这个属性的名字是info.
这个属性的值是一个函数:

11.8.2. 集合
Var arr = [102,2,3,4,5];
理解:数组一次性定义了多个变量。Arr[0], …,arr[length-1].这length个变量合在一起,就是一个集合。

11.8.3. 无序
对比数组理解:
Var arr = [102,2,3,4,5];有5个变量,组成一个整体。 Arr[0] ,arr[3]之间是有一个顺序的关系。
For(i=0;i<5;I++){

}
数组是有序的:arr[0]肯定在arr[1]的前面。
对象是无序的:

这里定义了一个对象obj,这个对象有三个属性,c,a,d是属性的名字,但它们在内存中保存时,是没有顺序的:并不是说我找到了c这个属性,则a属性就一定在它的后面。
11.9. js中的对象的组成:键值对(掌握)
对象中只有一类东西:就是属性。除了属性,对象中什么都没有!

属性就是键值对。

以下为例:

(1)通过var的方式定义了一个变量。
(2)变量的名字是obj.
(3)变量中保存是一个引用数据类型。具体来是说是一个对象。
(4)这个对象中有3个属性,即3个键值对。
“c” : “属性c的值”
它的键名是c ,键值是一个字符串“属性c的值” 。名与值用:隔开。 称为一个键值对。
11.9.1. 键名(属性的名字)是灵活的
一般来说,这个名字可以参考变量的命名规范。但也可以取一些奇怪的名字:

理解:
(1)由于名字以是字符串形式保存的,所以可以取任意的字符串。
当键名是符合变量命名规范时,其双引号是可以省略的。反之,如这个名字不符合,则不能省略。如”1+1”就不能省.而a就可以省。

11.9.2. 键值(属性的值)是任意的数据类型
对照数组进行理解:

数组的元素的值可以是任意的数据类型。
同理:属性的值可以任意的数据类型。

一般讲,我们称为属性,不讲键值对的概念。
11.10. 面向对象OO(理解)Object oriented.
用面向对象的程序设计的思想来解决一个什么问题。

面向过程编程不好,在解决一些问题时,有明显的缺点,所以发明面向对象的编程思想。

11.10.1. 解决这个问题:把大象放进冰箱.

11.10.2. 面向过程的解决方法
核心思想是分步骤:
第一步:打开冰箱门;
第二步:把大象放进去;
第三步:关上冰箱门;

11.10.3. 面向对象的解决方法(掌握)
思考1:这个问题当中有几个对象?
至少有三个对象:
 冰箱
 大象
 人
思考2:每个对象都有什么能力(做什么具体的工作,提供什么具体方法)?
 冰箱
 属性:门(门的大小,对开,单开…)
 方法:开门(),关门()
 大象
 属性:体积,重量,性别
 方法:走路(),叫喊()
 人
 属性:性别,身高…
 方法:开冰箱门(冰箱);牵大象(大象)

思考3:实现
人.开冰箱(冰箱){冰箱.开门()}
人.牵大象(大象)
大象.走路(冰箱)
人.关冰箱(冰箱){冰箱.关门()}
11.10.4. 扩展&&问题
问题1:冰箱大小,放不下大象?

这个问题是属于哪一个对象的问题?如果是冰箱,就让冰箱这个对象去解决;如果是大象有问题,就让大象这个对象去解决。

问题2:大象不进去?

问题出在八.牵大象()方法。所以你要去改进个方法。
11.10.5. 面向对象的优点
1.更接近于人的思想;(分工)。
2.对象与对象之间关联性低,在修改程序时,比如,有10个对象,而你只需要要修改其中某一个,其它的不受影响。
3.高级的特性:继承,多态,封装…

11.11. 面向对象的程序设计语言OOP
object oriented programming

这个世界有很多的编程语言,有一些是面向对象的,有一些不是。例如:常见的面向对象的程序设计语言:
 Php
 Java
 C#,C++

有一些不是面向对象的:
 c
 javascript
11.11.1. 如何判断一个语言是不是面向对象的?是否具备三个特点:
 封装。打包。理解:函数的局部变量在函数的外面不能访问。
 继承。分单继承,多继承。
 多态。同一个方法名,不同的子类调用时,会有不同的表现。
人.吃饭();
monkey.吃饭();

js不是面向对象的语言,但是,它能够通过其它的手段实现上面的三个特点,所以我们说:js基于面向对象的.

js中没有类的概念,它的底层实现不是面向对象。Es6提供class,只是说,js这个语言,也是一个产品,它的父亲会希望更多的人去使用js,所以他会不断加强js的能力,让程序员用起来更方便。例如:在es6之前是没有一个现成函数可以判断一个数据是不是数组,所以加一个Array.isArray();加class只是说我可以让js体现面向对象的思想更接近传统的面向对象语言,让我们写代码更容易一些,而不是说,js就真的是面向对象的。
11.11.1.1. 类是对象的抽象。
这四个对象 : 人,monkey,dog,cat 有什么共同点? 找共同点的过程就是抽象过程。

Class {
//属性
眼睛的数量:2;
嘴巴:1;
牙齿:都有,但数量不一样,强度不一样;

//动作

唱歌();
吃饭();

}

11.11.1.2. 对象是类的实例。
实例就是把上面的属性和方法都细化。每一个对象的属性和方法都可能不一样。

王菲{
//属性
眼睛的数量:2;
嘴巴:1;
牙齿:两排,不够硬;

//动作

唱歌(){会唱很多};
吃饭(){ 只能吃很少};

类:是工厂里面生产玩具的模具
对象:用模具加工的具体的产品

11.12. js中对象分类
11.12.1. 在js中,对象分为如下三类:内置对象
11.12.2. 内置对象
“由ECMA实现的,不依赖于宿主环境的对象,这些对象在js程序执行之前就已经存在了”
 本地对象:Object,Array,String,Number,Boolean,Date,Error…
 单体内置对象:只有两个Global,Math。与本地对象的区别在于:不加new 。

Constructor:构造器.这个错误说明:Math不能这么用。只能直接使用。Math是独一份的对象,不可能有多个math对象(参考这个名词:静态类)。

11.12.2.1. 程序执行之前就已经存在

11.12.2.2. 理解“宿主”
js只有两个宿主
 浏览器 (客户端的js)
 Node.js(服务器端的js)

js代码不能单独运行,我们现在是通过浏览器来运算js代码的,所以说,js代码寄宿浏览器中的。这时,浏览器是就是js代码的宿主。

还有谁js可以充当“宿主”的角色? Node.js

要运行js代码,要么是在浏览器端运行,要么是在node.js环境中运行。js代码不能独立运行(c语言的代码,在编译后会得到一个.exe文件,它就可以直接运行)

11.12.3. 宿主对象
宿主就是js寄宿环境定义的对象。在浏览器端的js而言,宿主对象就是浏览器对象。例如:window,document,history,bom等。
换句话说,如果在node.js的环境中,上面这个些都不存在的。

不同厂家的浏览器提供的支持可能不也不一样。
 标准浏览器提供的:addEventListenr() ; getComputedStyle()

 ie浏览器提供:attachEvent(); getStyles()
11.12.4. 自定义的对象

11.13. Global对象
global对象是ECMAScript中最特别的一个对象:
 是js程序的运行总管。
 全局变量,全局函数,其实就是这个对象的属性。
 其它的本地对象其实也是它的属性。

在浏览器: 它一直存在,但没有人见过!它按排一个代表,由这个代表来执行它的功能。这个代表就是window对象。它是global 的代言人。

在node.js中:它是存在的。
11.14. window对象(掌握)
11.14.1. 你添加的全局变量和全局函数都会变成window对象的属性

11.14.2. 你调用的方法、对象等都是window的自有属性

Array就是window对象的一个属性。
11.15. 三条定律
一切都是对象;

对象是属性的集合;

对象是由 函数 创造的;
12. 一切都是对象
12.1. 如何判断是否是对象
12.1.1. Typeof
12.1.2. Instanceof (掌握)
Instanceof

功能:判断一个对象是不是一个构造器(或者也称类)的实例
格式:要判断的目标 instanceof 构造器
返回值:true , false
如下:

Obj 是 Object构造器的实例。
基本数据类型a就不是Object构造器的实例 。
12.2. html标签是对象(掌握)

12.3. window是对象
12.4. document 是对象
为什么 可以使用document.getElementByid()?
因为document是一个对象,而getElementById是它的属性(更确切来讲,是它的原型属性)

12.5. Console 是对象

12.6. 基本数据类型也当作对象处理

基本数据类型:
 Number
 String
 Bool
 Undefined
 null
12.6.1. 为什么可以使用toString方法。
Var a = 10;
Console.info(a.toString());
12.6.2. 为什么str可以调用toUpperCase()方法?
Var str=”abcd”;
str.toUpperCase();

12.6.3. 包装对象来解释这个问题

第12: a.toString(); 从结构上看,看起来a是一个对象 ,toString是一个属性。

包装对象:把基本的数据类型悄悄地,短暂地包装一下,让它具备对象的特性。
以str为例:
第14定义一个基本数据类型,把值保存在str中。
第15行 str.toUpperCase 在执行一句时,实际等价于:

类似的,
对于其它类型的基本数据类型:
 Number
 Boolean
 String
也是一样的处理。
如果是一个number,则它会有一个包装对象:Number
如果是一个string,则它会有一个包装对象:String
如果是一个bool,则它会有一个包装对象:Boolean

12.6.4. 包装对象是临时的

第32 行,检测到t1后面加.,所以临时把t1包装一下,变成一个字符串对象,所以t1.abc=100不会报错。

第33行。由于这个32行中包装对象只是临时(钟点工)的,所以加上abc这属性就失效,就访问不到了,所以还是输出a.

是通过构造器创建一个字符串对象,这个相比包装对象而言,它是正式工,给它加的abc属性,会保存下来。
var t2 = new String( ); //这样写,length就为0 . 当String()中写值时,length值就为1 .

12.7. 引用类型的数据是对象
12.7.1. 函数是对象

48说明,f的构造器是Function。或者说,对象f是由Function创建的。
12.7.2. 数组是对象

52说明,a的构造器是Array。或者说,对象a是由Array创建的。
12.7.3. 对象是对象

56说明,obj的构造器是Object。或者说,对象obj是由Object创建的。
13. 对象是属性的集合
13.1. 两组概念
13.1.1. 变量与属性
13.1.1.1. 变量与属性相同点:都可以用来保存数据。

13.1.1.2. 变量与属性不同点
变量是个孤儿;全局变量是window对象的属性;

类似地:

对于window对象的自有属性,也是可以加上window的,一般会省略。

局部变量就是一个局部变量,不是window的,也不是函数的属性。

属性有家;属性一定是某一个对象的属性。

a的对象是obj
13.1.2. 函数与方法

13.1.2.1. 相同点
都可以正常访问,当然,加上()就执行里面的代码。
13.1.2.2. 不同点
函数是游侠;这里的f表面看不属于任何对象,但,它是加在window对象上的属性。

方法是护法;f1就是obj这个组织的。

13.2. 属性的操作
13.2.1. 访问某个属性
13.2.1.1. 方法一:对象名.属性名

13.2.1.2. 方式二: 对象名[“属性名”]

注意:
(1)[]里面必须是一个字符串常量
(2)或者是一个变量

(3)或者是一个表达式

(4)也可以写函数

13.2.1.3. 两种方法的区别
一般我们通过 . 来访问。
有如下情况,必须要用[]
(1)属性的名字不是标准的变量的规则。例如:

(2)在循环当中去访问

13.2.1.4. []的高级用法
[] 还有一个应用:这里面可以是任意运算结果是字符串表达式。

13.2.1.5. 访问一个不存在的属性会怎么样?

返回undefined
有如下情况会出现undefined
(1)变量没有初值
(2)函数没有返回值
(3)数组元素没有初始化
(4)形参个数大于实参个数
(5)访问一个对象的不存在的属性

13.2.1.6. 访问一个没有定义的变量会怎么样?

13.2.2. 给对象添加新属性
13.2.2.1. 方法1: .

13.2.2.2. 方法2: []
注意:
(1)加双引号。company[“tel”] = “88888888”;
(2)如果属性名是一个变量保存的,则直接写变量名。

13.2.2.3. 同名属性会覆盖

13.2.2.4. 基本数据类型的不能添加属性

abccc这个属性并没有被保存下来。(临时包装类型)。
13.2.3. For( in ) 遍历对象的属性
遍历:把对象的所有的属性都访问一次。

13.2.3.1. 遍历一个数组:for()

13.2.3.2. 遍历一个对象 for in

格式:
For( var 属性名 in 对象){
//可以通过 属性名 来直接获取当前键值对中的名字
//可以通过 对象[属性名] 来直接获取当前键值对中的值
}
图示

注意:
(1)For in比起for循环来:不需要知道对象的属性的个数,内部的机制就可以保证它可以一个不漏地把所有的属性都访问到。
(2)for( var 属性名 in 对象) 里面的属性名只是局部变量,只是符合要求的变量名都可以。
你只能通过对象[属性名] 的方式去访问属性值。

13.2.3.3. For in / for()
由于数组是一个比较特殊的对象,它首先是对象,所以它也可以用for in.
(1)你可以通过for in来遍历数组

(2)你不能通过for循环来遍历对象
(3)对象的一些属性是不能通过for in来输出的。

如这里的__proto_.。不能通过for in访问。但可以直接通过属性名来访问:

这些属性不能被输出的原因是:它们不可枚举的!
13.3. 属性的四个特征(了解)
每一个属性:有四个特征
 Configurable:可配置的 true or false(默认为true) 是否可以被删除
 Writable:可以修改 true or false(默认为true)
 Enumerable:可枚举的。是否可以被for in输出 true or false(默认为true)
 Value:属性值。默认是undefined
13.3.1. 获取属性的特征
格式:
Object.getOwnPropertyDescriptor(对象,“属性名”)
Object:构造器。必须这么写。
Own:自己的
Property:属性
Descript:描述

它只能获取自己的属性(自有属性),而这里的__proto__不是它的自有属性。
所以不能获取它的特征值。

13.3.2. 为什么显示声明的变量不能删除而遗漏声明的变量可以删除? ( 掌握 )

如上:显示定义的变量a,遗漏声明了变量b,它们都会被加在window对象上。
为什么:a不能delete,而b可以delete!

原因是:

var a 是window的一个属性,但它的configurable是false,表示它不能被删除。
而b的这个值是true,所以可以通过delete来删除window的属性b.
13.3.3. 精细化设置对象的属性
格式:Object.defineProperty(对象名,“属性名”,{
configurable:
writable:
enumerable:
value:
})
13.3.3.1. 设置一个不能枚举的属性

13.3.3.2. 设置一个只读的属性,模拟常量

由于name属性的writable是false,即它的属性值是不能被修改的。所以第34修改无效。

在Es6中,已经给我们提供这个功能。
关键字: const

这个const是一个常量,一旦定义后,就不能再去修改它的值。理解为,只能赋值一次。
13.4. 修改属性
13.4.1. 修改值
对象名.属性名 = 新的属性值;
13.4.2. 修改特征值
把不可枚举的改成可以枚举的。
Object.defineProperty(对象名,“属性名”,{
Enumerable : true
})
13.5. 删除属性
格式:
delete 对象名.属性名

要想能够成功 删除,则有一个前提“这个属性的configurable的值必须是true”.

特别地:
如果要删除window对象的属性,则可以省略对象名。

delete只能删除自有属性

sort是原型属性,delete不起作用。
13.6. 属性的分类(掌握)
两类
 自有属性
 原型属性

13.6.1. 自有属性

自写的属性。如name,tel。
这里的__proto__是隐式原型。
13.6.2. 原型属性
隐式原型对象上的属性

13.6.3. 自有属性与原型属性的使用顺序
在访问对象的属性时,先在自有属性集合找,找到了就用。
没有找到,就到原型属性中去找,找到了就用。

.toString() 属性在company的自有属性中没有找到,则去 原型 proto 中。
还是没有找到,则返回 undefined
13.6.3.1. 理解 arr.sort

对于arr对象来说,它自有属性是:0:5,1:1…4:4.而sort是它的原型对象的属性,也就是它的原型属性。所以它能够访问!
13.6.3.2. 自有属性与原型属性具有相同的属性名时
先使用自有属性。

解释:
(1)arr是一个数组,它也是一个对象 ,所以我们可以给它加属性。
给数组添加的属性,不增加数组的长度。
(2)这个自已添加的属性恰好叫”sort”,而与它的原型属性重名了。
(3)优先使用自有属性,则arr.sort 的值是一个字符串”abc”,
(4)所以arr.sort() 会报错。因为不是一个函数。

13.7. 判断对象是否具有某个属性(掌握)
13.7.1. 方法1:hasOwnProperty
13.7.1.1. 格式:
对象.hasOwnProperty (属性名)

注意:
(1)它只判断属性是不是自有属性。如__proto__ 是arr对象的原型,所以hasownproperty()返回false.
(2)关于数组:arr[0] 这里下标0相当于是对象的属性名。(如:Obj[“name”])

由于是数字,可以省去””。
13.7.2. 方法2:in
格式: 属性名 in 对象名
返回值:true false
功能:既可以检查自有属性也可以检查原型属性。

如上:__proto__是一个原型属性,这里还是得到true。
13.8. 对象与数组的相同与区别
13.8.1. 相同
(1)它们都是对象。理解数组是一类特殊的对象。
(2)它们都可以用来保存复杂的数据。

13.8.2. 不同
(1)对于新添加的数组元素,如果它不符合数组元素的要求:它的属性名不是有效的数字。则只是把这个元素的值当作一个普通的对象属性来处理。而不会让数组长度加1.

从这里理解:数组是一个很特殊的对象,它的特点在于元素之间的线性关系(具体反映在属性的名字是有序正整数)。
(2) 遍历数组用:for, while ,do while
遍历对象: for in
(3) 基本的使用格式也不一样. 数组[] 对象 {} (JSON)
(4) 判断一个元素是否是数组的值,要用到循环。
(5) 判断一个属性是否在对象中存,用in 或者 hasOwnProperty,不需要循环,也不能用循 环 (因为属性的集合是无序的)
13.9. 利用对象与数组的特性完成两个例子(掌握)
13.9.1. 数组去重
把数组中出现一次以上的数据去掉,只保留一个。也就是说,保证数组没有重复的元素。
13.9.1.1. 方法一:使用两层循环
思路:
对于arr中的每一个元素,如果在newArr[]中没有,可以添加进来。
一个空容器;
循环{
如果这个容器已经有了,则跳过;
否则放入;

返回这个容器

代码:

(1)有一个空容器
(2)判断当前的元素在容器是否存在
(3)添加容器
(4)返回
13.9.1.2. 方法二:利用对象的属性

思路:
对于arr中的每一个元素,如果在newobj {} 中没有这个属性,可以添加进来。
一个空容器(对象);
循环{
如果这个容器已经有了,则跳过;
否则放入;

返回这个数组

13.9.1.3. 小结:
用数组的两重循环可能对一些特殊的数据会去重失效。试下如下的数组:

13.9.1.4. 去重的ES6实现

13.9.2. 统计数组中各个元素出现次数
思路:
对于数组中的每一个元素。
如果这个元素t, 在obj的属性集中存在,则直接把obj[t]+1
如果这个元素t,在obj的属性集中不存在,说是是第一次遇到,则obj[t] = 1。

为啥obj的属性值会是数值, 是因为 :
数组中的值t是obj中的属性名[t];
当属性名obj[t]重复时,即是数组的值重复;
程序起初走的是else语句,obj[t]的属性值为 1,obj[t]重复时obj[t]+1也为数值.

调试

  1. 对象是由函数创造的
    如果你想知道当前的这个对象是由哪个函数创造的,你可以通过:
    对象.proto.constructor 来输出。
    如:obj.proto.constructor 就可以输出创建obj对象的那个函数(也称为构造器)
    理解:
    obj是一个对象,对象是属性的集合。
    __proto__是obj的一个属性。具体来讲,”proto”是属性的名字,obj._proto__就是属性的值。
    obj._proto__这个属性值又是一个对象。这个对象中有一个属性,名为constructor;值是一个函数,这个函数就是创建obj对象的那个构造器。
    14.1. 构造器的概念(掌握)
    常用的构造器有:Object,Array,Function,Number,Boolean,String,Error,Date…
    构造器就是一个函数。可以通过typeof Object来验证。

(1)一般我们使用它们会在前面加New。函数是可以直接调用的,但由于我们希望把它们当做构造器来用,所以会在前面加new。理解如下两句代码:

直接使用Number,相当于是把后面的参数转成数值类型,就是一个普通的函数。
New Number,会产生一个对象。
(3) 在函数的前面加new 时,我们才称这个函数是构造器。
(4) 作用:产生一个对象。
14.2. 从对象出发去访问Constructor属性
constructor属性这个属性是每一个对象都有。如下:

具体来讲,这个属性是原型对象__proto__上的属性。这就是c这个对象的原型属性。

这个属性的值是一个构造器,这个对象就是由这个构造器所产生的。
14.3. 数组是由函数创造的

创建数组有两种方式,一种字面量,另一种构造器。但本质是一样的。
结论
所有的数组都共有一个构造器:Array。这个构造器本质是一个函数,所以我们说,数组是由函数创造的。
14.4. 函数是由函数创造的
创建一个函数有三种方法。

本质上,都是由new Function来创建的。

函数本质上,也是一个对象。

检查这三种方式定义的函数的构造器:

结论
所有的函数是由Function这个构造器产生的。
14.5.
基本数据类型是由函数创造的
创建一个基本的数据类型有两种方法:
 直接量
 构造器。
我们一般用直接量,不会用构造器。但本质上一致。

通过constructor检查它们的构造器:

第13行:当你在基本数据类型a后面加一个.时,js会自动地把a“临时包装一下”变成一个Number类型的对象,所以,它可以访问__proto__属性 。
结论:
基本数据类型都是由函数创造的。
14.6. 对象是由函数创造的
创建对象有两种方法:
14.6.1. 方法一:字面量方法

直接在{ }中写键值对。
14.6.2. 方法二:构造器

结论
字面量的方式是一种更加简洁的方式。实际上,你理解字面量方式是构造器的一种快捷写法。(类似于快捷键),本质上,是一样的。

所有的对象都是由Object这个构造器产生 的。
14.7. html对象也是由函数产生的

即a标记是由HTMLAnchorElement()构造器产生的。
14.8. 所有的函数都可以充当构造器

如上代码:
(1)f是我们自己定义的一个普通的函数。它没有明确的写return。
(2)第15行,正常调用函数,所以得到a,值是undefined。
(3)第16行:在f()前面加一个new,此处,把函数f当作一个构造器来使用,我们通过个f构造器产生一个对象a1。

(4)同样,我们可以通过f,产生多个对象。 如a1,a2。这些对象的构造器都是f。
(5)特别地,如果这个f就是要想充当构造器,则建议把f改成大写字母。
14.9. 总结
构造器:是一个函数。
它直接调用,其返回值是它的return语句的值;
加new调用,则充当构造器的角色,会返回一个对象。

所有的对象都是由各个的构造器产生的。如:
10 ---->Number
“abc” —>String
False —>Boolean
Function f(){} ----> Function
[] ---->Array
{}---->Object
如果希望这个对象是由我们自己定制,而不是由系统提供的,我们可以通过自定义构造器来产生。也就是用自定义的函数来充当构造器的角色。

一般来讲:一个函数,如果是构造器的话,则它首字母要大写。
15. 原型与原型链(重难点)
proto:隐式原型
prototype:原型(js中,涉及面向对象的部分,都是由prototype来实现的)
15.1. 原型与隐式原型
15.1.1. 每一个对象都有一个__proto__属性

这个属性的值也是一个对象。这个对象有一个特别的属性名字是:constructor,它的值是一个函数。具体来讲这个函数是创建这个对象的构造器。
15.1.2. 每一个函数都有一个prototype属性
由于函数也是对象,所以它也是属性的集合。

15.1.3. 由于函数也是一个对象,所以函数既有__proto___,也有prototype属性
自定义的函数

构造器

15.1.4. 对象的__proto__属性<===>创建这个对象的那个函数(构造器)的prototype
15.1.4.1. 数组,对象,自定义构造器

15.2. 原型链(是由隐式原型链起来的)
15.2.1. 什么是原型链?
如果要求对象obj它一属性p的值:
(1)先在它的自有属性中去找。如果找到,则返回。
(2)如果它的自有属性中找不到,则去它的隐式原型(obj.proto)中去找。如果找到,则返回,找不到,则继续,在obj.__proto__这个对象的隐式原型中去找。这样一直找下去,找到null结束。如果还没有找到,则返回undefined.
(3)这个过程中形成的obj.proto.__proto__这个链条就是原型链。

15.2.1.1. 代码1

push这个属性并不是arr的自有属性,而是隐式原型上的属性,由于有这个原型链的机制,所以它能够访问push。
15.2.1.2. 代码2

对于自定义构造器F,我们用它生成一个对象f。
第28行:f.toString。相当是要访问f对象的toString属性。所以先在它的自有属性中找,没有找到(23行);继续在它的隐式原型中找(24行),没有找到,再继续(25行),可以找到。

换成找toString1。 错误的原因是:

由于f.proto.proto.proto 已经是null,就不能再去调用hasOwnProperty方法。
15.2.2. 应用原型链的两种方法
15.2.2.1. 通过对象的隐式原型__proto__添加新属性
arr1.__proto__与arr2.__proto__的原型值是相同的。 也等价于:Array.prototype

解释如下:

第二步:

为什么 arr2的__proto__ 与arr1的__proto__指向同一个对象呢?答案如下:

第三步:

第四步:

Arr1.abc,先在自有属性中找,找不到,沿着原型链,去找。然在arr1.__proto__中找到了。
同理,arr2.abc也是一样。

这个方法不好!
原因是:
(1)arr1与arr2从形式上看是平等的,都是一个数组对象。我们通过arr1.__proto__去修改整个数组对象的原型,达到了给所有的数组对象添加新属性的目标,但,这不直观。
__proto__是表示私有的,不能直接调用。
15.2.2.2. 通过构造器的原型prototype添加新属性
如下代码:
通过修改Array这个构造器的原型对象prototype,达到了一个目标:所有的数组对象都具有一个abc属性。这个效果与上面的代码是一样的。

原因是:“对象的__proto__属性<===>创建这个对象的那个函数(构造器)的prototype”
即第14行与第15行等价。
15.2.2.3. 结论:要用原型,不是隐式原型

proto 两个下划线,表示私有的,或者说不能公开的,或者内部的。
由于有原型的机制,所以在实际的使用中,可以省略__proto__。即
arr1.proto.push(1)
去掉__proto__ arr1.push(1)
15.2.2.4. 回答如下问题

1.为什么字面量“abc”可以调用方法?

因为它会被临时包装成String对象。所以可以去调用subStr属性。

2.substr这个属性是谁方法?

 是”abc”这个对象的隐式原型的方法
 是String这个构造器的原型prototype上的方法

3.为什么一个string对象能够访问String.prototype的方法?
原型链!
15.3. 原型链的应用
15.3.1. 数组求最值

15.3.1.1. 基本实现

已经能够工作,比如数组对象的方法:arr.sort(),我们希望达到如下的调用方式:

15.3.1.2. 把方法加在构造器的原型上

由于给构造器Array添加一个属性getMax,所以呢,所有的由Array所产生的对象都可以访问这个属性。
还有一个问题:getMax()中的参数。
对比arr.sort(),我们如何才能不写这个参数?
15.3.1.3. 解决参数的问题

1.把getmax的形参去掉。
2.把getMax的函数体中arr改成this
3.在调用getMax时,就不要实参了。
4.关于this,后面说。
15.3.1.4. 解决参数的问题

5.把getmax的形参去掉。
6.把getMax的函数体中arr改成this
7.在调用getMax时,就不要实参了。
8.关于this,后面说。
15.3.1.5. 添加方法给原型

改进:通过this来代替形参。

15.3.1.6. 写到一个单独的js文件中

则,上面的myArray.js是就是一个库。用于拓展Array的功能。
使用这个库。

直接引入这个文件,就可以直接调用相关的方法。
15.4. 图解原型链(不要求掌握)

  1. This(掌握)
    16.1.1. this是一个对象。

this出现在全局的代码块中,它就是window对象。

这里的this出现在一个对象的方法中,它就是对象本身。
16.1.2. 到处都有this
只要你能够写js的地方,就可以写this.

16.1.3. 不能手动地修改this的值

它不能出现在=的左边!
16.2. 确定this的值
16.2.1. 出现在函数的外部
这个this外层没有function,并且它不出现某一个对象的事件响应函数的内部。

这种情况:只有一个答案就是window.
如下情况:

This是这个button对象。为什么不是window.
上面的js代码,等价于下面:
Btn.onclick = function(){ alert(“abc”);alert(this);}
这里this出现在function的内部,所以不是window.
16.2.2. 出现在函数的内部
16.2.2.1. 这个函数不是某一个对象的属性。
就是一个普通的被调用的函数。

16.2.2.2. 这个函数是一个对象的属性

这里的say是obj对象一个属性,则里面的this会指向obj(this === obj )。
16.2.2.3. 调用函数之前加一个new的话,会创建一个新的对象,而这个函数内部的this就会指向创建的新对象

注意:
只有在代码运行时,才能确定this的值。换句话,第22行运行时,this会指向f。
如果是如下代码:

则第22行运行时,this才指向window。
16.2.3. 实战代码分析
为什么在下面的代码中可以用this?

16.2.4. 总结
确定this的值:“谁调用我,我就指向谁”
this的值:只有在代码运行时,才能确定!

修改this的值(掌握)
16.2.5. 可以通过Function对象的apply和call方法来指定函数内部的this的值

16.2.6. apply,call是从哪里来的?
(1)say.call理解:say是一个函数,当然也是一个对象,所以这里相当于是访问say这个对象call属性。按照原型链的规则:先在自有属性中找。没有找到。接着在隐式原型中去,找了。
call,apply都是say 这个对象的隐式原型上的属性。也就是Function构造器的prototype中的自有属性。

16.2.7. call apply作用&格式
16.2.7.1. 作用
修改函数内部的this对象的值。
16.2.7.2. Call 格式
对于call
原函数是:
F(形参1,形参2,…形参n){}
函数名.call(用于替换this的那个对象obj,形参1,形参2…,形参n);
注意:
(1)call的第一个参数是用于替换原函数中的this的。F.call(obj),表示在函数的内部会用obj来代替this.
(2)call后面的第二到第n个参数,与原函数f的形参列表对应。
(3)F.call(obj,形参1,形参2…,形参n)。会去调用函数F,但有一点,用obj替换函数F中的this.
16.2.7.3. 理解如下示例

在call的前面是一个函数,在call的后面是一个对象。我们以前在调用对象的方法时,格式是:对象名.方法名(),对象在前,方法在后。现在用call恰好反过来了。

借用的思想:把一个函数借给一个对象用一下,在用的时候,则函数内的this会指向这个对象 。
16.2.7.4. apply格式
对于apply
原函数是:
F(形参1,形参2,…形参n){}
函数名.apply(用于替换this的那个对象obj,[形参1,形参2…,形参n]);
注意:
(1)apply的第一个参数是用于替换原函数中的this的。F.apply(obj),表示在函数的内部会用obj来代替this.
(2)apply第二个参数是一个数组,里面是原函数f的形参列表。
(3)F.apply( obj,[形参1,形参2…,形参n] )。会去调用函数F,但有一点,用obj替换函数F中的this.
16.2.8. apply与call的区别
就一点区别:
call在调用时,形参是一个一个的。
Apply 在调用时,形参是一个整体,必须要用数组包起来。
16.2.8.1. 例子

特别地:
 如果这个函数没有形参,则使用apply和call是一样的。
16.2.9. call的应用 :判断数据类型
思想:借用了Object.prototype.toString

(1)每一个对象都有toString方法(除undefined,null外),当然,它们的结果是不一致的,即各是各的toString方法。
(2)XXX.toString()会把XXX转成一个字符串,它不会主动输出。
(3)第24行是把a.toString()的结果输出,得到1.
(4)第25行是:把Object.prototype上的toString方法 借给a用一下。得到[object Number]
(5)类似地:我们可以得到如下:

对于undefined和null解释如下:
如果直接去写 g.toString() ,d.toString();会出错。

为什么 这里Object.prototype.toString.call(g)不会出错呢?
因为出错是g没有这个toString方法 ,而不出错的原因是这里的toString是Object.prototyp临时借给g用的,不是属于g的。
16.2.9.1. 为什么不用其它的构造器上的prototype中的toString?
选择Object.prototype.toString的原因是:
(1)Object.prototype是根。参考原型链图
(2)它能够保证输出的结果一致性:[object Number] [object String] …
16.2.10. call的应用 :实现字符串的翻转
核心思想:借用数组的reverse实现字符串的reverse split join

代码优化下,去掉中间变量:

直接转成数组之后,使用reverse方法:
String.split(“”).reverse().join(“”)
也是可以的。
16.2.11. Call的应用:求数组的最大值

思想:借用Math.max方法 。
注意:
(1)借用这个方法时,由于这个方法的实现与它内部的this没有关系。只是说,给我一个形参列表,我就来求最值。由于与this没有关系,所以第一个参数是可以任意写的。

(2)由于apply的第二个参数是一个数组,所以这里比较适用使用apply
(3)如果要用call的话,则需要把这些数组组折出来,变成一个形参列表,可以使用”…”这个语法。这是es6的内容。百度”es6 三个点”
17. 创建对象
17.1. 字面量方式创建对象(掌握)

17.1.1. 示例
定义一个矩形对象。
它有两个属性:长,宽。
它有一上方法:求面积。

注意:
在对象的方法内部,要访问它的属性,应该加this。否则会报错。
改正:

如果 多个方法,则在相互调用时,也要加this

17.1.2. 优点:直接,简洁。
17.1.3. 缺点:
如果要批量创建多个对象,则会出现很多重复的代码。

只适合于这种情况:
这个对象只需要要创建一次。如果要多次创建,则需要用其它的创建方法。
17.2. 工厂方法创建对象(理解)
17.2.1. 基本实现

思想:
(1)有一个函数专门用于生产对象。这个函数名为 CreateRectFactory。
(2)它的第一步就是:创建一个空的对象。
(3)它的第二步就是:给这个空的对象加一些属性(或者是方法)。
(4)它的第三步就是:返回这个加工好的对象。
所以称为工厂函数。

17.2.2. 问题1:每个对象的属性都是一样的

解决:加参数。

17.2.3. 带参数的工厂函数

17.2.4. 优点
很简单就可以可以解决批量生产的问题。
17.2.5. 缺点
这个工厂方法生产的所有的对象就是Object这个“牌子”的,没有自己的品牌。

对比理解,上面的代码中arr是对象,f也是对象,很明显它是不同的品牌。前者是Array,后者是 Function.

我们希望,我们的产品有自己的品牌,具体到这个例子,我希望是”Rect”品牌的。
如何实现?

17.3. 使用构造器创建对象
任何一个函数都可以当作构造器来使用。
17.3.1. 理解New (掌握)
17.3.1.1. 不加new

相当于是window.f();所以this会指向window。Name1这个属性就加在window上面了。
17.3.1.2. 加new有什么变化

(1)仍然会调用这个函数。
(2)但自动生成一个返回值t1。虽然这里没有写return,这个过程系统自己做的。
(3)在这个函数的内部,this就是t1。
(4)你加的属性:如this.name1就相当于是t1.name1。所以在new结束后,可以通过t1.name1去访问值。
(5)没有加new,则this会指向window,加了new后,会向系统自动给我们创建这个对象上,当然, 这个对象会返回出来。如:var t1 = new f();则这个f中的this会指向t1(或者说,这个this就是t1)。
17.3.2. 加new后做四件事(不做要求)
17.3.2.1. 示例代码
function F(){}
Var f= new F();
在F()的内部会自动做如下:
(1)创建一个空对象var obj = {}
(2)F.apply(obj,arguments) //会让F()函数内部的this指向obj,所以你写的this.name1就相当于obj.name1。给对象添加属性.
(3)obj.proto === F.prototype // 搭建原型链
(4)return obj; // 让var f = new F();中的f接收obj,也就是这里的f就是函数内部的obj。
17.3.2.2. 理解代码:

通过new的方式创建对象:
第27行:说明t1这个对象有自己的”品牌”,就是f。
第28行:t1这个对象的隐式原型 === 构造器f的原型。所以加在f.prototype上的属性abc,能够被t1所访问。

17.3.2.3. 矩形构造器

理解:
第67行:会调用Rect方法。里面的this就是r1.
第69行:r1这个产品,它的品牌是”Rect”
第71行:会调用Rect方法。里面的this就是r2.

17.3.3. 缺点

每个对象有自己的getArea方法,这一点就会造成浪费。同时也不科学:求矩形面积,方法应该都是一样才是。

为什么是false?怎么才是true?
解释如下:

由于r1与r2的内部各自都有getArea,getsay方法,所以:

解决:
把方法写在构造器的原型(即,对象的隐式原型)上。

17.4. 原型方式创建对象(掌握)
17.4.1. 实现
把原来写在Rect()构造器内部的方法,写在构造器的原型上。

图示如下:

结果如下;

第70行,由于r1对象它的自有属性中没有getArea,所以它会在它隐式原型上去找。同样rs.getArea也是这样。由于r1,r2都 是Rect构造器创建的对象 ,所以 : r1.proto === r2.proto === Rect.prototype .
所以得到结果是true.

这样把方法从构造器中拿到构造器的原型上的好处在于:
所有的对象会共享这些方法,而不需要自己去创建。
17.4.2. 原型属性和对象的自有属性
原型属性是指把属性写在 构造器的原型上。

自有属性是指把属性写在 构造器的内部。

17.4.3. 总结
把每个对象都共用的属性写在原型属性上。
例如:加一个logo属性

每个对象各个不同的属性写在自有属性上。
例如:加一个area属性

最后,由构造器Rect创建的对象如下:

17.5. 总结
创建对象真正使用的只有两种:
 字面量 :适用于只需要创建一个对象的情况。
 原型方式:适用于批量创建对象的情况。
 把自有属性写在构造器的内部
 把原型属性写在构造器的原型上。

  1. 面向对象的选项卡(掌握)
    18.1. 基础版
    18.1.1. 结果

18.1.2. 结构

18.1.3. 样式

18.1.4. 代码

18.2. 基础版的改进-封装成一个函数
快捷地给另一个选项卡也添加功能:
18.2.1. 结构

有两个结构相同的选项卡。
18.2.2. 代码
可以通过调用函数的方式去实现选项卡的功能

调用函数:

18.3. 面向对象实现(一定要会)
18.3.1. 构造器

(1)给对象添加两个属性:
 this.tabAs
 This.lis
(2)给this.tabAs添加了事件响应
(3)第70行:为什么是that?
that是第61保存下来的局部变量。它的值是当时的this。
由于在事件响应函数的内部,this会指向当前的这个对象,具体到这里,this会指向tabAs[i]。而showIndex方法,是定义在构造器的原型上的一个属性,所以肯定是不能通过tabAs[i].showIndex来访问的。应该是通过Tab构造器所产生的对象来访问,而第61行保存的that的就是指向Tab构造器所产生的对象,所以这里需要把this改成that.

18.3.2. 通过构造器来创建tab对象

93行,创建一个对象,这个就会拥有Tab中定义的属性及Tab.prototype中定义的方法。

  1. 继承(了解)
    命好。不劳而获。
    由于对象是属性的集合。继承就是把别人的属性复制一份到自己。
    19.1. js生来继承的特性

理解:
对于变量a,它没有toUpperCase方法,这个方法是 String.prototypte中的方法。

而a可以直接使用,说明它通过原型链从它的构造器中继承这个属性。
19.2. 对象的继承
对象有两种:
 通过字面量方式创建的对象
 通过原型的方式创建的对象
由于创建对象的方式不一样,则继承的方式也不一样。
19.3. 字面量对象 – 通过拷贝的方式来实现继承(掌握)
拷贝就是复制。分成
 浅拷贝
 深拷贝
19.3.1. 浅拷贝

以上就把一个对象全部的属性直接拷给另一个对象,相当就是继承。核心的代码是:

问题是:

当我们去修改一个引用类型的属性时,会把父对象的那个属性也修改了。
例如:给xiaoxiaofan添加一个朋友时,也会影响父对象的friends.
原因如图示:

通过for循环,拷贝对象的属性时,只是把相应引用类型的地址复制过来了,就是说xiaoxiaofan的account属性与xiaofan的account属性是一样的,指向同一个引用数据的空间。所以,改一个,另一个也受影响。

浅拷贝只能处理基本的数据类型。
19.3.2. 深拷贝

19.3.2.1. 目标:实现数组的克隆

这样写不行,第29行,改了 arr1,arr2也受影响。

解决办法:

使用递归!
19.3.2.2. 目标:实现对象的克隆

浅拷贝

改成深拷贝

19.3.2.3. 终极目标 :实现真实对象的拷贝

综合了克隆数组与克隆对象的思想。利用了递归函数。
19.4. 对于原型方法创建的对象–混合继承的方式(了解)
//篮球运动员
function Player(name,height){
//自有属性
this.name = name;
this.height = height;
}
//原型属性
Player.prototype.shot = function(){
console.info(this.name +“身高 :”+this.height+" 投篮!");
}

	var p1 = new Player("库里",192);
	var p2 = new Player("科比",198);

	p1.shot();
	p2.shot();


	function Mvp(name,height,maxScore){
		this.name = name;
		this.height = height;
		this.maxScore = maxScore;
	}
	Mvp.prototype.shot = function(){
		console.info(this.name +"身高 :"+this.height+" 投篮!");
	}

	Mvp.prototype.dumk = function(){
		console.info(this.name +"身高 :"+this.height+" 扣篮!");
	}

	var m1 = new Mvp("库里",192,"60");
	var m2 = new Mvp("科比",192,"81");

	m1.dumk();
	m2.dumk();

需求:
mvp这个构造器,它应该具备player构造器的功能。我们希望mvp能够继承player的属性!
19.4.1. 对于自有属性的继承
用call,或apply来实现。

19.4.2. 对于原型属性的继承
用拷贝的方式来实现。

//篮球运动员
function Player(name,height){
//自有属性
this.name = name;
this.height = height;
}
//原型属性
Player.prototype.shot = function(){
console.info(this.name +“身高 :”+this.height+" 投篮!");
}
Player.prototype.jump = function(){
console.info(this.name +“起跳”);
}

	// var p1 = new Player("库里",192);
	// var p2 = new Player("科比",198);

	// p1.shot();
	// p2.shot();

// debugger;
function Mvp(name,height,maxScore){
// this.name = name;
// // this.height = height;
this.maxScore = maxScore; //这个构造器自己的参数还是要自己去赋值
Player.call(this,name,height) //对于play构造器中的参数
}
//继承父构造器的原型属性
for (var p in Player.prototype) {
//复制一份原型属性
Mvp.prototype[p] = Player.prototype[p];
}

	//创新 添加自己的属性
	Mvp.prototype.dumk = function(){
		console.info(this.name +"身高 :"+this.height+" 扣篮!");
	}

	var m1 = new Mvp("库里",192,"60");
	var m2 = new Mvp("科比",192,"81");
	console.info(m1,m2);

	m1.dumk();
	m2.dumk();
	m1.shot(); //这个shot是从Player继承过来的
	m1.jump(); //这个jump是从Player继承过来的
  1. 正则表达式基本概念
    20.1. 什么是正则表达式
    Regular(规则的) expression(表达式)

‘正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑。’
 正则表达式是一个字符串;
 表示一种规则;
 有自己特定的编写的语法;
 用于对其它字符串进行验证或者匹配

20.2. 与其它语言的关系
有很多的程序设计语言(js,c,c++,java,php…),在这些语言中只要要与字符串进行验证或者匹配就会用到正则表达式。

正则与程序设计语言的关系是:所有的程序设计语言都支持正则表达式。正则表达式(有一种特殊的人才:可以在用刀雕各种花形。)不依赖于任何程序设计语言(酒店)。
20.3. 应用
在整个计算机中的应用。
20.3.1. 编辑器中的正则表达式
市面绝大多数的文字编辑器都有正则表达式的功能:
 word中:
 Sublime:

20.3.2. 在程序设计中
 验证:给一个字符串str ,一个正则表达式reg。检查str是否是符合 reg所规定的规则。这个功能在不同的程序设计语言当中,可能语法不一样。在js当中:
reg.test(str)。典型的应用:表单验证。
验证用否的输入是否一个合法的手机号码。
 匹配:给一个字符串str ,一个正则表达式reg。把str中符合reg所规定的子串找出来。
在js中当,语法:reg.exec(str)。典型的应用:
小偷程序(抓包)。例如:可以把目标网页中的我们感兴趣的部分“偷”出来。
21. js中的正则
21.1. 创建正则表达式
21.1.1. 构造器RegExp方法
格式:

示例

(1)p就是一个正则表达式对象。
(2)p就可以使用__proto__上的方法。
(3)这里的修饰符有三个:
 I (ignoreCase) :是否区分大小写。忽略大不写?默认是false 。默认是不忽略大不写,说明是大小写是敏感的(默认是区分大小写的)
 G(gloabl) :是否全局匹配
 M:multiline:是否支持多行匹配
在使用的过程中,这个三个参数没有顺序的关系。那个在前在后都没有关系

21.1.2. 字面量 方法
格式: var p = 界定符 主体内容 界定符 修饰符

(1)理解界定符。/ / 在这个符号内部的内容就是正则。类比理解 :
数组的界定符是:[]
对象的界定符是;{}
字符串的界定符是:””
正则的界定符是://
(2)修饰符,同上。
21.1.3. 小结
这两种方法都可以使用。一般我们会字面量的方法。但有一种情况,只能用构造器的方法。
类比:访问对象的属性有两种方法: . 和 [] 。当要访问的属性是变量时,只能用对象[变量名]的方式。

这里也一样。
如果正则表达式的主体保存在一个变量中,则只能用构造器的方式。
21.2. 正则表达式的构成
一个正则表达式有如下四个部分构成:
 界定符 //。当然,如是通过构造器的方式创建,就不需要写了。
 原子。 是构成正则表达式的最基本的单位,它是必不可少的。
原子是\d。
 元字符。是用来修饰原子的。它是可选的。
 修饰符。修饰整个正则表达式的。在js中共有三个。M , g ,i 。要掌握就是g(全局匹配) i(区分大小写)
21.3. 使用正则表达式
 直接用RegExp对象的三个方法
 test():验证
test()方法是检测目标字符串是否有符合正则要求的子串。
var str = “abcdefg”;
str的子串如下:
a,b,c,e,f,g ;ab bc cd de fg; abc bcd cde …
子串:要相邻的;长度要小于等于目标的长度。

 exec():匹配
 compile():修改正则表达式
 与字符串对象的相关方法一起作用

21.4. 示例

test()方法
是检测目标字符串是否有符合正则要求的子串。只有一个字串是符合要求的,就返回true,如果所有的字符串都不符合正则要求,则返回false.
正则要求:p = /a/
字符串中要一个a(不能少,不能多)。
目标字符串: “abc” 。它的子串共6个:
a , b ,c ,ab,bc,abc
在这些子串,有一个是符合要求的,就是”a”
22. 原子
按表达能力分类:
 表示一个字符
 表示多个字符
22.1. 表示一个字符
A-Z,a-z,0-9基本的字符。它们只能表示一个字符。
/a/ 只能表示a
/abc/ 只能表示abc

加上i后,表示大小写均可。

22.2. 表示一类字符
22.2.1. \d与\D
\d表示:一个数字,即0-9。

\D表示:非数字。

对于目标字符串d1,由于它有一个子串”d”是符合要求的,所以是true.

注意:
(1)它们表示一个。
(2)子串的概念。
22.2.2. \s与\S
\s:空格

\S:非空格
表一个非空格的字符。

由于”1 a”中有子串是符合要求的,所以是true.
22.2.3. \w与\W
\w:表示“字”。只能表示如下四种:
(1)0-9
(2)a-z
(3)A-Z
(4)_

不能表示符号。

\W:非“字”

由于”_ “有一个字串” “是符合\W的要求的,所以返回true。
22.2.4. .
除了换行符,其它都可以表示。

  1. 元字符

示例:

 作用:修饰原子。{3}表示它前面的那个原子,要连续地出现3次。
 可选的。一个合法的正则表达式中,是允许没有元字符的。
 不能单独存在。

它的作用是用来修饰原子,不能单独写。
23.1. 开始与结束 ^与$
23.1.1. 开头 ^

表示:目标字符串必须要以它后的那个原子开头。
第21行,是以0开头的,所以不对。

^ 要出现在正则的最前面。

23.1.2. $表示目标字符串应以它前面的那个原子结束

一般要写在正则的最后面。
23.1.3. 开始与结束一起用

表示整个的字符串要符合要求(不是要某个字串,而是整个的)
23.2. 表示数量:+ ? {} *
+:表示它前面的那个原子,要出现一次以上(至少一次) 等价于:{1,}

?: 0或者1次。等价于:{0,1}
{m,n}:最少m次,最多n次
{m}:只能是m次
{m,}:最少m次,最多不限
{,n}:最多n次

  • : 0或者多次。等价于:{0,}

(1)重复的是它前面那一个原子。这里重复的是c。
(2)c{2,5}表示:c要连续出现2,3,4,5次。有一个满足就可以了。所以第66行为true。
^abc{2,5}$ 表示:只匹配整个的字符串,它要求整个的目标字符串是以a开始,后面接b,然后再接2-5c 而第67行,整个目标字符串中有6个c,不满足条件

23.3. 表示转义的
\ 作用:把一个字符的原来的意思改变。

23.3.1. 元字符想要当作一个普通的原子使用,则要加上转义符

有一些原子有自己特殊的含义:如.可以表示除换行所有的字符。但,在这里,我只希望有一个原子就表示一个. 所以,可以在前面加一个加转义符“\”.
如下字符,要想在正则表达式表示它自己,都需要加转义符。(它们不能直接表示自己,要加\才行。)

    • ? . | [ ] ( ) { } \ / ^ $ < >

如:

23.3.2. 有一些普通的原子,加了转义后就变成特殊的原子
\b
表示一个边界(与字符与字符隔开的东西就是边界),如:空格,正常开始和结束

\B
表示一个非边界

\t:表示一个tab键

还有如下;

23.4. 表示分组()
小括号在正则表达式中很有多应用,这里只是把几个原子“打包”成一个大原子。

由于*是可以有,也可以没有。所以第102行,1(ab)*2就可以匹配”12”,所以返回true.
第107行,由于1ababab2是满足条件的,所以它被匹配出来。
23.5. 表示逻辑 [] 和 |
23.5.1. []
格式:[原子1原子2…原子n] 。不需要加任何的符号
功能:只有要一个原子1出现即可。理解为 “或”

特别地:
[0-9a-zA-Z_] === \w

23.5.1.1. ^
取反。除了[]设表示原子集合之外的部分。

特别地,要当作取反的这个意思,就要放在原子最前面。

以上面的写法表示a^bc中的某一个字符。
23.5.2. | 或
表示前后的原子(原子集)有一个就可以。

ab|cd表示:子串中或者是ab , 或者是cd。
23.5.3. 练习

表示[]当中5个元素有一个即可。不要与ab|cd表示的意思搞混了。
24. 正则表达式的应用之验证

24.1. 验证手机号码

规则:
 11位的数字
 以1开头
 第2 位只能是35678

正则:/^1[35678]\d{9}$/
24.2. 验证座机号码
规则:
 4位区号(以0开头)-7位数字
 3位区号(以0开头)-8位数字

正则:
/^0\d{2}-\d{8}KaTeX parse error: Expected 'EOF', got '\d' at position 4: |^0\̲d̲{3}-\d{7}/

24.3. 验证颜色
#123456 , #ccc , #FFF
规则1:
 以#开头
 有6位:0-9a-f,不分大小写
规则2:
 以#开头
 有3位:0-9a-f,不分大小写

/^#[0-9a-f]{6}KaTeX parse error: Expected group after '^' at position 2: |^̲#[0-9a-f]{3}/i
24.4. 验证邮箱
[email protected]
[email protected]
规则:
 以“字开头”\w,个数至少一个: \w+
 有一个@ : @
 @后接”字”。要求2-5之间: \w{2,5}
 (.多个字母)至少一次,最多两次: (.[a-z]+){1,2}
 不区分大小写: 加修饰符 i

正则:
/^\w+@\w{2,5}(.[a-z]+){1,2}$/i

24.5. 验证身份证
规则:
 前17位是数字
 后一位:数字或者X

正则:
/^\d{17}[\dX]$/

由于“32”表示日期,32号这显然是错误的,但由于我的正则的规则很简单,所以它还是符合我的正则的要求的。

正则表达式是根据你定规则来写的,有可能,你写的正则与别人写的正则不一样。
25. 正则表达式的应用之匹配
25.1. 格式
Var rs = 正则.exec(目标字符串)

功能:
如果匹配成功:把目标字符串中符合正则要求的子串找出来,保存在rs中。
失败:则返回null
25.2. 基本应用
把一段文字中的生日找出来。
var str=“1986-11-11 你的生日是:1999-10-19我的生日是:”

(1)由于找到了目标,所以rs返回是一个数组。
(2)index指的是找到的目标子串在字符串中的下标。参考如下:

(3)Input:要匹配的字符串.
(4)如果找到不目标,则rs就是null

25.3. 全局匹配

如果要找到全部的符合条件的子串,则
(1)要把正则后面加g。表示开启全局匹配。如果不加,则只会找到第一个。
(2)每一次执行p.exec(str),就会从上一次成功匹配后的位置,开始向后找。所以,每一次执行exec就会找到下一个目标子串。
(3)每一次的匹配,都会让index增加。
(4)直到找不到为止,就会返回null
25.4. 通过while循环来找出全部的目标子串

原理:
(1) 第一次:rs = p.exec(str) 是一个赋值,把右边的值存到左边。先执行p.exec(str),得到子串,所以 rs不为null 。此时的rs是一个数组,保存是第一次匹配成功的结果。
(2) 由于赋值表达式的值是左值。所以 while( rs = p.exec(str) ) 相当于是while(rs) 。这个循环什么时间会结束?只有当rs值为null即为false时才结束,具体来讲,当我们找到不子串时,rs的值就是null,则while(rs)就不成立,则循环会结束。

  1. 反向引用
    捕获组捕获到的内容,不仅可以在正则表达式外部通过程序进行引用,也可以在正则表达式内部进行引用,这种引用方式就是反向引用。
    26.1. 产生
    当我们在写正则表达式的过程中,如果给正则表达式中加了小括号(无论是什么原因),则在匹配成功的结果,会额外地把小括号所对应的那个部分子串,保存下来。

没有加小括号,则rs只有一个元素,rs[0]保存是整个匹配成功的子串。

给表示年份的部分,加一个小括号,则rs中会多一个元素来。
rs[0]:仍然保存整个匹配成功的子串。
rs[1]:保存的是小括号部分对应的子串,就是这里年份

给表示年份的部分,加小括号,给表示月份的部分,加小括号。
即整个正则中,有两个小括号。
则rs中会多2个元素来,共三个元素
rs[0]:仍然保存整个匹配成功的子串。
rs[1]:保存的是第一个小括号部分对应的子串,就是这里年份
rs[2]:保存的是第二个小括号部分对应的子串,就是这里月份
保存下来的,对应小括号的内容,就称为引用。
你可以去使用这些引用。
两种方法:
 第一种:通过RegExp对象的属性来访问
 第二种:通过反向引用:\n
26.2. 查看引用
26.2.1. 通过RegExp对象的属性来访问

(1)RegExp是一个构造器,它用于产生正则对象。
(2)RegExp.$1访问RegExp这个对象的属性$1
(3)$1这个属性是RegExp这个对象的自有属性。

(4)从$1-$9分别对应9个小括号的匹配结果。
(5)正则对象的每一次匹配,都会更新RegExp对象的$1…$9属性。
26.2.2. 通过反向引用:\n(n表示数字)
回文数

小括号()找到的结果可以通过RegExp.$1来访问,但在正则表达式的内部,我们不能通过这种方式,而是用\数字来代替。

第一个(\d) 找到的结果会放在$1当中,在正则的内部用\1来表示这个结果。
第二个(\d) 找到的结果会放在$2当中,在正则的内部用\2来表示这个结果。

/(\d)(\d)\d\2\1/ 表示用\2去引用第二个小括号找到的值,用\1去引用第一个小括号找到的值。

这种用法叫反向引用。
26.3. 反向引用的案例
26.3.1. 重号

这里的\1就表示第一个小括号匹配的内容。所以后面四个与第一个数字是一样的。
26.4. 取消引用 ?:
加()也可以用来分组,把几个小原子组合成一个大原子,但它有一个副作用,就是产生反向引用。而这个反向引用会把这个结果保存下来。如果只是要做分组,而不想使用反向引用的结果 ,你就可以手动地取消引用。

默认的情况:

加了小括号,就会额外保存小括号对应的结果。

取消反向引用后:

  1. 贪婪与惰性匹配

在有表示数量的元字符对原子进行修饰时,它默认尽可能多地去匹配结果,让匹配的子串尽可能地长。这种特性叫贪婪匹配。

27.1. 贪婪匹配

在1个也可以,多个也可以情况下,它尽量多的去匹配。

在2-5的情况下,它尽量多的去匹配。

27.2. 惰性匹配 ?
在表示数量的元字符后加?

27.3. 惰性匹配应用1
抓取网页中感兴趣的内容。

由于默认是贪婪匹配的,所以不加?情况下,它会匹配到一个整个str,而我们的要求是找到一个一个的li,所以要加上?,变成惰性匹配。

27.4. 惰性匹配应用2

  1. 拓展

[\u4e00-\u9fa5] 这一个区间\u4e00-\u9fa5的编码表示汉字。表示一个汉字。
[^\u4e00-\u9fa5] 表示不是汉字。

把经常用的:邮箱,电话,手机号,网址…写出来。

猜你喜欢

转载自blog.csdn.net/weixin_43992935/article/details/89452965