JavaScript逐点突破系列之函数知识点梳理,建议反复看有惊喜

}

const f = function(x) {

return x * x * x;

}

let numbers = [0,1, 2, 5,10];

let cube = map(f,numbers);

console.log(cube);

返回 [0, 1, 8, 125, 1000]。

在 JavaScript 中,可以根据条件来定义一个函数。比如下面的代码,当num 等于 0 的时候才会定义 myFunc

var myFunc;

if (num == 0){

myFunc = function(theObject) {

theObject.make = “Toyota”

}

}

除了上述的定义函数方法外,你也可以在运行时用 Function 构造器由一个字符串来创建一个函数 ,很像 eval()函数。

当一个函数是一个对象的属性时,称之为方法。了解更多关于对象和方法的知识 使用对象

调用函数


定义一个函数并不会自动的执行它。定义了函数仅仅是赋予函数以名称并明确函数被调用时该做些什么。调用函数才会以给定的参数真正执行这些动作。例如,一旦你定义了函数square,你可以如下这样调用它:

square(5);

上述语句通过提供参数 5 来调用函数。函数执行完它的语句会返回值25。

函数一定要处于调用它们的域中,但是函数的声明可以被提升(出现在调用语句之后),如下例:

console.log(square(5));

/* … */

function square(n) { return n*n }

函数域是指函数声明时的所在的地方,或者函数在顶层被声明时指整个程序。

提示: 注意只有使用如上的语法形式(即 function funcName(){})才可以。而下面的代码是无效的。就是说,函数提升仅适用于函数声明,而不适用于函数表达式。

console.log(square); // square is hoisted with an initial value undefined.

console.log(square(5)); // Uncaught TypeError: square is not a function

const square = function (n) {

return n * n;

}

函数的参数并不局限于字符串或数字。你也可以将整个对象传递给函数。函数 show_props

函数可以被递归,就是说函数可以调用其本身。例如,下面这个函数就是用递归计算阶乘:

function factorial(n){

if ((n == 0) || (n == 1))

return 1;

else

return (n * factorial(n - 1));

}

你可以计算1-5的阶乘如下:

var a, b, c, d, e;

a = factorial(1); // 1赋值给a

b = factorial(2); // 2赋值给b

c = factorial(3); // 6赋值给c

d = factorial(4); // 24赋值给d

e = factorial(5); // 120赋值给e

还有其它的方式来调用函数。常见的一些情形是某些地方需要动态调用函数,或者函数的实参数量是变化的,或者调用函数的上下文需要指定为在运行时确定的特定对象。显然,函数本身就是对象,因此这些对象也有方法。作为此中情形之一,apply()方法可以实现这些目的。

函数作用域


在函数内定义的变量不能在函数之外的任何地方访问,因为变量仅仅在该函数的域的内部有定义。相对应的,一个函数可以访问定义在其范围内的任何变量和函数。换言之,定义在全局域中的函数可以访问所有定义在全局域中的变量。在另一个函数中定义的函数也可以访问在其父函数中定义的所有变量和父函数有权访问的任何其他变量。

// 下面的变量定义在全局作用域(global scope)中

var num1 = 20,

num2 = 3,

name = “Chamahk”;

// 本函数定义在全局作用域

function multiply() {

return num1 * num2;

}

multiply(); // 返回 60

// 嵌套函数的例子

function getScore() {

var num1 = 2,

num2 = 3;

function add() {

return name + " scored " + (num1 + num2);

}

return add();

}

getScore(); // 返回 “Chamahk scored 5”

作用域和函数堆栈


递归

