资讯专栏INFORMATION COLUMN

函数 闭包eeva iife

AnthonyHan / 3230人阅读

摘要:概述函数的声明命令函数表达式变量赋值命令后面不带有函数名。如果加上函数名,该函数名只在函数体内部有效,在函数体外部无效。同样的,函数体内部声明的函数,作用域绑定函数体内部。可以通过,达到调用函数自身的目的。

函数

函数是一段可以反复调用的代码块。函数还能接受输入的参数,不同的参数会返回不同的值。

1.概述
1.1函数的声明
1.2函数的重复声明
1.3圆括号运算符,return 语句和递归
1.4第一等公民
1.5函数名的提升
2函数的属性和方法
2.1name 属性
2.2length 属性
2.3toString()
3函数作用域
3.1定义
3.2函数内部的变量提升
3.3函数本身的作用域
4参数
4.1概述
4.2参数的省略
4.3传递方式
4.4同名参数
4.5arguments 对象
5.函数的其他知识点
5.1闭包
5.2立即调用的函数表达式(IIFE)
6eval 命令
6.1基本用法
6.2eval 的别名调用

函数

函数是一段可以反复调用的代码块。函数还能接受输入的参数,不同的参数会返回不同的值。

1.概述
1.1函数的声明
(1)function 命令
(2)函数表达式 变量赋值

var print = function(s) {}

function命令后面不带有函数名。如果加上函数名,该函数名只在函数体内部有效,在函数体外部无效。

var print = function x(){
console.log(typeof x);
};

x
// ReferenceError: x is not defined

print()
// function

用处有两个,一是可以在函数体内部调用自身,二是方便除错(除错工具显示函数调用栈时,将显示函数名,而不再显示这里是一个匿名函数)。

(3)Function 构造函数
var add = new Function(
"x",
"y",
"return x + y"
);除了最后一个参数是add函数的“函数体”,其他参数都是add函数的参数

// 等同于
function add(x, y) {
return x + y;
}

1.2函数的重复声明
由于函数名的提升
后面的声明就会覆盖前面的声明

1.3圆括号运算符,return 语句和递归
圆括号用于调用函数a()
遇到return后面句子都不执行,没有就返回undefined

函数可以调用自身,这就是递归(recursion)。下面就是通过递归,计算斐波那契数列的代码。

function fib(num) {
if (num === 0) return 0;
if (num === 1) return 1;
return fib(num - 2) + fib(num - 1);
}

fib(6) // 8

1.4第一等公民
函数只是一个可以执行的值

函数只是一个可以执行的值

function add(x, y) {
return x + y;
}

// 将函数赋值给一个变量
var operator = add;

// 将函数作为参数和返回值
function a(op){
return op;
}
a(add)(1, 1)
// 2

1.5函数名的提升
视为变量名,所以采用function命令声明函数时,整个函数会像变量声明一样
表达式形式是匿名函数
f();
var f = function (){};
// TypeError: undefined is not a function
上面的代码等同于下面的形式。

var f;
f();
f = function () {};
上面代码第二行,调用f的时候,f只是被声明了,还没有被赋值,等于undefined,所以会报错。因此,如果同时采用function命令和赋值语句声明同一个函数,最后总是采用赋值语句的定义。 因为两个都是声明 ,然后赋值 function形式是声明 var声明 最后赋值

var f = function () {
console.log("1");
}

function f() {
console.log("2");
}

f() // 1

2函数的属性和方法
2.1name 属性
2.1.1function f1() {}
f1.name // "f1"
如果是通过变量赋值定义的函数,那么name属性返回变量名。

2.2.2var f2 = function () {};
f2.name // "f2"

如果变量的值是一个具名函数,那么name属性返回function关键字之后的那个函数名。

2.2.3var f3 = function myName() {};
f3.name // "myName"

f3.name返回函数表达式的名字。注意,真正的函数名还是f3,而myName这个名字只在函数体内部可用

2.2.4name属性的一个用处,就是获取参数函数的名字。
var myFunc = function () {};

function test(f) {
console.log(f.name);
}

test(myFunc) // myFunc

2.2length 属性
函数定义之中的参数个数。

