关于个人的学习笔记,仅供参考,有谬误的地方还望多多指正,谢谢!
第一章:面向对象基础
1、面向对象的基本慨念
1.1 js 是不是一个面向对象的语言?
不是: 与传统面向对象的理论有矛盾. C#, Java
是: js 里面到处都是对象. 数组, 时间, 正则 ... 和 DOM
也可以像传统面向对象的语言那样用 new 的方式创建对象
1.2 js 是一个基于对象的多范式的编程语言
多范式: 编程风格
面向过程的方式编程
面向对象的方式编程
函数式 ...
1.3 函数式的一个特点: 递归与链式
jQuery 就是典型的链式编程风格
比如: 给 div 标签添加样式
$( 'div' ).css( 'border', '1px solid red' )
.css( 'width', '400px' )
.css( 'height', '200px' );
1.4 面向对象的概念
-> 面向: 将脸朝向 ... -> 关注, 用
面向过程开发 -> 使用过程的方式开发
面向对象开发 -> 使用对象开发
-> 面向过程
早上吃早饭: 吃面
自己做: 和面 -> 压成细面 -> 烧水 -> 煮面 -> 盛面 -> 吃
过程: 步骤, 细节, 顺序等
-> 今天早上
找到对象: 找到面馆 -> 要 -> 吃
-> 面向对象:
要做什么, 就找到对应的对象, 告诉它做, 等结果
-> 生活
买菜: 菜场
公交
电话
...
-> 是否说面向对象可以取代面向过程
面向对象是面向过程的封装
1.5 万物皆对象
在实际开发中, 对象是一个抽象的概念, 可以将其简单的理解为: 数据集或功能集.
-> 数据集: 很多数据打包到一起. { name: '张三', age: 19, gender: '男' }
假设展示 10 条商品数据. 每一个商品: 名字, 描述, 价格, 图片
->
每一条数据可以变成对象: { name:'', desc: '', price: 0, img: '' }
-> 引入数组, 将数据存储到数组中
-> 功能集(函数集, 方法集)
-> 在 js 中, 所谓的对象就是 键值对的 集合
-> 也可以混合使用
例如 jQuery:
$( 'div' ) 这个是一个 jq 对象, 但是实际上里面包含 DOM 对象
$( 'div' ).css( ... )
$( 'div' ).html( ... ) 是不是就说明 jq 对象中包含方法
-> 抽象性
商品列表: 书店, 书的列表
书名, 页数, 简介, 编号, 作者 对象只包含最核心的主要信息
游戏人物: 英雄
名字, 血量, 防御, 攻击, 护甲, 武器 只需要描述出需要的数据即可
-> 名词提炼法找对象
做一个表格排序
-> 过程
1) 创建表格
2) 添加点击事件
3) 排序, 更新数据
-> 对象
1) 创建表格对象
2) 添加数据
-> 实际开发的时候如果需要完成一个功能
-> 首先考虑系统是否提供了对象
例如创建图片: var img = new Image(); img.src = '...jpg';
例如获取页面元素: document, 标签对象
例如获得导航栏里面的所有的 li 标签
var nav = document.getElementsById( 'nav' ); // navigation
var lis = nav.getElementsByTagName( 'li' );
-> 如果系统没有可以考虑自定义, 或第三方
var itcast = {
tag: function ( tagName ) {
return document.getElementsByTagName( tagName );
}, addCss: function ( array, styles ) {
for ( var i = 0; i < array.length; i++ ) {
for ( var k in styles ) {
array[ i ].style[ k ] = styles[ k ];
}
}
}, id: function () {},
cName: function () {},
name: ...
attr: ...
class: ...
...
};
面向对象编程 是将事物对象化,通过对象通信来解决问题。面向对象编程,数据和对数据的操作是绑定在一起的。把关注的地方放在找对象上面,只需要找到一个合适的对象,让对象来做事情就可以了。
2、什么是面向对象
js对象是无序列属性的集合,其属性可以包含基本值、对象、或者函数。
例如:字符串、数字、数组、日期,等等....
不需要关注过程,直接可以对对象进行操作,更简洁高效
创建对象实例:
person=new Object();
person.firstname="Bill";
person.lastname="Gates";
person.age=56;
person.eyecolor="blue";
3、面向对象的特性
A.抽象性:在进行编程的时候,抽取核心的信息作为程序的数据
B.封装性:封闭一些核心的内容,让别人不知道你是怎么完成这件事情的,别人只需要调用就可以了,函数就体现了封装性。
C.继承性:主要通过原型来体现继承性。
4、断点调试
调试按钮
-> 逐语句: 单步运行. 就是一次只执行一句话. 如果遇到函数, 进入函数体内一步一步执行
-> 逐过程: 一次执行一个过程. 如果是一条语句那么与逐语句是一样的. 但是如果是一个函数, 那么他就会将函数执行完, 然后在往下走, 停下来.
-> 继续运行
条件断点
就是只有在 条件满足的时候(表达式为真的时候) 才会停下来的断点
5、值与引用类型
-> 基本类型( 值类型 ): 数字 number, 字符串 string, 布尔 boolean
-> 复合类型( 引用类型 ): 对象( 数组, 时间类型, 函数类型, 正则表达式, ... )
-> 空类型: Null, Undefined
4.1 基本类型和复合类型的存储方式
基本数据类型和空类型的存储模型就是一个方格里面放一个数据
复合类型, 除了函数, 其他的数据类型无法使用 typeof 获得数据类型名
注意: 如果需要获得对象的数据类型, 需要使用 Object.prototype.toString.apply( 数据 )
复合类型的存储方式
var p = { name: 'itcast' }
复合类型的对象是一个单独的内存区域, 对象有什么属性, 那么内存区域中就有什么数据.
变量 p 只存储该对象的 '地址'. 所以 p 不是真正存储数据的区域.
绘制数据的内存逻辑图
var arr1 = [ 1, 2, 3, 4 ];
var arr2 = [
{ name: '张三', age: 19, gender: '男' },
{ name: '李四', age: 18, gender: '男' },
{ name: '小李', age: 17, gender: '女' }
];
4.2 值类型与引用类型的存储特征
4.2-1.值类型的数据, 只需要开辟一段内存存储数据即可
var a = 123;
var b = 'abc';
var c = true;
4.2-2. 对象类型( 引用类型 ). 对象才是真正的数据, 需要占据单独的内存.
而变量名只是存储着对象的内存地址( 引用 ).
即创建一个对象并赋值, 实际上需要两块内存空间. 一个存储数据( 对象 ),
另一个存储变量以引用对象.
var o = { name: '张三', age: 19, gender: '男' };
值类型与引用类型的赋值与传参的特性
4.2-3. 赋值: 将原变量中的数据拷贝一份, 然后存储到给定变量中
4.2-4. 值类型
var a = 123; // 有一个盒子, 叫 a, 里面存储着数字 123
var b; // 又有一个盒子, 叫 b, 里面什么都没存( undefined )
b = a; // 将 a 中存储的东西复制一份, 然后赋值给 b. 即存储在 b 中
b 和 a 是两个独立的变量, 两者之间不再有关系. 改变其中一个数据, 另一个不变
4.2-5. 引用类型
var o1 = { num: 123 };
var o2;
// 赋值
o2 = o1; // 将 o1 中存储的内容 拷贝一份 存储到 o2 中
// o1 中存储的是引用, 或 '地址'
o1 和 o2 虽然是不相同的两个变量, 即两个独立的盒子. 但是由于存储的地址相同.
在访问数据的时候, o1 与 o2 也是访问同一个数据, o1 将数据修改了, o2 再读取,
读取的就是修改后的数据.
4.2-6. 函数参数传递
4.2-7. 什么是函数参数传递
函数要调用, 一般会给函数传递参数
在调用函数的时候, 回将参数中表示的数据拷贝一份, 然后给参数赋值
function foo( num ) {}
调用
var a = 123;
foo( a ); // 调用函数就是要执行函数
// 将 a 中存储的数据 拷贝一份
// 进入 函数 foo
// 给参数赋值, 相当于 num = a
// 进入函数体, 开始执行函数
此时的赋值特性与前面介绍的值类型引用类型的赋值是一个特点
4.3 深拷贝与浅拷贝
4.3-1. 什么是拷贝: 就是创建一个与目标数据一模一样的数据
-> 案例:
var p = { name: '张三' };
var p1 = p; // 是否存在拷贝
// 一般描述拷贝是指拷贝对象
p1 = {};
p1.name = p.name;
// 才是拷贝
-> 案例, 给 对象 p 提供一个 clone 方法, 完成拷贝
-> 案例:
有一辆汽车 Car: name=保时捷
有一个人 Person : name=张三
4.3-2. 如果对象的属性也是一个引用类型, 拷贝的时候不重新创建一个新的对象来实现该属性的拷贝, 那么就是浅拷贝. 即任何不完全的拷贝都是浅拷贝
4.3-3. 将两个对象完全从内存中隔离开, 就是深拷贝. 即每一个引用属性, 以及引用属性的引用属性, ... 全部拷贝出来.
6、构造函数的作用
6.1 js 中对象的动态特性
即 想要什么属性就可以提供什么属性
在 js 中 对象如果没有指定的属性, 只需要利用赋值就可以给对象提供该属性.
-> 点语法与关联数组语法
o.name = 'jim'; // 点语法赋值
console.log( o.name ); // 点语法取值
o[ 'name' ] = 'tom'; // +关联数组语法赋值
console.log( o[ 'name' ] ); // 关联数组语法取值
问题:
// 如果 name 是一个变量, 里面存储的是 字符串, 也是可以的
o[ name ] = 'jack'; // 对或不对?
-> 例创建一个 Person 对象
var p = {}; // 什么都没有的对象
// 根据需要添加成员
p.name = '张三';
p.age = 30;
p.gender = '男';
-> 简化: 提供一个创建 Person 对象的函数
function createPerson( name, age, gender ) {
var p = {};
p.name = name;
p.age = age;
p.gender = gender;
return p;
}
var p1 = createPerson( 'jim', 19, '男' );
var p2 = createPerson( 'lily', 18, '女' );
这个( 这种类型 )的函数就是用来创建对象的, 即生产对象. 常常将这类函数
称为 '工厂函数'
6.2 构造方法创建对象
-> 构造器中不需要 return 语句. 一般也可以不写
-> 调用构造器的时候, 使用 new 运算符引导
-> 在构造器中 this 表示当前对象. 给对象提供成员使用 this.xxx 的 方式
-> 将 createPerson 改造成构造器
// 构造器的定义
function createPerson( name, age, gender ) {
this.name = name;
this.age = age;
this.gender = gender;
}
// 调用构造器创建对象
var p = new createPerson( '李磊', 19, '男' );
-> 构造器创建对象的本质: 还是使用对象的动态特性
-> 首先执行 new 运算符. 即创建对象. 可以简单看做为 {} 的简写
var p = new ... '相当于' var p = {}
-> 调用构造器. 并隐含一个参数, 即刚刚创建的对象.
-> 在构造器中使用 this 引用刚刚创建出来的对象.
-> 构造器结束是 默认返回 this
-> 补充说明
-> 构造器的名字, 一般使用 Pascal 命名规则( 首字母大写的 )
-> 一般为了与其他面向对象的编程语言( C++, C#, Java )保持一致. 称构造函数名为类名
function Person( name, age, gender ) {
this.name = name;
this.age = age;
this.gender = gender;
}
// 调用构造器创建对象
var p = new Person( '李磊', 19, '男' );
7、异常与捕获
-> 异常
简单的说, 就是代码在执行过程中出现的错误. 并不是代码的语法错误.
-> 一旦出现异常, 其后的代码, 不再执行
-> try-catch 语法
1) try-catch 形态
try {
代码
} catch ( e ) {
代码
}
2) try-catch-finally 形态
-> 自定义抛出异常
一般可以封装函数完成特定的功能. 例如 tag 函数
function tag ( tagName ) {
if ( tagName 不是字符串 ) 抛出异常.
return document.getElementsByTagName ( tagName );
}
-> 抛出异常的语法
throw 对象
例:
throw new Error("作为人类只有男和女");
第二章:原型与继承
1、原型的概念
1.1 为什么需要原型
构造器创建对象的时候, 实际上会有成员重复
如果使用 构造器 this.方法名 = function .... 方式创建对象. 那么每一个对象
对应的方法就会重复.
解决办法就是让这个方法( 函数 )共享
-> 将函数写到外面, 那么 Person 在初始化对象的时候就不会再创建一个函数了.
只需要将 外面的函数引用 交给对象即可.
缺点: 一个对象可能有 n 多方法. 如果将所有的东西 都放到外面, 与其他库冲突的几率就会变大. 所以不宜采取该方法.
-> 将所有的方法( 函数 )都绑定到一个对象中.
-> js 原生就支持解决该问题的办法
每一个函数都有 一个属性 prototype
该属性指向一对象. 每一个函数的该对象都是存在.
(重点) 每一个由该函数作为构造器创建的对象, 都会默认连接到该对象上.
如果访问对象的方法, 而对象中没有定义, 就会在这个 构造函数.prototype
表示的对象中去找.
-> prototype 就是原型之意
针对构造函数而言, 原型就是 构造函数的 prototype 属性, 常常将其称为 原型属性.
针对实例对象而言, 原型就是 实例对象的 原型对象.
例:
function Person () {} // 有了构造函数, 和 原型
var p = new Person(); // 有了实例
1.2 一般如何使用原型对象
-> 简单的说就是将共享的方法放到原型中, 而独有数据与行为放在当前对象里
-> 例:
Person( name, age, gender, sayHello, eat, run )
-> 直接给原型对象添加成员
-> 直接替换原型对象( 注意: 手动的添加 constructor 属性, 表示对应的构造函数 )
1.3 __proto__
和 prototype 是否存在关系
早期浏览器是不支持 __proto__
火狐率先使用该属性, 但是是非标准
基本现在的新浏览器都支持该属性
-> 访问
使用构造函数, 就使用 prototype 属性访问原型
使用实例对象, 就使用 非标准的 __proto__ 属性访问原型
2、继承
2.1 什么是继承
自己没有, 别人有, 拿过来自己用, 就好像自己的一样.
-> 原型与实例对象
在 js 中, 方法定义在原型对象中, 而属性定义在实例对象中
调用方法的时候, 实例对象本身是没有该成员的, 但是依旧可以调用
该方法, 好像这个方法就是该实例对象的一样. 因此, 我们称该实例对象
继承自 原型对象
-> 任何一个实例, 都是继承自其原型对象的. 即原型式继承.
2.2 为什么需要继承
-> 编程的发展
复用( 重复使用 )
div 标签对象 nodeName, nodeType, nodeName, ...
appendChild, insertBefore, getElementsByTagName, ...
a 标签对象
baseElement
-> js 运行效率
共享特性
复用
2.3 传统的编程语言的面向对象操作
-> 对象: 是具有方法和属性的逻辑单元
在 js 函数是一个特殊的数据类型( 函数在 js 是一等公民 )
js 对象就是键值对, 值如果是数据, 那么键值就构成属性
如果值是函数, 那么就构成方法.
-> 创建方式
类 -> 实例化 -> 对象( 实例 )
class Person {
public string name;
public int age;
public string gender;
public void sayHello () {
// ...
}
}
// 类, 用来描述对象的结构, 它就是一个模板
Person p = new Person(); // 利用模板创建对象
// 访问属性
p.name
// 调用方法
p.sayHello();
传统的面向对象编程语言, 重点需要一个"模板", 即 类( class )
-> 传统的继承
传统的继承是模板的继承.
class Student : Person {
}
Student s = new Student();
// 注意此时 s 就可以调用 sayHello 方法
// 可以使用 name, age, 和 gender 属性了
2.4 相关概念
类 class 模板 构造函数, 类名就是构造函数名
子类 subclass 派生的模板 原型设置为指定对象的构造函数
实例 instance 某个类的对象 ...
实例成员(实例方法, 实例属性)
静态成员
静态方法 ... 直接绑定在函数上的方法
静态属性 ... 直接绑定在函数上的属性
// js 代码
function Person () {
this.name = '黄帝';
this.age = 0;
this.gender = '男';
}
// 不叫子类, 只是一个 Student 类
function Student() {
}
// 继承派生 成为子类
Student.prototype = new Person();
// 即完成派生
var s = new Student();
s.name // OK
2.4 属性访问原则( 重点 )
1) 对象在调用方法或访问属性的时候, 首先在当前对象中查询. 如果有该成员使用并停止查找.
2) 如果没有该成员就在其原型对象中查找. 如果有该成员即使用, 并停止查找.
3) 如果还没有就到 该对象的 原型对象 的 原型对象中查找.
...
4) 最后会查到 Object.prototype 上. 如果还没有即 返回 undefined.
2.5 如果修改原型对象中的属性值会怎样
给当前对象的原型提供的属性赋值, 实际上是给当前对象添加了该属性的新成员
并不会修改运行对象中的成员.
2.6 混入( mix )
var o1 = { name: '张三' };
var o2 = { age: 19 };
-----------
o2.name = o1.name
利用这个简单的赋值, 就可以将一个对象中的成员加到另一个对象中
混入使得 o2 具有了 age 和 o1 中的 name. 即将 o1 混入到 o2 中
混入也是一种继承的方式
2.7 如何使用代码实现混入
// 考虑需要执行, 写函数即可
// 由于将 一个 对象混入到 另一个对象. 所以有两个参数
function __mix__ ( obj, obj1 ) {
// 如何获得 obj1 中的所有成员?
for ( var k in obj1 ) {
obj[ k ] = obj1[ k ];
}
}
2.8 原型式继承
-> 写一个构造函数, 如果需要将其实例继承自某个特定的对象 o. 那么
只需要设置该构造函数的 prototype 属性为 o 即可
function Person {}
var o = { ... }
Person.prototype = o; // 继承
2.9 混合式继承
-> 混合就是将多个对象的各个功能混合到一起, 加到构造函数的原型对象上.
那么该构造函数创建的实例 就继承自多个对象了.
第三章:原型链
1、对象的原型链
原型链:原型链指的就是实例对象与实例对象的原型对象 及原型对象的原型对象 构成的链式结构
2、绘制原型链
实例对象的链式结构如下:
实例对象 --> 实例对象.__proto__(构造函数的原型)
--> 实例对象.__proto__.__proto__(Object.prototype) --> null
[1,2,3] --> Array.prototype --> Object.prototype --> null
{} --> Object.prototype --> null
4、Function创建函数
正常情况下会使用function关键来创建函数,函数也是一个对象, Function是函数对象的构造函数。
function one(){
}
var two = new Function("num1","num2","return num1+num2;")
注意:Function的参数都必须是字符串形式
Function的最后一个参数是创建的函数对象的函数体,前面的其他参数是这个函数对象的参数
5、函数相关知识点
5.1 arguments对象:
在每一个函数中都会有这样一个对象
这个对象中包含了调用函数时传递的所有的参数
$("div").html();
$("div").html("呵呵");
根据参数的个数的不同来做不同的处理,我们可以使用arguments对象来判断参数到底有几个,进一步再去做不同的处理
5.2 函数名.length:
得到的是定义函数的时候设置的参数的个数
function Person(name,age,gender){
}
Person.length : 3;
函数名.name:得到函数名
5.3 callee和caller:
这两个东西都是引用,他们引用的是函数,callee引用的是当前的函数,caller引用的是调用当前函数的那一个函数。
callee经常用在递归情况中:
传统的递归写法:
function foo(){
//foo = 123;
foo();//自己调用自己来实现递归
}
现在有了callee,可以不用上面的写法:
function foo(){
//foo = 123;
arguments.callee();
}
caller可以在函数中得到到底是哪一个函数调用了当前函数
function one(){
two();
}
function two(){
//得到当前函数的调用者one
two.caller;
}
one();
5.4 eval函数的使用形式:
eval( "var i=123;function a(){}" );
eval和Function的区别:
A.语法区别:eval语法更加简洁, Function语法会更复杂
B.作用域区别:eval执行的js代码的作用域是当前的作用域,一般会在全局作用域中产生作用,Function因为它是创建了一个函数,那么Function中的js代码的作用域就是这个函数的局部作用
重点:自调用函数
(function(){
//执行的代码
})();
因为自调用函数经常会使用到,什么时候会使用到自调用函数?
假如有一个函数只需要执行一次,我们就可以使用自调用函数来执行这个函数
补充:eval因为安全性的问题不建议使用
Function只在特定的情况下才会使用,需要生产动态函数的时候才会使用
一般我们还是会使用function来定义函数
5.5 将json格式的字符串转换为json对象的方式:
A.var o1 = eval( "("+json格式的字符串+")" );
B.var o2 = (new Function( 'return '+json格式的字符串))();
C.var o3 = JSON.parse( js格式的字符串 );
注意:第三种方式低版本的浏览器不支持
第三种方式要求js格式必须是严格的(键也必须用双引号引起来)
6、函数的链原型
第四章:作用域链
1、预解析
1.1 代码的预解析
-> 预解析
预
解析
提前的翻译解释, 在运行代码之前的一个解释.
-> 为什么需要它
-> 编译型语言: C, C++, C#, Java
就是需要一个 "翻译" 程序, 将源代码翻译成计算机可以读懂的二进制数据( 指令 ).
然后存储成可执行文件.
提前翻译好, 运行时直接执行得结果
-> 解释型( 脚本型 ): JavaScript, SQL, ...
代码在执行的时候, 有一个翻译程序, 读一句代码执行一句代码. 再读一句代码,
再执行一句代码.
一句一句的翻译执行. 每次运行都需要翻译一次.
-> 代码在执行之前, 需要快速的 "预览" 一遍. 那么可以尽可能提高执行效率.
1.2 在 js 中预解析的特点
-> 代码是如何执行的: 读取 js 文本, 预解析, 一句一句地执行
-> js 在预解析的过程中完成了声明部分的标记与变量作用域的设定
1.3 什么是 js 中的声明
-> 简单的说就是让 js 执行引擎知道有什么东西( 标识符 )
console.log( num ); // error: num is not defined
num(); // error: is not function
即代码在执行之前的预解析, 首先让 js 的执行引擎知道在当前运行环境中
有什么东西( 名字, 标识符 )是可以被使用的. 它是变量, 还是函数等?
-> 在 js 中有哪些声明:
1) 标识符的声明( 变量的声明 )
2) 函数的声明
-> 变量的声明:
语法: var 变量名;
目的: 告诉解释器, 有一个名字是一个变量, 在当前环境中可以被使用.
语句: 就是可以执行的东西.
var a = 123; 是一个语句
在使用 var 声明变量, 同时完成赋值的时候. 实际上, 预解析将其做了一定处理:
-> 凡是读取到 var 的时候, 就检查 var 紧跟的名字是否已经标记了
-> 如果没有标记, 就表明这个名字是一个标识符, 需要被标记
-> 如果已经被标记了, 那么 这个 var 被忽略
结论:
var a;
var a = 10;
等价
var a;
a = 10;
var a = 123;
var a = 456;
var a = 789;
等价于
var a = 123;
a = 456;
a = 789;
如果在代码中有多个 var 后面紧跟的名字是一样的. 那么只有第一个 var 起作用.
后面的所有 var 都会被自动的忽略.
-> 变量名提升
-> 函数的声明
-> 函数的各种定义形式
-> 声明式:
function func () {
console.log( '使用声明式定义' );
}
-> 表达式式( 匿名函数, 字面量函数, lambda 函数 ):
var func = function () {
console.log( '使用表达式式定义' );
};
-> ...
-> 特点:
1> 函数的声明是独立于语句. 不需要加分号结束. 也不能嵌入到代码表达式中.
2> 表达式式, 本质上是使用函数表达式( 字面量 )给变量赋值. 因此它是语句.
-> 表达式:
-> 将运算符与操作数连接起来的式子.
-> 就是一个有结果的代码单元( 不包括语句 )
var a; // 声明, 不是语句, 也没有结果
123 // 字面量, 有值, 是表达式. 是常量表达式
a = 123 // 赋值, 有值, 就是被赋值的那个值. 是赋值表达式.
function () {}
1.4 如果将变量的声明与函数的声明放在一起有些需要注意的情况
1) 函数的声明实际上包含两部分
1> 告诉解释器 xxx 名字已经可以使用( 函数名, 标识符 )
2> 告诉解释, 这个名字代表着一个函数( 变量里存储着函数的引用 )
2) 当函数声明与变量声明冲突的时候. 只看谁先有数据.
2、词法作用域
-> 作用域: 就是变量可以使用到不能使用的范围
-> 块级作用域:
-> 块: 代码块, 即 { }
-> 变量的使用从定义开始, 到其所在的块级作用域结束
// js 伪代码
{
console.log( num ); // error: num 未定义
var num = 123;
{
console.log( num ); // => 123
}
console.log( num ); // => 123
}
console.log( num ); // error: num 未定义
-> 代表语言: C, C++, C#, Java, ...
-> js 是词法作用域
-> 词法: 就是定义, 书写代码的规则.
-> 所以 所谓的 词法作用域, 就是 在书写代码的时候, 根据书写代码的结构
就可以确定数据的访问范围的作用域.
-> js 不受 块的影响, 即使在块中定义声明变量, 在块的外面依旧可以使用
console.log( num ); // => undefined
{
var num = 123;
}
console.log( num ); // => 123
-> 所谓的 js 的词法作用域, 就是根据预解析规则定义变量的使用范围, 全部代码中
只有函数可以限定范围. 其他均不能限定访问范围. 在内部是一个独立的作用范围结构.
-> 结论:
词法作用域就是描述变量的访问范围:
1> 在代码中只有函数可以限定作用范围. 允许函数访问外部的变量. 反之不允许.
2> 在函数内优先访问内部声明的变量, 如果没有才会访问外部的.
3> 所有变量的访问规则, 按照预解析规则来访问
3、作用域链
3.1 什么是作用域链
链指的就是访问规则
function foo() {
console.log( num );
}
--------------------
function func () {
function foo() {
console.log( num );
}
foo();
}
--------------------
function F () {
function func () {
function foo() {
console.log( num );
}
foo();
}
func();
}
... ...
由于这种一环套一环的访问规则, 这样的作用域构成一个链式结构. 所以直接称其为作用域链.
-> 作用域链是用来做变量查找的. 因此变量可以存储什么东西. 链中就应该有什么东西.
换句话说就是, 链里面存储的是各种对象. 可以将其想象成对象的序列( 数组 )
3.2 绘制作用域链的规则
1> 将所有的 script 标签作为一条链结构. 标记为 0 级别的链.
2> 将全局范围内, 所有的声明变量名和声明函数名按照代码的顺序标注在 0 级链中.
3> 由于每一个函数都可以构成一个新的作用域链. 所以每一个 0 级链上的函数都延展出 1 级链.
4> 分别在每一个函数中进行上述操作. 将函数中的每一个名字标注在 1 级链中.
5> 每一条 1 级链中如果有函数, 可以再次的延展出 2 级链. 以此类推.
3.3 分析代码的执行
当作用域链绘制完成后. 代码的的分析也需要一步一步的完成.
1> 根据代码的执行顺序( 从上往下, 从左至右 )在图中标记每一步的变量数据的变化
2> 如果需要访问某个变量. 直接在当前 n 级链上查找变量. 查找无序.
3> 如果找到变量, 直接使用. 如果没有找到变量在 上一级, n - 1 级中查找.
4> 一直找下去, 知直到 0 级链. 如果 0 级链还没有就报错. xxx is not defined.
第五章:闭包的作用
1、闭包
闭包的概念
-> 字面意义
闭: 关闭, 封闭
包: 包裹, 打包
闭包的含义就是一个被包裹的隔离的空间.
-> 在 js 中, 什么是闭包
在 js 中函数是一个具有变量作用域隔离特性的一个内存结构, 即为一个闭包.
function foo () {
}
闭包的间接访问
-> 使用 return 数据不能直接访问原来的数据, 那么可以考虑利用函数的返回访问原始数据
function foo () {
var num = 123; // 原始数据
function func () {
return num; // 它就是原始数据 num
}
return func;
}
2、函数是对象
A.函数可以当做对象进行赋值
function foo(){
console.log("hello");
}
//f1变量保存的是foo函数的引用
var f1 = foo;
f1();
//声明一个数组
var arr = [];
//数组的第0个元素保存的是f1函数的引用
arr[0] = f1;
//这样就相当于调用了foo函数
arr[0]();
//使用res变量保存f1函数调用的结果,所以res是undefined
var res = f1();
总结:有()叫调用函数,没有()叫函数引用。
B.函数可以作为另外一个函数的参数进行传递
function f1( a )
{
a();
}
function foo(){
console.log("这里是foo");
}
f1( foo );
/*
$("div").on("click",function(){
})
*/
/*
$("div").each(function(index,element){
})
function each(func)
{
for(var i=0;i
func(i,div[i]);
}
}
*/
function f1(){
return func;
function func(){
console.log("this is func");
}
}
var f = f1();
f();
通过返回一个对象的方式访问闭包中的数据:
function foo(){
var num=123,name = "jack";
return {
getName:function(){
return name;
},
setName:function( n ){
name = n;
}
}
}
//得到函数返回的对象
var obj = foo();
obj.setName("rose");
obj.getName();
闭包内部的数据如何进行访问,无法直接进行访问,但是可以间接进行访问:
A.返回一个函数来访问数据(只能访问一个数据)
B.返回一个对象来访问数据(支持访问多个数据,因为对象可以有多个方法)
推荐使用B。
3、闭包的应用
A.沙箱模式:
(function(){
//把你所有的代码添加在沙箱的匿名函数中
//此时在沙箱中运行的代码,不会影响到沙箱外的内容
//最主要就是避免了全局作用域的污染。
})();
B.事件的追加和移除。
C.Cache缓存
function Cache()
{
//Cache是一个函数对象,这个age成员就加在Cache函数上
Cache["age"] = 100;
}
delete语法:
delete 对象名["成员名"];//删除对象的某一个成员。
第六章:函数的四种调用模式
1、函数的四种调用模式
1.1函数调用模式
独立的函数调用的方式
function foo(){
this.age= 20;
this.name ="jack";
}
foo();//这一种独立的调用函数的方式,
//因为foo()是函数调用模式,所以函数中的this就是window对象
//所以当函数foo执行完毕之后,会给window对象添加name属性和age属性
注意:自调用函数,沙箱 都是使用函数调用模式来调用的。
(function(){
this//this就是window对象
})()
1.2方法调用模式
其实就是用一个对象来调用函数,如果想要用对象调用这个函数,我们需要将这个函数作为对象的成员,此时我们就称对象是函数的宿主对象。这个函数就是对象中的一个方法,这个函数就从属于对象,这个对象就是方法的主人(宿主)
function foo(){
this.age= 20;
this.name ="jack";
}
var obj = {};
obj.f1 = foo;
obj.f1();//当obj调用f1的时候,就是调用了函数foo,此时foo中的this就是obj对象,当f1执行完毕的时候obj就有age和name属性。
//这种将函数设置为数组中的一个元素,并且通过数组来调用函数的方式
//也是方法调用模式
var arr = [];//数组对象arr
arr[0] = foo;//arr的第0个元素是一个函数
arr[0]();//此时使用的是方法调用模式,调用这个方法的对象是数组
//arr.foo();
1.3构造器调用模式
就是使用new关键字来调用函数的方式。
function Person(){
this.name="jack";
this.age=20;
}
//当代码执行到new的时候,会创建一个实例对象。
//这个实例对象它是由Person函数创建出来,而且是以Person.prototype为原型的
//当new创建完了对象以后,用this指针指向将new创建出来的对象,就进入到Person函数中进行代码的执行
//因为this指针指向的是新创建出来的那个对象,所以我们会为新创建出来的那个对象添加name,age属性。
//最后我们将这个创建好的对象的引用赋值给p1
var p1 = new Person();
补充:我们可以在构造函数中写return语句,如果return后面跟的是简单数据类型的数据,那么这个return无效。
如果return后面跟的是引用类型的数据,那么我们就会返回这个引用类型的数据,而不会返回新创建出来的对象。
1.4上下文调用模式(借调模式)
也是一种调用函数的形式,这种上下文调用模式既可以使用函数调用模式来调用函数,也可以使用方法调用模式来调用函数。
A.apply方法
函数名.apply();
B.call方法
函数名.call();
apply方法的使用实行:
function foo(){
this.age = 998;
}
//这里是给window加了一个age属性
//foo();
//var obj = {};
//obj.f = foo;
//调用foo给obj对象添加了一个age属性
//obj.f();
//使用apply来实现函数调用调用foo
foo.apply(null);
foo.apply();
//使用apply来实现方法调用模式调用foo
var obj = {};
foo.apply( obj );
//使用上下文模式有什么好处:如果使用上下文模式进行函数调用模式调用,没有什么好处,主要的好处就在使用上下文调用模式来进行方法调用模式。
//使用上下文调用模式的时候,函数不需要成为对象的成员也可以进行调用。对象可以去借调别人的方法
//上下文最大的好处就是可以改变调用函数的对象
//Math.max random
//Array.splice Array.push
//var obj = {};
//Math.max.apply( obj ,[10,20]); // obj.max(10,20)
apply形式的调用传入参数:
函数名.apply( null, [参数数组] ); //函数调用模式
函数名.apply( 对象, [参数数组] ); //方法调用模式
例:
foo.apply(null,[ 1,2,3,4 ]);//该上下文调用等价于foo(1,2,3,4);
foo.apply(obj,[1,2,3]);//该上下文调用等价于obj.foo( 1,2,3 );
var arr = [ ];
var arr1 = [1,2,3,4];
[ ].push.apply( arr , arr1 );
//要借调的方法是[].push方法
//谁调用?arr调用
//相当于arr.push(1,2,3,4);
apply方法的练习:
在Math对象中有一个max的方法可以求得最大值
var points = [ {x:100,y:200},{x:50,y:88},{x:100,y:108} ]
求这些点里面最大的x值和最大的y值
var points = [ {x:100,y:200},{x:50,y:88},{x:100,y:108} ];
//x得到的就是所有的x坐标,即[100,50,100];
var x = (function(){
var arr=[ ];
for(var i =0;i arr.push(points[ I ].x); } return arr; })(); //y得到的就是所有的y坐标,即[200,88,108]; var y = (function(){ var arr=[]; for(var i =0;i arr.push(points[i].y); } return arr; })(); //使用借调的方式调用max方法并获得最大的x值与y值 //下面的代码相当于是:var maxX = Math.max(100,50,100); var maxX = Math.max.apply( null,x ); //下面的代码相当于是:var maxY = Math.max(200,88,108); var maxY = Math.max.apply( null,y ); foo.apply();// foo(); foo.apply(null);// foo(); foo.apply(obj);// obj.foo(); foo.apply(null,[1,2,3]);// foo(1,2,3); foo.apply(obj,[0]); // obj.foo( 0 ); foo.apply([1,2,3,4]); //[1,2,3,4].foo(); call形式的调用传入参数: 如果调用的函数不需要传参数,那么call调用的形式和apply一模一样。 但是如果调用函数的时候需要传参数就不一样了。 foo.apply(null,[1,2,3]);// foo(1,2,3); foo.apply(obj,[0]); // obj.foo( 0 ); 改成call形式: foo.call(null,1,2,3); foo.call(obj,0); call和apply最大的区别就是,apply的参数必须用数组包含起来 call的参数直接一个一个的写call调用的参数中就可以了。 简单的一个使用的时机: 如果这个函数需要传递一个参数,我们最好使用call。 如果这个函数需要多个参数,我们最好使用apply。 function foo(){ console.log( this ); } A.函数调用模式 foo();//函数调用模式中函数中this是window对象 B.方法调用模式 var obj = {}; obj.foo = foo; obj.foo(); //方法调用模式中函数的this是调用函数的那个对象 var arr = [] arr[0] = foo; arr[0](); //方法调用模式,此时函数中的this是数组对象arr C.构造器调用模式 var p1 = new foo();//构造器调用模式,此时函数中的this是新创建出来的那个对象,也就是p1对象 D.上下文调用模式(借调模式) foo.apply(null);//上下文调用模式,此时函数中的this是window,因为第一个参数是null,代表是用函数调用模式来调用foo,所以函数中的this是window foo.call(obj);//上下文调用模式,此时函数中的this是obj,因为call方法的第一个参数是obj,其实就是使用obj来调用foo函数,所以函数中的this是obj 2.1 bind调用模式:如果使用bind模式来调用函数,其实就是限制死就是用一个对象调用这个函数。 function foo() { } var obj = {}; //bind方法会返回一个函数,这个函数就是绑定了对象obj的一个函数 var f1 = foo.bind(obj); //当我们去调用f1的时候,其实就相当于是 obj.foo(); f1(); 2.2 object.prototype的成员,hasOwnProperty,toString 2.3包装类型:js为了基本类型的数据可以更加良好的进行使用,所以为基本类型数字,字符串,布尔提供了包装类型Number , String ,Boolean。 基本类型的数据在调用方法的时候会自动的将基本类型的数据转换成包装类型的数据。 使用上下文调用的时候,如果apply或call方法的第一个参数是基本类型的数据 那么就会将这个基本类型数据转换为包装类型。 2.4 语法糖getter,setter: var obj = (function(){ var num = 123; var gender = '男'; return { get num (){ return num; }, set num (v){ num = v; }, get gender (){ return gender; }, set gender ( g ){ if(g=="男" || g=="女"){ gender = g; } } } })(); obj.gender = '女'; console.log(obj.gender); 2.5 ES5中数组新引入的方法: A.forEach 语法结构: 数组.forEach( function(v,i){ //这个函数是用来遍历数组中每一个元素的 } ) B.map 语法结构: var res =数组.map( function(v,i){ //这个函数是用来遍历数组中每一个元素的 return 值; } ) map函数执行完毕之后,会返回一个新的数组,这个数组的元素就是由函数中的return的值决定、。 是由字符串,元字符组成的一个式子,我们可以使用这个式子来进行搜索,匹配。 A.匹配:看字符串中是否有匹配正则表达式的内容 B.提取:看字符串中是否有匹配正则表达式的内容,把它拿出来 C.替换:看字符串中是否有匹配正则表达式的内容,然后将它换成别的内容 A. var reg1 = new RegExp("正则表达式字符串"); B. var reg2 = /正则表达式字符串/; var reg = /abc/; //此时如果需要匹配的内容中包含了正则表达式所代表的内容 //那么就会返回true,如果没有匹配的内容,就返回false. var res = reg.test("需要匹配的内容"); . 任意一个字符 () 分组或者提升优先级 [] 匹配[]中出现的任意一个字符 | 或者。 a|bb|abc \ 转义字符,在正则表达式中有一些符号具有特殊的函数,如果需要匹配这些符号,必须进行转义。 * 限定*前面的字符 或 分组出现0或多次 + 限定+前面的字符 或 分组出现1或多次 ? 限定+前面的字符 或 分组出现0或1次 {n} 限定{}前面的字符 或 分组 出现n次 {n,} 限定{}前面的字符 或 分组 至少出现n次 {n,m} 限定{}前面的字符 或 分组 至少出现n次,至多出现m次 练习: var str = "李老师好漂亮哦"; //限定好漂亮可以出现1次或多次 var reg = /李老师(好漂亮)+哦/; ^ 限定^后面的字符或分组 必须是 字符串的开头内容 $ 限定$前面的字符或分组 必须是 字符串的结尾内容 练习:document.querySelectorAll(".a"); document.querySelectorAll("#a"); 我们现在拿到了一个字符串str,验证str是不是类名 var reg = /^\.(.)+/; 注意:^ $经常一起使用,用来限定字符串必须是哪些内容,必须以什么开始,必须以什么结尾 /^(邱老师好帅啊)$/; /邱老师好帅啊/.test("这是一个假新闻:邱老师好帅啊! 纯属造谣"); \s 空白字符,换行,tab \S 非空白字符 \d 数字 \D 非数字 \w 字母,数字,下划线 \W 非字母,数字,下划线 //exec会去匹配内容中是否有符合正则表达式对象的内容,如果有,就将这个内容 //添加到结果数组中去,最后将结果数组作为exec方法的返回值返回。 var 结果=正则表达式对象.exec(内容); 练习:var str = "2017-6-3"; var reg = /\d+/; var res = reg.exec(str);//res=>["2017"] 注意:如果需要进行循环提取,需要为正则表达式对象开启全局模式 var reg = /\d+/g; var reg2 = new RegExp("\d+","g"); 开启了全局模式之后,我们可以使用exec方法来进行多次提取,直到最后无法提取到匹配正则表达式的内容,返回的结果就是null。 正则提取的结果还可以再一次进行解析,分组提取:用()进行分组提取。 练习: var str = "2017-6-3"; var reg = /(\d+)-(\d+)-(\d+)/; //res = ["2017-6-3","2017","6","3"] //注意:第一个元素一定是整个正则匹配到的内容 var res = reg.exec( str ); console.log(res); 如果想要分组不被提取在结果数组中,你就可以使用?:元字符 var str = "2017-6-3"; //在第一个分组中加上?:,代表不提取第一个分组作为结果 var reg = /(?:\d+)-(\d+)-(\d+)/; //res = ["2017-6-3","2017","6","3"] //注意:第一个元素一定是整个正则匹配到的内容 var res = reg.exec( str ); console.log(res); 我们在进行正则匹配的时候可以进行分组,当分组得到的数据需要在后面继续进行匹配的时候,我们可以使用\数字的形式来反向引用前面的分组 // \1是反向引用前面第一个分组的数据,意思是前面分组匹配到的数据, // 在\1的地方还会再出现一次。 var reg = /<(\w+)>.+<\/\1>/; .默认情况下,正则表达式使用贪婪模式, 如果想要切换成非贪婪模式,在次数限定符后面加? 否定元字符:[^abc] 1.5四种调用模式总结
2、补充知识点
第七章:正则表达式
1.基本知识
1.1正则表达式概念
1.2正则表达式可以做什么事情
1.3创建正则表达式对象的方式
1.4使用正则表达式对象进行匹配
2.元字符
2.1基本元字符
2.2限定元字符
2.3首尾元字符:
2.4简写元字符
3.正则提取
10.反向引用
11其他