用生命谱写代码的赞歌

0%

函数与闭包

Function

  • 声明式
1
2
3
function foo() {

}
  • 表达式
1
2
fnName();
var fnName = function() {};
  • 构造函数方式

    1
    var fn = new Function("arg1", "arg2", "arg3", ..."argN", "body");
    • “arg1”, “arg2”, “arg3”, …”argN” 为生成函数的形参列表,且为可选的参数列表
    • “body” 为生成函数的函数体部分
    • 如果不给构造函数传参数的话,就会创建一个没有形参列表也没有函数体实现的函数。
    • 如果只传入一个参数,那么这个参数值会交给 “body” 形参。

几个概念

  • 函数对象:在绘制函数的原型链的时候,将函数称为函数对象
  • 除了函数,都称为 普通对象
  • 非严格模式下,声明的全局变量都是 window 对象的属性
  • 函数的名字存储的是函数对象的地址

函数相关属性

  • caller: 返回调用当前函数的函数
    • 所谓函数调用函数 就是指在函数体内部调用其他函数
  • length: 记录的是形参的个数
  • name: 记录函数的名字

arguments 伪数组对象,用于存储实参

  • arguments.callee: 返回正在执行的函数。
  • length: 存储实参的个数
  • 当一个匿名函数作为某个对象的属性值时,在其内部只有使用 arguments.callee 来获取函数
  • 用途:可以用在不定项形参个数的函数实现
  • 函数没有重载,可以用arguments模拟函数重载

函数四种调用模式

确定调用模式的作用:确定this执向的

  1. 普通函数执行模式:声明一个函数后,直接通过函数的名字调用。

    1
    2
    3
    4
    function foo() {
    console.log(this); // this -> window
    }
    foo(); // 普通函数执行模式
  2. 构造函数模式: 通过 new 操作符创建一个对象,此时该函数的执行模式为 构造函数模式

    1
    2
    3
    4
    5
    function fn() {
    console.log(this); // this -> 构造函数创建出来的对象
    }
    var f = new fn; // 构造函数执行模式
    var ff = new fn;
  3. 方法调用模式:通过对象来调用

    1
    2
    3
    4
    var o = {say: function() {
    console.log(this); // this -> 方法的调用者, 即对象o。
    }};
    o.say(); // 方法调用模式
  4. 上下文模式 ( call/apply 模式):

    • 作用:动态改变 this 的指向
    • this -> call|apply方法的第一个参数
    1
    2
    3
    4
    5
    function print() {
    console.log(this.name);
    }
    var obj = {name: "tom"};
    print.call(obj); // tom

call 和 apply 的区别

  • fn.call(thisObj, arg1, arg2, arg3, …argN);

    • thisObj –> 在fn函数内部的this指向(必需)
    • arg1, arg2, arg3, …argN –> 函数fn在执行时,传入的实参。(可选)
  • fn.apply(thisObj, [array]);

    • thisObj –> 在fn函数内部的this指向(必需)
    • 数组对象 –> 将数组的所有元素值当做fn执行时的实参。(可选)

上下文模式应用

  • 借用方法

    • 数组合并
    • 借用Math对象方法
    • 获取对象类型
    • 实现伪数组对象转换为真数组
  • 实现借用构造函数继承

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    function Person(name, age, gender, high) {
    this.name = name;
    this.age = age;
    this.gender = gender;
    this.high = high;
    }

    function Teacher(name, age, gender, high, codeNo, cellphoneNo) {
    Person.apply(this, Array.prototype.slice.call(arguments, 0, 4));
    this.codeNo = codeNo;
    this.cellphoneNo = cellphoneNo;
    }

    var t = new Teacher('guojing', 18, 'boy', '226', '91248', '13813813811');
    console.log(t);
  • 改变this指向

构造函数的执行过程

  • 先创建一个空对象
  • 将构造函数的作用域 交给 上述对象 || this -> 空对象
  • 开始执行构造函数内部代码
  • return this

注意: 在构造函数中,如果 显式 返回一个基本数据类型数据 会被忽略掉。当然返回 对象 就不会