2.3toString()
返回一个字符串,内容是函数的源码。
Math.sqrt.toString()
// "function sqrt() { [native code] }
利用这一点,可以变相实现多行字符串。

var multiline = function (fn) {
var arr = fn.toString().split("n");
return arr.slice(1, arr.length - 1).join("n");
};

function f() {/*
这是一个
多行注释
*/}

multiline(f);
// " 这是一个
// 多行注释"

3函数作用域
3.1定义
3.1.1全局 一直存在在内存 到处可读取

3.1.2 函数作用域 变量只在函数种存在
对于var命令来说,局部变量只能在函数内部声明,在其他区块中声明,一律都是全局变量。

3.2函数内部的变量提升
函数作用域内部也会产生“变量提升”现象。var命令声明的变量,不管在什么位置,变量声明都会被提升到函数体的头部

3.3函数本身的作用域
作用域只与声明的地方有关
函数本身也是一个值,也有自己的作用域。它的作用域与变量一样,就是其声明时所在的作用域,与其运行时所在的作用域无关
var a = 1;
var x = function () {
console.log(a);
};

function f() {
var a = 2;
x();
}

f() // 1

函数x是在函数f的外部声明的,所以它的作用域绑定外层,内部变量a不会到函数f体内取值,所以输出1,而不是2。
3.3.1同样的,函数体内部声明的函数,作用域绑定函数体内部。

function foo() {
var x = 1;
function bar() {

console.log(x);

}
return bar;
}

var x = 2;
var f = foo();
f() // 1
上面代码中,函数foo内部声明了一个函数bar,bar的作用域绑定foo。当我们在foo外部取出bar执行时,变量x指向的是foo内部的x,而不是foo外部的x。正是这种机制,构成了下文要讲解的“闭包”现象。

4参数
4.1概述

4.2参数的省略
4.1.1无论提供多少个参数(或者不提供参数),JavaScript 都不会报错。省略的参数的值就变为undefined。需要注意的是,函数的length属性与实际传入的参数个数无关
4.1.2没有办法只省略靠前的参数,而保留靠后的参数。如果一定要省略靠前的参数,只有显式传入undefined。

function f(a, b) {
return a;
}

f( , 1) // SyntaxError: Unexpected token ,(…)
f(undefined, 1) // undefined
上面代码中,如果省略第一个参数,就会报错。

4.3传递方式
4.3.1函数参数如果是原始类型的值(数值、字符串、布尔值),传递方式是传值传递。在函数里面的是复制的另外一个值了

在函数体内修改参数值,不会影响到函数外部

var p = 2;

function f(p) {
p = 3;
}
f(p);

p // 2
上面代码中,变量p是一个原始类型的值,传入函数f的方式是传值传递。因此,在函数内部,p的值是原始值的拷贝,无论怎么修改,都不会影响到原始值。

4.3.2如果函数参数是复合类型的值(数组、对象、其他函数),传递方式是传址传递

传入函数的原始值的地址,因此在函数内部修改参数,将会影响到原始值。

var obj = { p: 1 };

function f(o) {
o.p = 2;
}
f(obj);

obj.p // 2
上面代码中,传入函数f的是参数对象obj的地址。因此,在函数内部修改obj的属性p,会影响到原始值。(外面和里面指向同一个地址)

4.3.3注意,如果函数内部修改的,不是参数对象的某个属性,而是替换掉整个参数,这时不会影响到原始值。

var obj = [1, 2, 3];

function f(o) {
o = [2, 3, 4];
}
f(obj);

obj // [1, 2, 3]

形式参数(o)的值实际是参数obj的地址,重新对o赋值导致o指向另一个地址,保存在原地址上的值当然不受影响。(外面和里面指向不同地址了)
4.4同名参数

如果有同名的参数,则取最后出现的那个值

function f(a, a) {
console.log(a);
}

f(1, 2) // 2

取值的时候,以后面的a为准,即使后面的a没有值或被省略,也是以其为准。

function f(a, a) {
console.log(a);
}

f(1) // undefined

function f(a, a) {
console.log(arguments[0]);
}

f(1) // 1

4.5arguments 对象
4.5.1只能在函数体内用
正常可以修改 严格无效
有length属性

