JavaScript函数,面试字节跳动被问前端屏幕适配方案

函数作用域

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

// 下面的变量定义在全局作用域(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发生了冲突。这里的作用链域是{insideoutside, 全局对象}。因此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”);

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

深知大多数初中级前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Web前端开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img
img
img
img

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

如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注:前端)
img

总结

  • 框架原理真的深入某一部分具体的代码和实现方式时,要多注意到细节,不要只能写出一个框架。

  • 算法方面很薄弱的,最好多刷一刷,不然影响你的工资和成功率😯

  • 在投递简历之前,最好通过各种渠道找到公司内部的人,先提前了解业务,也可以帮助后期优秀 offer 的决策。

  • 要勇于说不,对于某些 offer 待遇不满意、业务不喜欢,应该相信自己,不要因为当下没有更好的 offer 而投降,一份工作短则一年长则 N 年,为了幸福生活要慎重选择!!!

    开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

,不要只能写出一个框架。

  • 算法方面很薄弱的,最好多刷一刷,不然影响你的工资和成功率😯

  • 在投递简历之前,最好通过各种渠道找到公司内部的人,先提前了解业务,也可以帮助后期优秀 offer 的决策。

  • 要勇于说不,对于某些 offer 待遇不满意、业务不喜欢,应该相信自己,不要因为当下没有更好的 offer 而投降,一份工作短则一年长则 N 年,为了幸福生活要慎重选择!!!

    开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

喜欢这篇文章文章的小伙伴们点赞+转发支持,你们的支持是我最大的动力!

  • 8
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值