JavaScript深入浅出(2)

函数和作用域

函数

函数是一块JavaScript代码,被定义一次,但可执行和调用多次。
JS中的函数也是对象,所以JS函数可以像其它对象那样操作和传递,所以我们也常叫JS中的函数为函数对象。

函数调用方式:

在JavaScript中一共有四种调用模式:

  • 对象方法调用模式:o.method();
  • 函数调用模式: foo();
  • 构造器调用模式 : new Foo();
  • apply/call/bind调用模式: func.call(0);
函数定义:函数声明与函数表达式

5
1.函数声明:

1
2
3
function fnA(ar0,arg1,arg2){
//函数体
}

2.函数表达式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var a = function(arg0,arg1,arg2){
//函数体
};

// IEF(Immediately Executed Function),立即执行函数
/*在function前面加!、+、 -甚至是逗号等到都可以起到函数定义后立即执行的效果,而()、!、+、-、=等运算符,都将函数声明转换成函数表达式,消除了javascript引擎识别函数表达式和函数声明的歧义,告诉javascript引擎这是一个函数表达式,不是函数声明,可以在后面加括号,并立即执行函数的代码。
加括号是最安全的做法,因为!、+、-等运算符还会和函数的返回值进行运算,有时造成不必要的麻烦。*/

(function() {
// do sth
})();
// first-class function
return function() {
// do sth
};
// NFE (Named Function Expression)
var add = function foo (a, b) {
// do sth
};

3.使用Function构造器函数(不推荐)
如:var sum = new Function(num1,num2,"return num1 + num2")

1
2
3
4
5
6
7
8
9
10
11
12
// CASE 1;localVal仍为局部变量
Function('var localVal = "local"; console.log(localVal);')();
console.log(typeof localVal);
// result: local, undefined

// CASE 2;local不可访问,全局变量global可以访问
var globalVal = 'global';
(function() {
var localVal = 'local';
Function('console.log(typeof localVal, typeof globalVal);')();
})();
// result: undefined, string

函数属性 & arguments

6
除了声明时定义的形式参数,每个函数接受两个附加的参数:this和arguments,参数this在面向对象编程中非常重要,它的值取决于调用的模式。
函数的arguments属性包含了调用函数时传入的所有参数,而不管函数的声明中是否定义了这些形参;arguments不是数组,只是一个“类似数组”的对象(在函数中运行arguments instanceof Array;返回false)。可以通过Array.prototype.slice.apply(arguments)

apply/call方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function foo(x, y) {
console.log(x, y, this);
}
foo.call(100, 1, 2); // 1, 2, Number(100)
foo.apply(true, [3, 4]); // 3, 4, Boolean(true)
foo.apply(null); // undefined, undefined, window
foo.apply(undefined); // undefined, undefined, window

//严格模式
function foo(x, y) {
'use strict';
console.log(x, y, this);
}
foo.apply(null); // undefined, undefined, null
foo.apply(undefined); // undefined, undefined, undefined
bind方法

作用1: this执行bind()中的对象

1
2
3
4
5
6
7
8
9
10
this.x = 9;
var module = {
x: 81,
getX: function() { return this.x; }
};
module.getX(); // 81
var getX = module.getX;
getX(); // 9
var boundGetX = getX.bind(module);
boundGetX(); // 81

作用2:柯里化currying.bind(this,a):第一个参数为this指向,也可以不指明,用undefined/null,第二个为参数,即传入对象中的第一个参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function add(a, b, c) {
return a + b + c;
}
var func = add.bind(undefined, 100);
func(1, 2); // 103
var func2 = func.bind(undefined, 200);
func2(10); // 310

function getConfig(colors, size, otherOptions) {
console.log(colors, size, otherOptions);
}
var defaultConfig = getConfig.bind(null, "#CC0000", "1024 * 768");
defaultConfig("123"); // #CC0000 1024 * 768 123
defaultConfig("456"); // #CC0000 1024 * 768 456

bind与构造函数一起使用时,其this指向消失。

1
2
3
4
5
6
7
function foo() {
this.b = 100;
return this.a;
}
var func = foo.bind({a:1});
func(); // 1
new func(); // {b : 100}

bind方法模拟:浏览器兼容方案:
7

this

在一个函数中,this总是指向当前函数的所有者对象,this总是在运行时才能确定其具体的指向, 也才能知道它的调用对象。

全局的this(浏览器)
1
2
3
4
console.log(this.document === document); // true
console.log(this === window); // true
this.a = 37;
console.log(window.a); // 37
一般函数的this(浏览器)
1
2
3
4
5
6
7
8
9
10
function f1(){
return this;
}
f1() === window; // true, global object