实例成员与静态成员

  • 实例成员:通过构造函数创建出来的对象上的成员,称为实例成员
  • 静态成员:函数对象上的成员,被称为静态成员

区别:实例成员必须先创建实例,再访问;而静态成员直接通过函数的名字即可访问。

  • 在实际开发时,如果遇到 工具类方法,此时可以考虑写成静态成员。
  • 静态成员相对于实例成员好处为:不需要创建对象,直接通过函数名字来访问成员。

递归:函数调用自己

  • 场景:在遍历 树形结构 时,优先考虑使用递归方式。
  • 摸索规律,找到一个简单的方式来解决复杂问题 。

词法作用域

  • 全局作用域
  • 局部作用域
  • 全局变量
  • 局部变量
  • 定义:变量的作用域是由 代码书写的位置 决定,而不是 变量被使用的位置 决定;只有函数可以限定作用域。

js中预解析

  • 词法分析: 在此阶段,如果代码有语法错误会直接抛出异常。
  • 变量名提升 和 函数名提升

函数声明最好不要放在if语句块内部。

  • 在现代浏览器不会将函数声明提升
  • 在早期浏览器会得到提升。

作用域种类

  • 弱类型语言:大部分为静态作用域
  • 强类型语言:动态作用域

in

  • 语法: in 左边的操作数必须为 字符串类型,表示属性的名字,如果不是,会尝试将其转换成字符串,如果失败了,就报错。右边的操作数为对象。不为对象,报错。
  • 含义:判断对象是否能访问到该属性,如果能,就返回 true,否则返回 false。

delete

  • 可以删除对象的指定属性
  • 注意: 在操作DOM对象时,只能删除通过”.”或”[]”,方式添加的自定义属性。

eval 与 Function

  • 共同点
    • 都可以将一个字符串当做一段js代码来执行。
  • eval 具有作用域安全问题,有可能污染变量,而Function并不会。
  • 可以肆意在全局上执行一段字符串代码,会造成网站不安全。

闭包

就是可以访问其他函数内部数据的函数。

  • 在js中,函数内部的数据在外部无法访问。
  • 为了访问到函数内部的数据,提出闭包技术。

闭包的作用

  • 计数器
  • 沙箱模式 – 匿名自调用函数。很多的框架都使用了沙箱模式
    • 与外界隔离,即可以分割作用域
    • 内部代码自执行。
    • 在实际开发中,可以考虑将全局对象当做实参传入沙箱内部的变量,以提高 js 性能。
    • 用例:如果在开发中,遇到只需要执行一次的代码块。可以将其放到沙箱内部。
  • 缓存 – 优化fib递归调用性能
  • 实现面向对象的封装
    • 在Java 中,对象的属性可以添加访问修饰符,private, public,而在 js 并没这些关键字
    • Object.defineProperty
  • 科里化:将一个 拥有多个参数 的函数转化成一个 单一参数 函数的形式。

闭包的优缺点

  • 缺点: 常驻内存,增大内存的开销,使用不当就会造成内存泄漏。
  • 优点:
    • 缓存
    • 实现封装性
    • 避免污染全局对象
    • 处理逻辑的连续性。
  • 应用
    • 参数复用
    • 延迟计算/执行
    • 提前返回
1
2
3
4
5
6
7
8
9
10
11
// 闭包,返回sayAlert函数的执行需要依赖say667()函数的num变量,所以num变量在say667()函数执行后不会回收
function say667() {
var num = 666;
var sayAlert = function() {
console.log(num); // 667
}
num++;
return sayAlert;
}
var sayAlert = say667();
sayAlert();

变量的搜索原则

当访问一个变量时,

  • 首先在当前作用域上查找,如果找到就直接使用,并停止查找;
  • 如果没有找到,就会向上一级作用域上查找,如果找到就直接使用,并停止查找;
  • 如果还没有找到就继续向上一级作用域查找,直到全局作用域,如果找到就直接使用,并停止查找;
  • 否则报错(xxx is not defined.)

在变量搜索的时候,如果访问的是全局变量,那么会搜索整个作用域链。性能会降低。在实际开发时,可以将常用的全局对象传入局部变量内。在同级链上的变量,互相不能访问。