每周一段JavaScript权威指南-第三周

Posted by Wings on 2020-05-22

第三周

(第八章)

finish


1:第八章(函数)

函数是一段:只定义一次,但是可以被执行或调用任意次的javascript代码。每次调用函数时会产生一个值—调用上下文(this指向)。若函数挂载在一个对象上,作为对象的一个属性,则称为对象方法。调用函数时this指向该对象。

1、函数定义

定义&&声明:

1
2
3
4
5
6
7
8
9
// 定义表达式
var fun = function() {}
// 生命表达式
function fun() {}
// 自我调用:(此时一定要有一个终止条件,防止无限递归,造成内存泄漏)
function self() {
if(xxx) return;
slef();
}

2、函数调用:

1、调用方式:
    1、作为函数 -》function a() {}
    2、作为方法 -》obj.a = function(){}
    3、作为构造函数 -》new A(){}
    4、通过call/apply间接调用。 a.call(context, arguments);
2、构造函数调用:
    构造函数调用创建一个新的空对象(或者是包含构造函数中this指向的属性对象);
    这个对象继承自构造函数的prototype(obj.__proto__ === Objcet.prototype)。
    构造函数试图初始化这个新创建的对象,并将这个对象用作其调用上下文。
    即构造函数中的this指向这个新对象。如果构造函数使用return返回一个值。则新实例则等于这个返回值。
3、间接调用:
    通过apply/call间接的调用方法并指定具体的调用上下文(this)

3、函数的参数:

1、arguments是指向实参对象的引用。且它是一个类数组对象。
2、在严格模式下的arguments:
    举例验证严格与非严格模式下arguments的区别:
1
2
3
4
5
6
function a(x) {
arguments[0] === x;
arguments[0] = null;
console.log(x);
// 在严格模式下输出的x依然为传入值,但在非严格模式下输出的x为null
}
3、callee和caller属性:
是argument的自带属性。callee指代当前正在执行的函数。caller则指代调用当前在执行函数的函数。
在严格模式中不可用。caller是非标准,但是浏览器厂商大多实现了。

4、作为值得函数:

函数不仅是一种语法也是值。也就是说函数可以赋值给变量。
即function x(fun) {fun();} => 传入fun是一个方法。

提前剧透:一般函数式编程会把函数当作一个值来传入一个函数,并且像操控对象一样操控它。

5、闭包:

JavaScript采用的是词法作用域。也就是说,函数的执行依赖于变量作用域。这个作用域是在函数定义的时候就决定的。而不是在调用的时候确定的。

为了实现这种逻辑:函数对象的内部不仅包含代码逻辑,还必须引入当前的作用域链。

函数对象可以通过作用域链相互关联起来,函数体内部的变量都可以保存在函数作用域内。(闭包)

复习作用域链:
假设将局部变量看做成一个自定义对象的属性(obj.x)。这个自定义对象可看作当前作用域链的指向(或者可以理解为挂载)对象

每一段js代码都有一个关联作用域链。当使用局部变量时,可以理解为在当前作用域对象上找该属性,如果没找到,顺着作用域链继续向上查找。直到找到顶部,若没有则报错。

所有作用域链的顶部都死global全局对象

一个正常函数作用域链包含两个对象。一个是当前函数作用域对象,另一个是全局对象。
当是嵌套函数时,至少包含3个作用域对象。

当定义一个函数时,其实是保存了一个作用域链,但此时当前函数的作用域对象没有挂在该作用域链上。

当执行一个函数时,函数会创建一个新对象,将所有变量属性挂在它身上,并且该对象被添加在之前保存的作用域链上。

在内嵌函数中,每次调用外部函数,内部函数的作用域链都会新生成(都是不同的)。因为每次调用外部作用域链也是不同的。

闭包:调用函数时所指向的作用域链和定义时的作用域链不是同一个。(即外部函数将嵌套函数作为返回值返回时)。

我的理解是在定义时生成了一个作用域链-》function p() {return function c(){}};

而使用时,首先 var c = p(); 此时生成了一个关于c对象的新作用域链。所以作用域链有所不同。

1
2
3
4
5
6
7
8
9
10
// 举个例子:
function p() {
var arg = ''
return function c() {console.log(arg)}
}
var a = p(); // 作用域链:a -> c -> p;
var b = p(); // b -> c -> p;
// p中的arg持续在a/b的作用域对象上。所以不会被垃圾回收。持续存在。
// 而且a,b是两个不同的作用域对象(新的)。
// 这也说明了,每次声明都会生成一个新的作用域对象和作用域链。