var f = function(a, b) {
arguments[0] = 3;
arguments[1] = 2;
return a + b;
}

f(1, 1) // 5

4.5.2与数组的关系
数组专有的方法(比如slice和forEach),不能在arguments对象上直接使用

两种常用的转换方法:slice方法和逐一填入新数组。

var args = Array.prototype.slice.call(arguments);

// 或者
var args = [];
for (var i = 0; i < arguments.length; i++) {
args.push(arguments[i]);
}

4.5.3callee属性
arguments对象带有一个callee属性,返回它所对应的原函数。

var f = function () {
console.log(arguments.callee === f);
}

f() // true
可以通过arguments.callee,达到调用函数自身的目的。这个属性在严格模式里面是禁用的,因此不建议使用。
5.函数的其他知识点
5.1闭包
定义在函数内的函数
用处 1.读取函数内的变量

2.让这些变量始终保存在内存中

function createIncrementor(start) {
return function () {

return start++;

};
}

var inc = createIncrementor(5);

inc() // 5
inc() // 6
inc() // 7
上面代码中,start是函数createIncrementor的内部变量。通过闭包,start的状态被保留了,每一次调用都是在上一次调用的基础上进行计算。从中可以看到,闭包inc使得函数createIncrementor的内部环境,一直存在。所以,闭包可以看作是函数内部作用域的一个接口。

为什么会这样呢?原因就在于inc始终在内存中,而inc的存在依赖于createIncrementor,因此也始终在内存中,不会在调用结束后,被垃圾回收机制回收。

因为inc是全局的,使得闭包可以一直存在同时访问内部变量

3封装私有变量

function Person(name) {
var _age;
function setAge(n) {

_age = n;

}
function getAge() {

return _age;

}

return {

name: name,
getAge: getAge,
setAge: setAge

};
}

var p1 = Person("张三");
p1.setAge(25);
p1.getAge() // 25
上面代码中,函数Person的内部变量_age,通过闭包getAge和setAge,变成了返回对象p1的私有变量

注意,外层函数每次运行,都会生成一个新的闭包,而这个闭包又会保留外层函数的内部变量,所以内存消耗很大。因此不能滥用闭包,否则会造成网页的性能问题

5.2立即调用的函数表达式(IIFE)
()是运算符 表达式
function(){ / code / }();
// SyntaxError: Unexpected token (
产生这个错误的原因是,function这个关键字即可以当作语句,也可以当作表达式。
如果function关键字出现在行首,一律解释成语句。因此,JavaScript 引擎看到行首是function关键字之后,认为这一段都是函数的定义,不应该以圆括号结尾

// 语句
function f() {}

// 表达式
var f = function f() {}

5.2.1最简单的处理,就是将其放在一个圆括号里面。

(function(){ / code / }());
// 或者
(function(){ / code / })();
上面两种写法都是以圆括号开头,引擎就会认为后面跟的是一个表示式,而不是函数定义语句,所以就避免了错误。这就叫做“立即调用的函数表达式

任何让解释器以表达式来处理函数定义的方法,都能产生同样的效果,比如下面三种写法。

var i = function(){ return 10; }();
true && function(){ / code / }();
0, function(){ / code / }();
甚至像下面这样写,也是可以的。

!function () { / code / }();
~function () { / code / }();
-function () { / code / }();
+function () { / code / }();

5.2.2目的
1.不必为函数命名,避免污染全局变量
2.封装私有变量
// 写法一
var tmp = newData;
processData(tmp);
storeData(tmp);

// 写法二
(function () {
var tmp = newData;
processData(tmp);
storeData(tmp);
}());
上面代码中,写法二比写法一更好,因为完全避免了污染全局变量

6eval() 命令
6.1接受一个字符串当参数并当语录执行 否则报错
eval("var a = 1;");
a // 1

eval("3x") // Uncaught SyntaxError: Invalid or unexpected token
6.2
放在eval中的字符串,应该有独自存在的意义,不能用来与eval以外的命令配合使用。举例来说,下面的代码将会报错。

eval("return;"); // Uncaught SyntaxError: Illegal return statement