一个函数可以指向并调用自身。有三种方法可以达到这个目的:

  1. 函数名

  2. `arguments.callee

  3. 作用域下的一个指向该函数的变量名

例如,思考一下如下的函数定义:

var foo = function bar() {

// statements go here

};

在这个函数体内,以下的语句是等价的:

  1. bar()

  2. arguments.callee() (译者注:ES5禁止在严格模式下使用此属性)

  3. foo()

调用自身的函数我们称之为_递归函数_。在某种意义上说,递归近似于循环。两者都重复执行相同的代码,并且两者都需要一个终止条件(避免无限循环或者无限递归)。例如以下的循环:

var x = 0;

while (x < 10) { // “x < 10” 是循环条件

// do stuff

x++;

}

可以被转化成一个递归函数和对其的调用:

function loop(x) {

if (x >= 10) // “x >= 10” 是退出条件(等同于 “!(x < 10)”)

return;

// 做些什么

loop(x + 1); // 递归调用

}

loop(0);

不过,有些算法并不能简单的用迭代来实现。例如,获取树结构中所有的节点时,使用递归实现要容易得多:

function walkTree(node) {

if (node == null) //

return;

// do something with node

for (var i = 0; i < node.childNodes.length; i++) {

walkTree(node.childNodes[i]);

}

}

loop函数相比,这里每个递归调用都产生了更多的递归。

将递归算法转换为非递归算法是可能的,不过逻辑上通常会更加复杂,而且需要使用堆栈。事实上,递归函数就使用了堆栈:函数堆栈。

这种类似堆栈的行为可以在下例中看到:

function foo(i) {

if (i < 0)

return;

console.log(‘begin:’ + i);

foo(i - 1);

console.log(‘end:’ + i);

}

foo(3);

// 输出:

// begin:3

// begin:2

// begin:1

// begin:0

// end:0

// end:1

// end:2

// end:3

嵌套函数和闭包

你可以在一个函数里面嵌套另外一个函数。嵌套(内部)函数对其容器(外部)函数是私有的。它自身也形成了一个闭包。一个闭包是一个可以自己拥有独立的环境与变量的表达式(通常是函数)。

既然嵌套函数是一个闭包,就意味着一个嵌套函数可以”继承“容器函数的参数和变量。换句话说,内部函数包含外部函数的作用域。

可以总结如下:

  • 内部函数只可以在外部函数中访问。

  • 内部函数形成了一个闭包:它可以访问外部函数的参数和变量,但是外部函数却不能使用它的参数和变量。

下面的例子展示了嵌套函数:

function addSquares(a, b) {

function square(x) {

return x * x;

}

return square(a) + square(b);

}

a = addSquares(2, 3); // returns 13

b = addSquares(3, 4); // returns 25

c = addSquares(4, 5); // returns 41

由于内部函数形成了闭包,因此你可以调用外部函数并为外部函数和内部函数指定参数:

function outside(x) {

function inside(y) {

return x + y;

}

return inside;

}

fn_inside = outside(3); // 可以这样想:给一个函数,使它的值加3

result = fn_inside(5); // returns 8

result1 = outside(3)(5); // returns 8

保存变量

注意到上例中 inside 被返回时 x 是怎么被保留下来的。一个闭包必须保存它可见作用域中所有参数和变量。因为每一次调用传入的参数都可能不同,每一次对外部函数的调用实际上重新创建了一遍这个闭包。只有当返回的 inside 没有再被引用时,内存才会被释放。

这与在其他对象中存储引用没什么不同,但是通常不太明显,因为并不能直接设置引用,也不能检查它们。

多层嵌套函数

函数可以被多层嵌套。例如,函数A可以包含函数B,函数B可以再包含函数C。B和C都形成了闭包,所以B可以访问A,C可以访问B和A。因此,闭包可以包含多个作用域;他们递归式的包含了所有包含它的函数作用域。这个称之为作用_域链_。(稍后会详细解释)

思考一下下面的例子:

function A(x) {

function B(y) {

function C(z) {

console.log(x + y + z);

}

C(3);

}

B(2);

}

A(1); // logs 6 (1 + 2 + 3)

在这个例子里面,C可以访问B的y和A的x。这是因为:

  1. B形成了一个包含A的闭包,B可以访问A的参数和变量

  2. C形成了一个包含B的闭包

  3. B包含A,所以C也包含A,C可以访问B和A的参数和变量。换言之,C用这个顺序链接了B和A的作用域

反过来却不是这样。A不能访问C,因为A看不到B中的参数和变量,C是B中的一个变量,所以C是B私有的。

命名冲突

当同一个闭包作用域下两个参数或者变量同名时,就会产生命名冲突。更近的作用域有更高的优先权,所以最近的优先级最高,最远的优先级最低。这就是作用域链。链的第一个元素就是最里面的作用域,最后一个元素便是最外层的作用域。

看以下的例子:

function outside() {

var x = 5;

function inside(x) {

return x * 2;

}

return inside;

}

outside()(10); // returns 20 instead of 10

命名冲突发生在return x上,inside的参数xoutside变量x发生了冲突。这里的作用链域是{inside, outside, 全局对象}。因此insidex具有最高优先权,返回了20(insidex)而不是10(outsidex)。

闭包


闭包是 JavaScript 中最强大的特性之一。JavaScript 允许函数嵌套,并且内部函数可以访问定义在外部函数中的所有变量和函数,以及外部函数能访问的所有变量和函数。

但是,外部函数却不能够访问定义在内部函数中的变量和函数。这给内部函数的变量提供了一定的安全性。

此外,由于内部函数可以访问外部函数的作用域,因此当内部函数生存周期大于外部函数时,外部函数中定义的变量和函数的生存周期将比内部函数执行时间长。当内部函数以某一种方式被任何一个外部函数作用域访问时,一个闭包就产生了。

var pet = function(name) { //外部函数定义了一个变量"name"

var getName = function() {

//内部函数可以访问 外部函数定义的"name"

return name;

}

//返回这个内部函数,从而将其暴露在外部函数作用域

return getName;

};

myPet = pet(“Vivie”);

myPet(); // 返回结果 “Vivie”

实际上可能会比上面的代码复杂的多。在下面这种情形中,返回了一个包含可以操作外部函数的内部变量方法的对象。

var createPet = function(name) {

var sex;

return {

setName: function(newName) {

name = newName;

},

getName: function() {

return name;

},

getSex: function() {

return sex;

},

setSex: function(newSex) {

if(typeof newSex == “string”

&& (newSex.toLowerCase() == “male” || newSex.toLowerCase() == “female”)) {

sex = newSex;

}

}

}

}

var pet = createPet(“Vivie”);

pet.getName(); // Vivie

pet.setName(“Oliver”);

pet.setSex(“male”);

pet.getSex(); // male

pet.getName(); // Oliver

在上面的代码中,外部函数的name变量对内嵌函数来说是可取得的,而除了通过内嵌函数本身,没有其它任何方法可以取得内嵌的变量。内嵌函数的内嵌变量就像内嵌函数的保险柜。它们会为内嵌函数保留“稳定”——而又安全——的数据参与运行。而这些内嵌函数甚至不会被分配给一个变量,或者不必一定要有名字。

var getCode = (function(){

var secureCode = “0]Eal(eh&2”; // A code we do not want outsiders to be able to modify…

return function () {

return secureCode;

};

})();

getCode(); // Returns the secret code

尽管有上述优点,使用闭包时仍然要小心避免一些陷阱。如果一个闭包的函数定义了一个和外部函数的某个变量名称相同的变量,那么这个闭包将无法引用外部函数的这个变量。

var createPet = function(name) { // Outer function defines a variable called “name”

return {

setName: function(name) { // Enclosed function also defines a variable called “name”

name = name; // ??? How do we access the “name” defined by the outer function ???

}

}

}

使用 arguments 对象


函数的实际参数会被保存在一个类似数组的arguments对象中。在函数内,你可以按如下方式找出传入的参数:

arguments[i]

其中i是参数的序数编号(译注:数组索引),以0开始。所以第一个传来的参数会是arguments[0]。参数的数量由arguments.length表示。

使用arguments对象,你可以处理比声明的更多的参数来调用函数。这在你事先不知道会需要将多少参数传递给函数时十分有用。你可以用arguments.length来获得实际传递给函数的参数的数量,然后用arguments对象来取得每个参数。

例如,设想有一个用来连接字符串的函数。唯一事先确定的参数是在连接后的字符串中用来分隔各个连接部分的字符(译注:比如例子里的分号“;”)。该函数定义如下:

function myConcat(separator) {

var result = ‘’; // 把值初始化成一个字符串,这样就可以用来保存字符串了!!

var i;

// iterate through arguments

for (i = 1; i < arguments.length; i++) {

result += arguments[i] + separator;

}

return result;

}

你可以给这个函数传递任意数量的参数,它会将各个参数连接成一个字符串“列表”:

// returns "red, orange, blue, "

myConcat(", ", “red”, “orange”, “blue”);

// returns "elephant; giraffe; lion; cheetah; "

myConcat("; ", “elephant”, “giraffe”, “lion”, “cheetah”);

// returns "sage. basil. oregano. pepper. parsley. "

myConcat(". ", “sage”, “basil”, “oregano”, “pepper”, “parsley”);

提示:arguments变量只是 *”*类数组对象“,并不是一个数组。称其为类数组对象是说它有一个索引编号和length属性。尽管如此,它并不拥有全部的Array对象的操作方法。

函数参数


从ECMAScript 6开始,有两个新的类型的参数:默认参数,剩余参数。

默认参数

在JavaScript中,函数参数的默认值是undefined。然而,在某些情况下设置不同的默认值是有用的。这时默认参数可以提供帮助。

在过去,用于设定默认参数的一般策略是在函数的主体中测试参数值是否为undefined,如果是则赋予这个参数一个默认值。如果在下面的例子中,调用函数时没有实参传递给b,那么它的值就是undefined,于是计算a*b得到、函数返回的是 NaN。但是,在下面的例子中,这个已经被第二行获取处理:

function multiply(a, b) {

b = (typeof b !== ‘undefined’) ? b : 1;

return a*b;

}

multiply(5); // 5

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)

最后

一个好的心态和一个坚持的心很重要,很多冲着高薪的人想学习前端,但是能学到最后的没有几个,遇到困难就放弃了,这种人到处都是,就是因为有的东西难,所以他的回报才很大,我们评判一个前端开发者是什么水平,就是他解决问题的能力有多强。

分享一些简单的前端面试题以及学习路线给大家,狂戳这里即可免费领取

b : 1;

return a*b;

}

multiply(5); // 5

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-dKiYCiFo-1713506760846)]

[外链图片转存中…(img-Ve03W4kd-1713506760846)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

[外链图片转存中…(img-z7QU3tc2-1713506760847)]

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)

[外链图片转存中…(img-GlvE9Psr-1713506760847)]

最后

一个好的心态和一个坚持的心很重要,很多冲着高薪的人想学习前端,但是能学到最后的没有几个,遇到困难就放弃了,这种人到处都是,就是因为有的东西难,所以他的回报才很大,我们评判一个前端开发者是什么水平,就是他解决问题的能力有多强。

分享一些简单的前端面试题以及学习路线给大家,狂戳这里即可免费领取

[外链图片转存中…(img-It24gE3p-1713506760847)]

  • 22
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值