文章目录
- 概述
- 函数声明
- 函数形参与实参
- 函数预编译
- 用一个例子说明一下,这四个步骤分别要干些什么。
- 重复四个步骤,反复练习一下
- 全局编译
- 多重执行期上下文
概述
别小看变量声明与赋值,在所有的笔试中,基本都会考,这个要多变态就能多变态,但只要掌握基本的规律,我们就能游刃有余,面对一切困难。
var function1 = function() {
console.log('abc')
}
function1()
var function1 = function abc() {
console.log('abc')
}
function1()
abc()
var function1 = function() {
console.log('function1')
}
var abc = function function1() {
console.log('abc')
}
function1()
abc()
上面三段代码分别会发生什么?
函数声明
函数有函数声明和函数表达式两种方式创建
函数表达式
var function1 = function() {
console.log('function1')
}
函数声明
function function2() {
console.log('function2')
}
以下代码会发生什么,当函数声明的名称和函数表达式赋值的变量相同时
function function1() {
console.log('function2')
}
function1() // function3
var function1 = function() {
console.log('function1')
}
function function1() {
console.log('function3')
}
function1() // function1
结论 函数声明整体提升,函数表达式只提升赋值的变量
以下两种函数有什么区别
var function1 = function() {
console.log('function1')
}
var function2 = function abc() {
console.log('function2')
}
function1() // function1
function2() // function2
abc() // 报错
以上两种命名方式没区别。abc写与不写,基本没影响,写了也白写,通过abc()
无法调用,abc未被声明,所以会报错 abc is not a function
唯一的区别在于function1.name === 'function1' // true
function2.name === 'abc' // true
结论 函数声明赋值给变量时,函数声明就成了表达式,此时函数声明本身的名字将不重要。
函数形参与实参
arguments 就是实参 而 函数名xxx.length就可以拿到所有形参的数量
function test(a, b) {
// 获取形参的数量
console.log(test.length) // 2
// 获取实参的数量
console.log(arguments.length) // 4
return a + b
}
test(1,2,3,4) // 3
有了上面的启发,请你写一个函数能够返回所有传入参数的和
function sum() {
let result = 0;
for (let i = 0; i < arguments.length;i++) {
result += arguments[i]
}
return result
}
sum(1,2,3,4)
加深一点难度,请你将sum柯里化
function sum(a, b, c) {
return a + b + c
}
function curry(fun) {
return function curryFun() {
const args = arguments
// 如果参数数量足够,直接返回函数调用结果, args是实参而fun.length就是形参所需的数量
if(fun.length <= args.length) {
return fun(...args)
} else {
// 当参数数量不够时,直接递归返回curried函数,当下一次curried函数被调用时,再判断参数是否足够
return function(...nextargs) {
return curryFun(...args, ...nextargs)
}
}
}
}
var curried = curry(sum)
var result = curried(1)(2)(3) // 6
看一下形参和实参之间的关系
// 例1
function test(a, b) {
a = 1;
console.log(arguments[0]) // 1
arguments[1] = 2
console.log(b) // 2
}
test(0,0)
// 例2
function test1(a,b) {
a = 1;
console.log(arguments[0]) // 1
arguments[1] = 2
console.log(b) // undefined
}
test1(0)
上面例1说明了形参与实参是有映射关系的,如果形参发生改变,对应的实参arguments也会发生变化,arguments如果发生变化,形参也会发生变化。但是形参和实参arguments是两个不同数据,它们只是有映射关系。
上面的例2与例1的唯一不同就是,实参传入的时候只有一个参数,所以形参中只有第一个参数a与实参arguments[0]完成了映射。第二个参数没有传,所以arguments[1]也就不存在,也无法与形参b完成映射。
这也从侧面证明了,形参与实参只是映射关系,本质上还是两个不同的数据。
函数预编译
发生在函数执行的前一刻。
- 创建AO(active object) 对象,又称为执行期上下文。
- 将形参的名称,和声明变量的名称,作为键值对的key,放入AO对象中,value为undefined。
- 将形参和实参统一。
- 在函数体里面找函数声明,并将函数赋值。
用一个例子说明一下,这四个步骤分别要干些什么。
function test (a, b) {
console.log(a);
console.log(b)
var a = 2;
console.log(a)
function a () {};
console.log(a)
console.log(b)
var b = function b() {}
console.log(b)
}
test(1)
第一步,创建AO对象
const AO = {}
第二步 将形参的名称,和声明变量的名称,作为键值对的key,放入AO对象中,value为undefined。
test的形参有 a b,声明变量也是 a b
const AO = {
a: undefined,
b: undefined
}
第三步,将实参赋值给形参,test(1)
也就是把1赋值给a
const AO = {
a: 1,
b: undefined
}
第四步, 在函数体里面找函数声明,并将函数赋值。只有一个函数声明function a () {};
,所以
const AO = {
a: function a() {},
b: undefined
}
开始执行函数体
function test (a, b) {
console.log(a); // 直接从AO里拿a: function a() {}
console.log(b) // 直接从AO里拿b: undefined
var a = 2; // 拆分成两部,`var a;a = 1;` `var a;`已经在第二步中完成了,剩下`a = 2`,将AO的a赋值为2
console.log(a) // 直接从AO里拿a: 2
function a () {}; // 已经在第四步执行了
console.log(a) // 直接从AO里拿a: 2
console.log(b) // 直接从AO里拿b: undefined
var b = function b() {} // 拆分成两部,`var b;b = function b() {};` `var b;`已经在第二步中完成了,剩下`b = function b() {}`,将AO的b赋值为 function b() {}
console.log(b) // // 直接从AO里拿b: function b() {}
}
重复四个步骤,反复练习一下
function test1(c, d) {
console.log(c, d)
console.log(f)
var c = function() {}
function d() {}
console.log(c, d)
var d = 0
c = 1
console.log(c, d)
function e() {}
var f = 0;
}
test1(3, 4)
熟悉以后直接从第二步开始
将形参的名称,和声明变量的名称,作为键值对的key,放入AO对象中,value为undefined。
test的形参有 c d,声明变量也是 c d f,重复的只管一个
const AO = {
c: undefined,
d: undefined,
f: undefined
}
第三步,形参实参统一 传入的3和4 分别对应着c和d
const AO = {
c: 3,
d: 4,
f: undefined
}
第四步, 在函数体里面找函数声明,并创建key 赋值给AO对象。
test1中的函数声明只有function d() {}
和function e() {}
const AO = {
c: 3,
d: function d() {},
f: undefined
e: function e() {}
}
开始执行代码
function test1(c, d) {
console.log(c, d) // 直接从AO里拿c和d // 3 , function d() {},
console.log(f) // 直接从AO里拿f undefined
var c = function() {} // 将AO里的c赋值为function() {}
function d() {} // 在第四步执行了
console.log(c, d) // 直接从AO里拿c和d function() {} function d() {}
var d = 0 // 将AO里的d赋值为0
c = 1 // 将AO里的c赋值为1
console.log(c, d) // 1 0
function e() {}
var f = 0;
}
难度升级,加入作用域
全局编译
- 创建GO(global object) 对象,又称为全局执行期上下文。
- 声明变量的名称,作为键值对的key,放入GO对象中,value为undefined。
- 在函数体里面找函数声明,并将函数赋值。
GO(global object)在不同的环境指代不同,在浏览器中,GO就是window,在node中GO就是global
多重执行期上下文
var aa = 3;
function bb() {};
var dd;
function test3(aa, bb) {
console.log(aa)
console.log(bb)
dd = 3;
console.log(dd)
aa = 4;
bb = 5;
function dd() {}
console.log(aa);
console.log(bb);
console.log(dd);
}
test3(1, 2)
console.log(aa)
console.log(bb)
console.log(dd)
先创建GO(上面说的三个步骤)
const GO = {
aa: undefined,
dd: undefined,
bb: function bb() {},
test3: function test3() {...}
}
创建完成后,开始执行代码,执行到test3时,GO如下,创建AO(上面说的四个步骤)
const GO = {
aa: 3,
dd: undefined,
bb: function bb() {},
test3: function test3() {...}
}
const AO = {
aa: 1,
bb: 2,
dd: function dd() {}
}
函数test3的AO创建好以后,开始执行函数test3的函数体
var aa = 3;
function bb() {};
var dd;
function test3(aa, bb) {
console.log(aa) // 1
console.log(bb) // 2
dd = 3; // AO中有dd,改AO中的dd
console.log(dd) // AO中有dd,取AO中的dd 3
aa = 4;
bb = 5;
function dd() {} // 这一步相当于有了自己的dd
console.log(aa); // 4
console.log(bb); // 5
console.log(dd); // 3
}
test3(1, 2)
console.log(aa) // GO中有aa 3
console.log(bb) // GO中有bb function bb() {}
console.log(dd) // GO中有dd undefined