function f2(){
"use strict"; // see strict mode
return this;
}
f2() === undefined; // true
作为对象方法的函数的this
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var o = {
prop: 37,
f: function() {
return this.prop;
}
};
console.log(o.f()); // 37

var o = {prop: 37};
function independent() {
return this.prop;
}
o.f = independent;
console.log(o.f()); // 37
对象原型链上的this
1
2
3
4
5
var o = {f:function(){ return this.a + this.b; }};
var p = Object.create(o);
p.a = 1;
p.b = 4;
console.log(p.f()); // 5
get/set方法与this
1
2
3
4
5
6
7
8
9
10
11
12
13
function modulus(){
return Math.sqrt(this.re * this.re + this.im * this.im);
}
var o = {
re: 1,
im: -1,
get phase(){
return Math.atan2(this.im, this.re);
}
};
Object.defineProperty(o, 'modulus', {
get: modulus, enumerable:true, configurable:true});
console.log(o.phase, o.modulus); // logs -0.78 1.4142
构造器中的this
1
2
3
4
5
6
7
8
9
10
11
function MyClass(){
this.a = 37;
}
var o = new MyClass();
console.log(o.a); // 37
function C2(){
this.a = 37;
return {a : 38};
}
o = new C2();
console.log(o.a); // 38
call/apply方法与this
1
2
3
4
5
6
7
8
9
10
11
function add(c, d){
return this.a + this.b + c + d;
}
var o = {a:1, b:3};
add.call(o, 5, 7); // 1 + 3 + 5 + 7 = 16
add.apply(o, [10, 20]); // 1 + 3 + 10 + 20 = 34

function bar() {
console.log(Object.prototype.toString.call(this));
}
bar.call(7); // "[object Number]"
bind方法与this
1
2
3
4
5
6
7
function f(){
return this.a;
}
var g = f.bind({a : "test"});
console.log(g()); // test
var o = {a : 37, f : f, g : g};
console.log(o.f(), o.g()); // 37, test

闭包

闭包是指有权访问另一个函数作用域中的变量的函数。
创建闭包的常见的方式,就是在一个函数内部创建另一个函数,通过另一个函数访问这个函数的局部变量。
闭包其实就是一个函数;如果一个函数访问了它的外部变量,那么它就是一个闭包。从技术上来讲,在Javascript中,每个function都是闭包,因为它总是能访问在它外部定义的变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function outer() {
var localVal = 30;
return function() {
return localVal;
}
}
var func = outer();
func(); // 30

!function() {
var localData = "localData here";
document.addEventListener('click',
function(){
console.log(localData);
});
}();

闭包优缺点
优点:

  • 灵活和方便
  • 封装
    缺点:
  • 空间浪费
  • 内存泄漏
  • 性能消耗
闭包常见错误——循环闭包
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
document.body.innerHTML = "<div id=div1>aaa</div>"
+ "<div id=div2>bbb</div><div id=div3>ccc</div>";
for (var i = 1; i < 4; i++) {
document.getElementById('div' + i).
addEventListener('click', function() {
alert(i); // all are 4!
});

//修改如下:
document.body.innerHTML = "<div id=div1>aaa</div>"
+ "<div id=div2>bbb</div><div id=div3>ccc</div>";
for (var i = 1; i < 4; i++) {
!function(i) {
document.getElementById('div' + i).
addEventListener('click', function() {
alert(i); // 1, 2, 3
});
}(i);
}

作用域

包括:全局作用域、函数作用域、eval作用域
8

作用域链

执行上下文

执行上下文(Execution Context,缩写EC)

变量对象

变量对象(Variable Object, 缩写为VO)是一个抽象
概念中的“对象”,它用于存储执行上下文中的:

  1. 变量
  2. 函数声明
  3. 函数参数

变量初始化阶段,VO按照如下顺序填充:

  1. 函数参数 (若未传⼊,初始化该参数值为undefined)
  2. 函数声明 (若发生命名冲突,会覆盖)
  3. 变量声明 (初始化变量值为undefined,若发生命名冲突,会忽略。 )
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    function foo(x,y,z){
    function func(){};
    var func;
    console.log(func);
    }
    foo(100); // function func(){}

    function foo(x,y,z){
    function func(){};
    var func=1;
    console.log(func);
    }
    foo(100); // 1

    //测试
    alert(x); // function
    var x = 10;
    alert(x); // 10
    x = 20;
    function x() {}
    alert(x); // 20
    if (true) {
    var a = 1;
    } else {
    var b = true;
    }
    alert(a); // 1
    alert(b); // undefined
坚持原创技术分享,您的支持将鼓励我继续创作!