6、函数的属性、方法、构造函数

1、函数的length:
    代表函数期望的实参个数。而arguments.length则代表函数实际传入形参的个数。
2、prototype:
    每个函数都有该属性,该属性指向一个对象,该对象即为原型对象。当函数用作构造函数时,新创建的对象就会将该原型对象挂在自己的原型链上。
3、call/apply:
    第一个参数为执行函数上下文的指定(this指向)。在严格模式中,this严格按照传入的值指定。在非严格模式中,若传入的是null或undefined则this指向的是全局对象。
4、bind:
    将函数绑定在某个对象上(第一个参数传入的值机位该对象)
    若将bind用作构造函数,则将忽略绑定的对象,而是以构造函数的方式进行调用。
    bind生成的函数无prototype属性,所以新实例将继承原始构造函数的prototype。
    bind参数为持续绑定的(实参在调用时被绑定)。举例:
1
2
3
4
5
// 非严格模式下(严格模式null会报错)
function a(x, y) {this.x = x; this.y =y;};
var b = a.bind(null, 1);
// b(2) => x == 1 ; y == 2;
var c = new b(3); // c => {x: 1, y: 3}
5、toString:
    一般返回函数的完整源码字符串形式。内置函数返回的则是一个[native code];
6、Function()构造函数:
    可以在运行时动态创建函数并编译。
    它创建的函数的this永远指向顶层。
    eg: var f = new Function('x', 'y', "return x * y;") => 
    var f = function(x, y) {return x * y};
7、可调用对象:
    可以调用执行但不是真正的函数对象(概念类似类数组)
    举例document.getElementById.
    Regexp();

7、函数式编程:

在Javascript中,可以像操控对象一样操控函数,不关心函数发生的状态,而只关心得到的结果。即为函数式编程。

类似Array.map(function(el) {});传入了函数,但是开发者不知道map对函数做了怎样的处理。只知道返回的el参数为数组的每个元素。

1、高阶函数:   
    接受一个或多个函数,并返回一个新函数以供调用。eg:
1
2
3
4
5
6
7
8
function a(f, g) {
return function() {
// 内部函数的this指向了调用他们的对象。
f.call(this, g.apply(this, arguments));
}
}
// var b = a(fun1, fun2);
// b(arg);
2、不完全函数:
    直接上例子:
    fun(1, 2, 3, 4) => fun(1, 2)(3, 4);
3、记忆:
    利用闭包保存局部变量的技术,将已经处理过的值保存在局部变量中,并且在下一次使用函数时,优先查询变量中是否含有该值。
1
2
3
4
5
6
7
8
function m(f) {
var cache = {};
return function () {
// 函数处理 得到一个唯一标识key(对应一种操作). value(对应该操作的值)
if(key in cache) return cache[key];
return cache[key] = value
}
}

总结:

1、this指向调用上下文
2、
    1)定义表达式:var a = function(){}; 声明表达式:function a() {};
    2)函数名称自我调用
    3)定义表达式出现任意位置,但是声明表达式不能出现在循环中。
3、
    1)函数的四种调用方式。
    2)嵌套函数的this不指向调用他的函数。若是方法调用则this指向调用他的对象,若是函数调用this指向全局或者undefined
    3)构造函数的实例上下文为自己。vm.__proto__ == Function.prototype;
4、
    1)arguments指向实参对对象引用。
    2)严格模式中arguments无法修改形参的值,非严格模式中可以。
5、
    1)函数作用域是在定义时确定的。
    2)函数对象通过作用域链连接,并可以将变量保存在内部。这类使用方法我们称为闭包
    3)定义和调用函数不是相同作用域链
    4)每次定义表达式后,都会形成新的作用域链。
6、
    1)call/apply的第一个参数是调用上下文(this指向)
    2)bind不立即执行,持续绑定传入的形参。(其实可以理解为定义时,作用域对象就把传入的形参绑定在自身属性了)
    3)bind生成的函数的length是函数参数个数(绑定过的个数)。
    4)函数的length是定义时需要的形参个数。
    5)bind方法生成的函数无prototype,所以生成实例原型链挂载的是指向原函数的原型对象。
    6)Function构造函数生成的作用域对象为Global。
7、
    1)像操控对象一样操控函数,不关心传入函数的内容和状态称为函数式编程模式。
    2)高阶函数,接受一个或多个函数并返回函数。
    3)不完全函数;
    4)记忆:函数中利用闭包来保留一个对象用于存储历史数据。