面向对象编程重点回顾

递归

在函数内部直接或者间接的调用自己就是递归

递归的要素

  1. 自己调用自己
  2. 要有结束条件

化归思想

化繁为简,化难为易的过程,化是转化,归是归结。

练习:

  • 求前n项和
  • 求n!
  • 求n的m次方(求幂)
  • 斐波那契数列

递归获取后代元素

1
2
3
4
5
6
7
8
9
10
11
function getChildren(ele){
var result = [];
var children = ele.children;
for(var i = 0; i < children.length; i++){
var child = children[i];
result.push(child);
var temp = getChildren(child);
result = result.concat(temp);
}
return result;
}

作用域

什么是作用域?

变量的作用范围就是作用域

什么是词法作用域?(词法作用域又叫静态作用域)

代码在写好的时候,根据代码的书写结构,就可以确定变量的作用范围,这种作用域,就是词法作用域

什么是块级作用域?

使用代码块限定的作用域就是块级作用域,js中没有块级作用域

作用域链

函数可以创建作用域,函数中又可以声明函数,这样就形成了作用域套作用域的连式结构,称作作用域链

变量的搜索原则

  1. 现在使用变量的作用域中进行查找,如果找到了就直接使用
  2. 如果没有找到,就去上级作用域链中进行查找,如果找到了就直接使用
  3. 如果没有找到,就沿着作用域链一直向上查找,直到找到全局作用域

JS代码执行过程

  1. 预解析
  2. 执行

变量提升

在js代码预解析的阶段,会将所有的变量声明以及函数声明提升到其所在的作用域的最顶上,这就是变量提升(Hoisting)

变量提升的特殊情况

  1. 函数同名:都提升,后面的会覆盖前面的
  2. 函数与变量同名: 只提升函数,忽略变量声明
  3. 变量提升分作用域
  4. 变量提升分段(script标签)
  5. 条件式函数声明: 会被当做函数表达式处理,只提升函数名称,函数体不提升
  6. 形参的赋值是在变量提升过程之前

闭包

闭包的概念

可以访问独立数据的函数!

闭包要解决的问题

在函数外部无法访问函数内部声明的变量

闭包的基本模型

1
2
3
4
5
6
7
function outer(){
var data = "";
function inner(){
return data;
}
return inner;
}

闭包的原理

作用域,变量的访问规则,上级作用域中无法直接访问下级作用域中的变量,但是下级作用域可以直接访问上级作用域中变量

闭包的用途

  1. 保护变量,增加对于变量赋值的时候的校验逻辑
  2. 给函数新增一个私有的变量

for循环注册点击事件以及for循环setTimeout的问题

缓存

将数据进行临时的存储,以提升访问效率

常见的缓存

  1. 浏览器缓存
  2. 硬件缓存
  3. CDN
  4. 数据库缓存

    缓存解决递归实现的斐波那契数列存在的性能问题(重点)

实现jQuery缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function createCache(){
var cache = {};
var keys = [];
return function(key, value){
if(value != undefined){
cache[key] = value;
keys.push(key);
if(keys.length > 20){
delete cache[keys.shift()];
}
}
return cache[key];
}
}

jQuery的缓存源码

1
2
3
4
5
6
7
8
9
10
function createCache(){
var keys = [];
function cache(key, value){
if(keys.push(key + " ") > 20){
delete cache[keys.shift()];
}
return (cache[key + " "] = value);
}
return cache;
}

沙箱

1
2
3
4
5
(function(window){
//变量声明
//功能代码
//如果需要,使用window向外界暴露接口
})(window)

传参的目的

  1. 利于代码压缩
  2. 实现逻辑隔离

沙箱的用途

  1. 框架
  2. 组件
  3. 插件

函数的调用模式

函数调用模式

1
2
函数名();
//this--->window

方法调用模式

1
2
对象.方法名();
//this---->调用该方法的对象

构造函数调用模式

1
2
new 函数名();
//this----> new创建出来的实例

上下文调用模式

工厂模式和寄生模式创建对象(只是调用模式不同)

上下文调用模式

call

1
2
3
4
函数名.call(this要指向的对象, arg1..argN);
//1. 调用函数
//2. 将this指向第一个参数
//3. 将第二个及之后的所有的参数,作为实参依次传递给函数

apply

1
2
3
4
函数名.apply(this要指向的对象, 数组或者伪数组);
//1. 调用函数
//2. 将this指向第一个参数
//3. 将第二个参数中的数组或者伪数组,拆解开,将所有的元素依次作为实参依次传递给函数

注意事项

  1. 如果第一个参数为null或者Undefined,this指向window
  2. 如果第一个参数为值类型的数据,就会将该数据转换成对应的引用类型的数据,再讲this指向该引用类型数据

tip

一般情况下apply方法的传参特性会非常常用!!!

练习

  1. 转换伪数组
  2. 求数组最大值

forEach和map

forEach

1
2
3
4
5
数组.forEach(function(value, index, arr){})
//遍历数组,执行回调函数
//value:当前正在遍历的元素
//index:当前正在遍历的元素的索引
//arr:当前正在遍历的数组

map

1
2
3
4
5
数组.map(function(value, index, arr){})
//遍历数组,执行回调函数
//value:当前正在遍历的元素
//index:当前正在遍历的元素的索引
//arr:当前正在遍历的数组

map的返回值

将每次执行回调函数的返回值,组合成一个新的数组,作为map方法的返回值!

map的练习

  1. 将数组中的元素转成字符串
  2. 求最大值
  3. 生成随机数

Object.defineProperty

用来给对象添加属性的

1
2
3
4
Object.defineProperty(obj, propertyName, descriptor);
//obj 就是要给谁添加属性
//propertyName 要添加的属性名
//descriptor 属性的描述信息,也就是特性集合,是一个对象哦~

descriptor对象中的属性

  • writable 默认为false,表示属性是否可写
  • configurable 默认为false, 表示属性是否可以配置(删除)
  • enumerable 默认为false, 表示属性是否可以被遍历(for-in)
  • value 用来设置属性的值
  • get和set方法,和writable一样都可以用来设置属性是否可以写

严格模式

如何开启严格模式

1
2
"use strict"
'use strict'

严格模式中的特点

  1. 声明变量必须使用var
  2. 对象的属性不能重名
  3. 函数的形参名不能重复
  4. 八进制常量不允许使用
  5. eval有自己的作用域

案例(歌曲管理,Tab栏切换)

事件兼容性处理

三种注册事件的方式

  1. onclick简单形式
  2. addEventListener(type, handler, useCapture)
  3. attachEvent(type, handler)

addEventListener和attachEvent的区别

  1. 回调函数中获取事件对象的方式不一样,addEventListener通过回调函数的参数获取,attachEvent通过window.event获取
  2. 回调函数中的this指向不一致,addEventListener中this指向当前dom对象,attachEvent中this指向window

兼容性处理的通用函数

1
2
3
4
5
6
7
8
9
10
11
function registerEvent(ele, type, handler){
if(ele.addEventListener){
ele.addEventListener(type, handler);
}else if(ele.attachEvent){
ele.attachEvent("on" + type, function(){
handler.call(ele, window.event);
})
}else{
ele["on" + type] = handler;
}
}