6.3如果eval的参数不是字符串,那么会原样返回。

eval(123) // 123

6.4作用域
var a = 1;
eval("a = 2");

a // 2
上面代码中,eval命令修改了外部变量a的值。由于这个原因,eval有安全风险

如果使用严格模式,(自己声明的不会影响,上面的问题依然有)eval内部声明的变量,不会影响到外部作用域。

(function f() {
"use strict";
eval("var foo = 123");
console.log(foo); // ReferenceError: foo is not defined
})()

(function f() {
"use strict";
var foo = 1;
eval("foo = 2");
console.log(foo); // 2
})()

eval最常见的场合是解析 JSON 数据的字符串,不过正确的做法应该是使用原生的JSON.parse方法

6.1基本用法
6.2eval 的别名调用
1.eval不利于引擎优化执行速度

2.引擎在静态代码分析的阶段,根本无法分辨执行
的是eval。

var m = eval;
m("var x = 1");
x // 1
上面代码中,变量m是eval的别名。静态代码分析阶段,引擎分辨不出m("var x = 1")执行的是eval命令

为了保证eval的别名不影响代码优化改进:用别名的话,里面都是全局变量

var a = 1;

function f() {
var a = 2;
var e = eval;
e("console.log(a)");
}

f() // 1

上面代码中,eval是别名调用,所以即使它是在函数中,它的作用域还是全局作用域,因此输出的a为全局变量。这样的话,引擎就能确认e()不会对当前的函数作用域产生影响,优化的时候就可以把这一行排除掉。

eval的别名调用的形式五花八门,只要不是直接调用,都属于别名调用,因为引擎只能分辨eval()这一种形式是直接调用。

eval.call(null, "...")
window.eval("...")
(1, eval)("...")
(eval, eval)("...")
上面这些形式都是eval的别名调用,作用域都是全局作用域。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/105995.html

相关文章

  • 重读你不知道的JS (上) 第一节五章

    摘要:词法作用域的查找规则是闭包的一部分。因此的确同闭包息息相关,即使本身并不会真的使用闭包。而上面的创建一个闭包,本质上这是将一个块转换成一个可以被关闭的作用域。结合块级作用域与闭包模块这个模式在中被称为模块。 你不知道的JS(上卷)笔记 你不知道的 JavaScript JavaScript 既是一门充满吸引力、简单易用的语言,又是一门具有许多复杂微妙技术的语言,即使是经验丰富的 Jav...

    worldligang 评论0 收藏0
  • 作用域闭包

    摘要:由于声明在函数内部,所以它拥有涵盖内部作用域的闭包,使得该作用域能够一直存活,以便在以后的任何时间进行引用。尽管本身并不是观察闭包的恰当例子,但他的确创建了一个封闭的作用域,并且也是最常用来创建被封闭起来的闭包的工具。 在讲解作用域闭包的内容之前,需要对以下概念有所掌握: JavaScript具有两种作用域:全局作用域和函数作用域,至于块作用域也不能说没有,比如说: try .....

    Anleb 评论0 收藏0
  • 讲清楚之 javascript 函数

    摘要:中函数是一等公民。小明小明调用函数时,传递给函数的值被称为函数的实参值传递,对应位置的函数参数名叫作形参。所以不推荐使用构造函数创建函数因为它需要的函数体作为字符串可能会阻止一些引擎优化也会引起浏览器资源回收等问题。 函数 之前几节中围绕着函数梳理了 this、原型链、作用域链、闭包等内容,这一节梳理一下函数本身的一些特点。 javascript 中函数是一等公民。 并且函数也是对象,...

    Keagan 评论0 收藏0
  • Effective JavaScript读书笔记(二)

    摘要:尽可能的使用局部变量,少用全局变量。正确的实现就是在函数体内部使用将声明成局部变量。在新特性中,引入了块级作用域这个概念,因此还可以使用,来声明局部变量。它们共享外部变量,并且闭包还可以更新的值。 变量作用域 作用域,对于JavaScript语言来说无处不在,变量作用域,函数作用域(运行时上下文和定义时上下文),作用域污染等等都跟作用域息息相关,掌握JavaScript作用于规则,可以...

    Yuqi 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<