函数的声明
function
function f(s){
console.log(s);
}
函数表达式
var f = function(s){
console.log(s);
}; //需要分号
var x = function y(param) { }
console.log(y) //报错 js垃圾的地方
函数对象定义函数
var f= new window.Function('x','y','return x+y')
console.log(f(1,2))
箭头函数
var sum = (x,y)=>x+y;
console.log(sum(1,2))
⭐函数调用 call
- 尽量使用call调用函数,第一个参数就是函数的this
- call的第一个参数如果是undefined,非严格模式下,this是全局对象
var f = function(s){
console.log(s);
};
f.call(undefined,1)
let f = function(){
console.log(this)
}
//f.call(undefined) //global window
f.call(1) //[Number: 1]
function f(){
'use strict'
console.log(this)
}
f.call(1) //1
let f = function(){
'use strict'
console.log(this)
}
f.call(undefined) //undefined
函数是对象
let f = {}
f.params = {0:'x',1:'y'}
f.body = 'console.log(this.params[0])'
f.call = function(){
eval(f.body)
}
f.call(); //x
递归
斐波那契数列
function fib(num) {
if (num === 0) return 0;
if (num === 1) return 1;
return fib(num - 2) + fib(num - 1);
}
fib(6) // 8
第一等公民
JavaScript 语言将函数看作一种值,与其它值(数值、字符串、布尔值等等)地位相同。凡是可以使用值的地方,就能使用函数。比如,可以把函数赋值给变量和对象的属性,也可以当作参数传入其他函数,或者作为函数的结果返回。函数只是一个可以执行的值,此外并无特殊之处。由于函数与其他数据类型地位平等,所以在 JavaScript 语言中又称函数为第一等公民
function add(x, y) {
return x + y;
}
// 将函数赋值给一个变量
var operator = add;
// 将函数作为参数和返回值
function a(op){
return op;
}
a(add)(1, 1)
// 2
函数名提升
JavaScript 引擎将函数名视同变量名,所以采用function命令声明函数时,整个函数会像变量声明一样,被提升到代码头部
f(); function f() {}
f(); var f = function (){};
//报错
函数的属性和方法
name
function f1() {}
f1.name // "f1"
var f2 = function () {};
f2.name // "f2"
var myFunc = function () {};
function test(f) {
console.log(f.name);
}
test(myFunc) // myFunc 获取参数函数的名字
var f= new window.Function('x','y','return x+y')
console.log(f.name) //anonymous
var f1 = function f2(){}
console.log(f1.name) //f2
length
函数预期传入的参数个数,即函数定义之中的参数个数function f(a, b) {} f.length // 2
toString
返回一个字符串,内容是函数的源码
function f() {
a();
b();
c();
}
f.toString()
// function f() {
// a();
// b();
// c();
// }
//实现多行字符串
var multiline = function (fn) {
var arr = fn.toString().split('\n');
return arr.slice(1, arr.length - 1).join('\n');
};
function f() {/*
这是一个
多行注释
*/}
multiline(f);
// " 这是一个
// 多行注释"
作用域
//变量提升
var a = 1
function f(){
console.log(a) //undefined f作用域内变量提升
var a = 2;
}
f()
//函数作用域
var a = 1
function f(){
console.log(a) //undefined f作用域内变量提升
var a = 2;
f2()
}
function f2(){
console.log(a) //1
}
f()
var i = 2
setTimeout(() => {
console.log(i) //6
}, 10000);
i = 6
<ul>
<li>选项1</li>
<li>选项2</li>
<li>选项3</li>
<li>选项4</li>
<li>选项5</li>
<li>选项6</li>
</ul>
var liTags = document.querySelectorAll('li')
for(var i = 0; i<liTags.length; i++){
liTags[i].onclick = function(){
console.log(i) // 点击第3个 li 时,打印 2 还是打印 6?
}
}
函数作用域
作用域精解
[[scope]]:每个 javascript 函数都是一个对象,对象中有些属性我们可以访问,但有些不可以,这些属性仅供 javascript 引擎存取,[[scope]]就是其中一个。[[scope]]指的就是我们所说的作用域,其中存储了运行期上下文的集合。
作用域链:[[scope]]中所存储的执行期上下文对象的集合,这个集合呈链式链接,我们把这种链式链接叫做作用域链。
运行期上下文:当函数在执行的前一刻,会创建一个称为执行期上下文的内部对象。
一个执行期上下文定义了一个函数执行时的环境,函数每次执行时对应的执行上下文都是独一无二的,所以多次调用一个函数会导致创建多个执行上下文,当函数执行完毕,执行上下文被销毁。
查找变量:在哪个函数里面查找变量,就从哪个函数作用域链的顶端依次向下查找。
function test (){
}
第一次执行 test(); → AO{} //AO 是用完就不要的
第二次执行 test(); → AO{} //这是另外的 AO
function a (){
function b (){
var bb = 234;
aa = 0;
}
var aa = 123;
b();
console.log(aa)
}
var glob = 100;
a();
函数执行时才会产生AO,才会有变量和其内的函数定义
- a 函数被定义时,scope中会继承全局GO,因为它在全局环境中
- a 函数被执行时,scope的顶端会增加a函数的AO
- b 函数被定义时,b的scope会继承a函数的scope,因为它在a的环境中
- b 函数执行时,b的scope的顶端会增加b函数的AO
- 当 b 执行结束,它的AO就销毁
- 当 a 执行结束,它的AO就销毁,b在a的AO中,所以b也被销毁
function a(){
function b(){
function c(){}
c()
}
b()
}
a()
当 c 执行完后,会干掉自己的 cAO,回到 c 被定义的状态,当 c 再被执行时,会生成一个新的 newcAO{},其余都一样
Javascript 只有两种作用域:一种是全局作用域,变量在整个程序中一直存在,所有地方都可以读取;另一种是函数作用域,变量只在函数内部存在
var v = 1;
function f(){
var v = 2;
console.log(v);
}
f() // 2
v // 1
if (true) {
var x = 5;
}
console.log(x); // 5 块级作用域
函数内部变量提升
function a(){
console.log(x);
var x = 0;
}
a(); //undefined
函数自己的作用域
作用域与变量一样,就是其声明时所在的作用域,与其运行时所在的作用域无关
var a = 1;
var f = function(){
console.log(a);
}
function ff(){
var a=2;
f();
}
ff(); //1
var x = function () {
console.log(a);
};
function y(f) {
var a = 2;
f();
}
y(x)
// ReferenceError: a is not defined
参数
函数参数不是必需的,Javascript 允许省略参数
function f(a, b) {
return a;
}
f(1, 2, 3) // 1
f(1) // 1
f() // undefined
f.length // 2
function f(a, b) {
return a;
}
f( , 1) // SyntaxError: Unexpected token ,(…)
f(undefined, 1) // undefined
值传递和引用传递
var p = 2;
function f(p) {
p = 3;
}
f(p);
p // 2
var obj = { p: 1 };
function f(o) {
o.p = 2;
}
f(obj);
obj.p // 2
同名参数
如果有同名的参数,则取最后出现的那个值
function f(a, a) {
console.log(a);
}
f(1, 2) // 2
arguments
- 由于 JavaScript 允许函数有不定数目的参数,所以需要一种机制,可以在函数体内部读取所有参数
- 严格模式下,arguments对象是一个只读对象,修改它是无效的,但不会报错
var f = function(a,b,c){
'use strict';
arguments[0] = 3;
return [(a+b+c),arguments.length];
}
console.log(f(1,2,3)); //6,3
- 虽然arguments很像数组,但它是一个对象。数组专有的方法(比如slice和forEach),不能在arguments对象上直接使用 ,变成数组:
var args = Array.prototype.slice.call(arguments);
- arguments对象带有一个callee属性,返回它所对应的原函数
var f = function () {
console.log(arguments.callee === f);
}
f() // true
不是引用传递,是传入初始的映射关系
function sum(a, b) {
a = 2
arguments[0] = 3
console.log(a) //3
}
sum(1, 1)
function sum(a, b) {
b = 2
console.log(arguments[1]) //undefined
}
sum(1)
闭包
闭包的原理 作用域链 内存泄漏
函数访问了外部的变量 ,函数+变量就是闭包
当函数嵌套,内部函数保存到外部时,必然会产生闭包,闭包会导致原有作用域链不释放,造成内存泄露
闭包 作用域链
function a() {
function b() {
var bbb = 234
console.log(aaa)
}
var aaa = 123
return b
}
var demo = a()
demo() //123
return b 是把 b(包括 a 的 AO)保存到外部了(放在全局)
当 a 执行完砍掉自己的 AO 时,b的箭头没有断掉,b 依然可以访问到 a 的 AO(因为 return b),所以a环境的内存没释放
但凡是内部的函数被保存到外部,一定生成闭包
function a() {
var num = 100
function b() {
num++
console.log(num)
}
return b
}
var demo = a()
demo()//101
demo()//102
b定义的时候,直接获得a的环境,有aAO和GO,第一次执行demo,aAO里的num++,第二次执行demo,还是aAO里的num++,内存不释放
test中arr中的函数,在循环外面调用的时候,访问的仍然是testAO中的i,i循环结束是10
function test() {
var arr = []
for (var i = 0; i < 10; i++) {
arr[i] = function() {
console.log(i)
}
arr[i]()//0 1 2 3 4 5 6 7 8 9
}
return arr
}
var myArr = test()
myArr.forEach(function(func) {
func()
}) //10 10 10 10 10 10 10 10 10 10
破除闭包的作用域链,可用立即执行函数,立即执行函数会立即有一个作用域 ()AO,里面的 j 是()AO的,立即就将 i 的值传给了j,j放入了函数中
闭包会导致多个执行函数共用一个公有变量,如果不是特殊需要,应尽量防止这种情况发生
function test() {
var arr = []
for (var i = 0; i < 10; i++) {;
(function(j) {
arr[j] = function() {
console.log(j)
}
})(i)
}
return arr
}
var myArr = test()
myArr.forEach(function(func) {
func()
}) //0 1 2 3 4 5 6 7 8 9
window.onload = function() {
var divContants = document.querySelector('.contants')
var descSpan = document.querySelector('.desc span')
var desc = document.querySelector('.desc')
var buttons = document.getElementsByTagName('button')
console.log(buttons)
for (var i = 0; i < buttons.length; i++) {;
(function(j) {
buttons[j].onclick = function(e) {
var bgColor = getComputedStyle(e.target, null).getPropertyValue(
'background-color'
)
descSpan.style.color = bgColor
divContants.style.backgroundColor = bgColor
desc.style.backgroundColor = '#E9F4FE'
}
})(i)
}
}
通过函数访问函数内部变量
function a(x){
var arr = 1;
function b(){
console.log(arr);
}
return {
fb : b,
brr : arr
}
}
var ts = a();
ts.fb(); //1
console.log(ts.brr); //1
闭包作用域是函数内部,内存不释放
function a(x){
return function(){
return x++;
};
}
var arr = a(5);
console.log(arr()); //5
console.log(arr()); //6
console.log(arr()); //7
闭包-构造函数
function a(){
var age;
function getAge(){
return age;
}
function setAge(a){
age = a;
}
return {
getage:getAge,
setage:setAge
}
}
var b = a();
b.setage(5);
console.log(b.getage()); //5
闭包的作用
实现公有变量
实现累加
function a() {
var num = 100
function b() {
num++
console.log(num)
}
return b
}
var demo = a()
demo()//101
demo()//102
可以做缓存(存储结构)
a和b指向的testAO是同一个内存
function test() {
var num = 100
function a() {
num++
console.log(num)
}
//a defined a.[[scope]] 0 : testAO
1 : GO
function b() {
num--
console.log(num)
}
//b defined a.[[scope]] 0 : testAO
1 : GO
return [a, b]
}
var arr = test()
//test doing test[[scope]] 0:testAO
1:GO
arr[0]() //101
//a doing a.[[scope]] 0 : aAO
1 : testAO
2 : GO
arr[1]() //100
//a doing a.[[scope]] 0 : aAO
1 : a 运行后的 testAO
2 : GO
function eater() {
var food = ''
var obj = {
eat: function() {
console.log('i am eating ' + food)
},
push: function(myFood) {
food = myFood
},
}
return obj
}
var eater1 = eater()
eater1.push('banana')
eater1.eat() //i am eating banana
在 function eater(){里面的 food}就相当于一个隐式存储的机构
模块化开发,防止污染全局变量
私有化变量
立即执行函数
(function(){ /* code */ })();
(function(){ /* code */ }());
- 执行后立即释放
- 不必为函数命名,避免了污染全局变量
- IIFE 内部形成了一个单独的作用域,可以封装一些外部无法读取的私有变量
通常情况下,只对匿名函数使用这种“立即执行的函数表达式”。
//可以传参数,可以返回值
var num = (function(a, b, c) {
return a + b + c
})(1, 2, 3)
//只有表达式才能被执行符号执行
function test() {
var a = 123
}()//报错
test()//成功
+ function test() {
var a = 123
}()//成功
(function test() {
var a = 123
})()//成功
// 立即执行后,表达式就释放了
var test = (function() {
var a = 123
console.log(a)
})()
test() //test is not a function
var x = 1
if (function f() {}) {
x += typeof f
}
console.log(x) //1undefined
//括号里的表达式释放了,typeof返回的是字符串
eval的本质是在当前作用域之中,注入代码。由于安全风险和不利于 JavaScript 引擎优化执行速度,所以一般不推荐使用。通常情况下,eval最常见的场合是解析 JSON 数据的字符串,不过正确的做法应该是使用原生的JSON.parse方法
递归
阶乘
- 找规律 n! = n * (n-1)!
- 找出口 n=1
```javascript
function mul(n) {
if (n == 1 || n == 0) {
} return n * mul(n - 1) }return 1
console.log(mul(5))
**斐波那契额数列**
- 找规律 fb(n) = fb(n-1) + fb(n-2)
- 找出口 n=1 || n=2 return 1
```javascript
function fb(n) {
if (n == 1 || n == 2) {
return 1
}
return fb(n - 1) + fb(n - 2)
}
console.log(fb(5))