函数的声明

function

  1. function f(s){
  2. console.log(s);
  3. }

函数表达式

  1. var f = function(s){
  2. console.log(s);
  3. }; //需要分号
  4. var x = function y(param) { }
  5. console.log(y) //报错 js垃圾的地方

函数对象定义函数

  1. var f= new window.Function('x','y','return x+y')
  2. console.log(f(1,2))

箭头函数

  1. var sum = (x,y)=>x+y;
  2. console.log(sum(1,2))

⭐函数调用 call

  • 尽量使用call调用函数,第一个参数就是函数的this
  • call的第一个参数如果是undefined,非严格模式下,this是全局对象
  1. var f = function(s){
  2. console.log(s);
  3. };
  4. f.call(undefined,1)
  5. let f = function(){
  6. console.log(this)
  7. }
  8. //f.call(undefined) //global window
  9. f.call(1) //[Number: 1]
  10. function f(){
  11. 'use strict'
  12. console.log(this)
  13. }
  14. f.call(1) //1
  15. let f = function(){
  16. 'use strict'
  17. console.log(this)
  18. }
  19. f.call(undefined) //undefined

函数是对象

  1. let f = {}
  2. f.params = {0:'x',1:'y'}
  3. f.body = 'console.log(this.params[0])'
  4. f.call = function(){
  5. eval(f.body)
  6. }
  7. f.call(); //x

递归

斐波那契数列

  1. function fib(num) {
  2. if (num === 0) return 0;
  3. if (num === 1) return 1;
  4. return fib(num - 2) + fib(num - 1);
  5. }
  6. fib(6) // 8

第一等公民

JavaScript 语言将函数看作一种值,与其它值(数值、字符串、布尔值等等)地位相同。凡是可以使用值的地方,就能使用函数。比如,可以把函数赋值给变量和对象的属性,也可以当作参数传入其他函数,或者作为函数的结果返回。函数只是一个可以执行的值,此外并无特殊之处。由于函数与其他数据类型地位平等,所以在 JavaScript 语言中又称函数为第一等公民

  1. function add(x, y) {
  2. return x + y;
  3. }
  4. // 将函数赋值给一个变量
  5. var operator = add;
  6. // 将函数作为参数和返回值
  7. function a(op){
  8. return op;
  9. }
  10. a(add)(1, 1)
  11. // 2

函数名提升

JavaScript 引擎将函数名视同变量名,所以采用function命令声明函数时,整个函数会像变量声明一样,被提升到代码头部

f(); function f() {}
f(); var f = function (){}; //报错

函数的属性和方法

name

  1. function f1() {}
  2. f1.name // "f1"
  3. var f2 = function () {};
  4. f2.name // "f2"
  5. var myFunc = function () {};
  6. function test(f) {
  7. console.log(f.name);
  8. }
  9. test(myFunc) // myFunc 获取参数函数的名字
  10. var f= new window.Function('x','y','return x+y')
  11. console.log(f.name) //anonymous
  12. var f1 = function f2(){}
  13. console.log(f1.name) //f2

length

函数预期传入的参数个数,即函数定义之中的参数个数
function f(a, b) {} f.length // 2

toString

返回一个字符串,内容是函数的源码

  1. function f() {
  2. a();
  3. b();
  4. c();
  5. }
  6. f.toString()
  7. // function f() {
  8. // a();
  9. // b();
  10. // c();
  11. // }
  12. //实现多行字符串
  13. var multiline = function (fn) {
  14. var arr = fn.toString().split('\n');
  15. return arr.slice(1, arr.length - 1).join('\n');
  16. };
  17. function f() {/*
  18. 这是一个
  19. 多行注释
  20. */}
  21. multiline(f);
  22. // " 这是一个
  23. // 多行注释"

作用域

  1. //变量提升
  2. var a = 1
  3. function f(){
  4. console.log(a) //undefined f作用域内变量提升
  5. var a = 2;
  6. }
  7. f()
  8. //函数作用域
  9. var a = 1
  10. function f(){
  11. console.log(a) //undefined f作用域内变量提升
  12. var a = 2;
  13. f2()
  14. }
  15. function f2(){
  16. console.log(a) //1
  17. }
  18. f()
  19. var i = 2
  20. setTimeout(() => {
  21. console.log(i) //6
  22. }, 10000);
  23. i = 6
  24. <ul>
  25. <li>选项1</li>
  26. <li>选项2</li>
  27. <li>选项3</li>
  28. <li>选项4</li>
  29. <li>选项5</li>
  30. <li>选项6</li>
  31. </ul>
  32. var liTags = document.querySelectorAll('li')
  33. for(var i = 0; i<liTags.length; i++){
  34. liTags[i].onclick = function(){
  35. console.log(i) // 点击第3个 li 时,打印 2 还是打印 6?
  36. }
  37. }

函数作用域

作用域精解

[[scope]]:每个 javascript 函数都是一个对象,对象中有些属性我们可以访问,但有些不可以,这些属性仅供 javascript 引擎存取,[[scope]]就是其中一个。[[scope]]指的就是我们所说的作用域,其中存储了运行期上下文的集合。
作用域链:[[scope]]中所存储的执行期上下文对象的集合,这个集合呈链式链接,我们把这种链式链接叫做作用域链。
运行期上下文:当函数在执行的前一刻,会创建一个称为执行期上下文的内部对象。
一个执行期上下文定义了一个函数执行时的环境,函数每次执行时对应的执行上下文都是独一无二的,所以多次调用一个函数会导致创建多个执行上下文,当函数执行完毕,执行上下文被销毁。
查找变量:在哪个函数里面查找变量,就从哪个函数作用域链的顶端依次向下查找。

  1. function test (){
  2. }
  3. 第一次执行 test(); AO{} //AO 是用完就不要的
  4. 第二次执行 test(); AO{} //这是另外的 AO
  1. function a (){
  2. function b (){
  3. var bb = 234;
  4. aa = 0;
  5. }
  6. var aa = 123;
  7. b();
  8. console.log(aa)
  9. }
  10. var glob = 100;
  11. a();

函数执行时才会产生AO,才会有变量和其内的函数定义

image.png
image.png

  1. a 函数被定义时,scope中会继承全局GO,因为它在全局环境中
  2. a 函数被执行时,scope的顶端会增加a函数的AO
  3. b 函数被定义时,b的scope会继承a函数的scope,因为它在a的环境中
  4. b 函数执行时,b的scope的顶端会增加b函数的AO
  5. 当 b 执行结束,它的AO就销毁
  6. 当 a 执行结束,它的AO就销毁,b在a的AO中,所以b也被销毁
  1. function a(){
  2. function b(){
  3. function c(){}
  4. c()
  5. }
  6. b()
  7. }
  8. a()

image.png
当 c 执行完后,会干掉自己的 cAO,回到 c 被定义的状态,当 c 再被执行时,会生成一个新的 newcAO{},其余都一样

Javascript 只有两种作用域:一种是全局作用域,变量在整个程序中一直存在,所有地方都可以读取;另一种是函数作用域,变量只在函数内部存在

  1. var v = 1;
  2. function f(){
  3. var v = 2;
  4. console.log(v);
  5. }
  6. f() // 2
  7. v // 1
  8. if (true) {
  9. var x = 5;
  10. }
  11. console.log(x); // 5 块级作用域

函数内部变量提升

  1. function a(){
  2. console.log(x);
  3. var x = 0;
  4. }
  5. a(); //undefined

函数自己的作用域

作用域与变量一样,就是其声明时所在的作用域,与其运行时所在的作用域无关

  1. var a = 1;
  2. var f = function(){
  3. console.log(a);
  4. }
  5. function ff(){
  6. var a=2;
  7. f();
  8. }
  9. ff(); //1
  10. var x = function () {
  11. console.log(a);
  12. };
  13. function y(f) {
  14. var a = 2;
  15. f();
  16. }
  17. y(x)
  18. // ReferenceError: a is not defined

参数

函数参数不是必需的,Javascript 允许省略参数

  1. function f(a, b) {
  2. return a;
  3. }
  4. f(1, 2, 3) // 1
  5. f(1) // 1
  6. f() // undefined
  7. f.length // 2
  8. function f(a, b) {
  9. return a;
  10. }
  11. f( , 1) // SyntaxError: Unexpected token ,(…)
  12. f(undefined, 1) // undefined

值传递和引用传递

  1. var p = 2;
  2. function f(p) {
  3. p = 3;
  4. }
  5. f(p);
  6. p // 2
  7. var obj = { p: 1 };
  8. function f(o) {
  9. o.p = 2;
  10. }
  11. f(obj);
  12. obj.p // 2

同名参数

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

  1. function f(a, a) {
  2. console.log(a);
  3. }
  4. f(1, 2) // 2

arguments

  • 由于 JavaScript 允许函数有不定数目的参数,所以需要一种机制,可以在函数体内部读取所有参数
  • 严格模式下,arguments对象是一个只读对象,修改它是无效的,但不会报错
  1. var f = function(a,b,c){
  2. 'use strict';
  3. arguments[0] = 3;
  4. return [(a+b+c),arguments.length];
  5. }
  6. console.log(f(1,2,3)); //6,3
  • 虽然arguments很像数组,但它是一个对象。数组专有的方法(比如slice和forEach),不能在arguments对象上直接使用 ,变成数组:var args = Array.prototype.slice.call(arguments);
  • arguments对象带有一个callee属性,返回它所对应的原函数
  1. var f = function () {
  2. console.log(arguments.callee === f);
  3. }
  4. f() // true

不是引用传递,是传入初始的映射关系

  1. function sum(a, b) {
  2. a = 2
  3. arguments[0] = 3
  4. console.log(a) //3
  5. }
  6. sum(1, 1)
  7. function sum(a, b) {
  8. b = 2
  9. console.log(arguments[1]) //undefined
  10. }
  11. sum(1)

闭包

闭包的原理 作用域链 内存泄漏

函数访问了外部的变量 ,函数+变量就是闭包

当函数嵌套,内部函数保存到外部时,必然会产生闭包,闭包会导致原有作用域链不释放,造成内存泄露

闭包 作用域链

  1. function a() {
  2. function b() {
  3. var bbb = 234
  4. console.log(aaa)
  5. }
  6. var aaa = 123
  7. return b
  8. }
  9. var demo = a()
  10. demo() //123

return b 是把 b(包括 a 的 AO)保存到外部了(放在全局)
当 a 执行完砍掉自己的 AO 时,b的箭头没有断掉,b 依然可以访问到 a 的 AO(因为 return b),所以a环境的内存没释放
image.png
但凡是内部的函数被保存到外部,一定生成闭包

  1. function a() {
  2. var num = 100
  3. function b() {
  4. num++
  5. console.log(num)
  6. }
  7. return b
  8. }
  9. var demo = a()
  10. demo()//101
  11. demo()//102

b定义的时候,直接获得a的环境,有aAO和GO,第一次执行demo,aAO里的num++,第二次执行demo,还是aAO里的num++,内存不释放

test中arr中的函数,在循环外面调用的时候,访问的仍然是testAO中的i,i循环结束是10

  1. function test() {
  2. var arr = []
  3. for (var i = 0; i < 10; i++) {
  4. arr[i] = function() {
  5. console.log(i)
  6. }
  7. arr[i]()//0 1 2 3 4 5 6 7 8 9
  8. }
  9. return arr
  10. }
  11. var myArr = test()
  12. myArr.forEach(function(func) {
  13. func()
  14. }) //10 10 10 10 10 10 10 10 10 10

破除闭包的作用域链,可用立即执行函数,立即执行函数会立即有一个作用域 ()AO,里面的 j 是()AO的,立即就将 i 的值传给了j,j放入了函数中

闭包会导致多个执行函数共用一个公有变量,如果不是特殊需要,应尽量防止这种情况发生

  1. function test() {
  2. var arr = []
  3. for (var i = 0; i < 10; i++) {;
  4. (function(j) {
  5. arr[j] = function() {
  6. console.log(j)
  7. }
  8. })(i)
  9. }
  10. return arr
  11. }
  12. var myArr = test()
  13. myArr.forEach(function(func) {
  14. func()
  15. }) //0 1 2 3 4 5 6 7 8 9
  1. window.onload = function() {
  2. var divContants = document.querySelector('.contants')
  3. var descSpan = document.querySelector('.desc span')
  4. var desc = document.querySelector('.desc')
  5. var buttons = document.getElementsByTagName('button')
  6. console.log(buttons)
  7. for (var i = 0; i < buttons.length; i++) {;
  8. (function(j) {
  9. buttons[j].onclick = function(e) {
  10. var bgColor = getComputedStyle(e.target, null).getPropertyValue(
  11. 'background-color'
  12. )
  13. descSpan.style.color = bgColor
  14. divContants.style.backgroundColor = bgColor
  15. desc.style.backgroundColor = '#E9F4FE'
  16. }
  17. })(i)
  18. }
  19. }

通过函数访问函数内部变量

  1. function a(x){
  2. var arr = 1;
  3. function b(){
  4. console.log(arr);
  5. }
  6. return {
  7. fb : b,
  8. brr : arr
  9. }
  10. }
  11. var ts = a();
  12. ts.fb(); //1
  13. console.log(ts.brr); //1

闭包作用域是函数内部,内存不释放

  1. function a(x){
  2. return function(){
  3. return x++;
  4. };
  5. }
  6. var arr = a(5);
  7. console.log(arr()); //5
  8. console.log(arr()); //6
  9. console.log(arr()); //7

闭包-构造函数

  1. function a(){
  2. var age;
  3. function getAge(){
  4. return age;
  5. }
  6. function setAge(a){
  7. age = a;
  8. }
  9. return {
  10. getage:getAge,
  11. setage:setAge
  12. }
  13. }
  14. var b = a();
  15. b.setage(5);
  16. console.log(b.getage()); //5

闭包的作用

实现公有变量

实现累加

  1. function a() {
  2. var num = 100
  3. function b() {
  4. num++
  5. console.log(num)
  6. }
  7. return b
  8. }
  9. var demo = a()
  10. demo()//101
  11. demo()//102

可以做缓存(存储结构)

a和b指向的testAO是同一个内存

  1. function test() {
  2. var num = 100
  3. function a() {
  4. num++
  5. console.log(num)
  6. }
  7. //a defined a.[[scope]] 0 : testAO
  8. 1 : GO
  9. function b() {
  10. num--
  11. console.log(num)
  12. }
  13. //b defined a.[[scope]] 0 : testAO
  14. 1 : GO
  15. return [a, b]
  16. }
  17. var arr = test()
  18. //test doing test[[scope]] 0:testAO
  19. 1GO
  20. arr[0]() //101
  21. //a doing a.[[scope]] 0 : aAO
  22. 1 : testAO
  23. 2 : GO
  24. arr[1]() //100
  25. //a doing a.[[scope]] 0 : aAO
  26. 1 : a 运行后的 testAO
  27. 2 : GO
  1. function eater() {
  2. var food = ''
  3. var obj = {
  4. eat: function() {
  5. console.log('i am eating ' + food)
  6. },
  7. push: function(myFood) {
  8. food = myFood
  9. },
  10. }
  11. return obj
  12. }
  13. var eater1 = eater()
  14. eater1.push('banana')
  15. eater1.eat() //i am eating banana

在 function eater(){里面的 food}就相当于一个隐式存储的机构

模块化开发,防止污染全局变量

私有化变量

image.png

立即执行函数

(function(){ /* code */ })();

(function(){ /* code */ }());

  • 执行后立即释放
  • 不必为函数命名,避免了污染全局变量
  • IIFE 内部形成了一个单独的作用域,可以封装一些外部无法读取的私有变量


通常情况下,只对匿名函数使用这种“立即执行的函数表达式”。

  1. //可以传参数,可以返回值
  2. var num = (function(a, b, c) {
  3. return a + b + c
  4. })(1, 2, 3)
  1. //只有表达式才能被执行符号执行
  2. function test() {
  3. var a = 123
  4. }()//报错
  5. test()//成功
  6. + function test() {
  7. var a = 123
  8. }()//成功
  9. (function test() {
  10. var a = 123
  11. })()//成功
  12. // 立即执行后,表达式就释放了
  13. var test = (function() {
  14. var a = 123
  15. console.log(a)
  16. })()
  17. test() //test is not a function
  18. var x = 1
  19. if (function f() {}) {
  20. x += typeof f
  21. }
  22. console.log(x) //1undefined
  23. //括号里的表达式释放了,typeof返回的是字符串

eval的本质是在当前作用域之中,注入代码。由于安全风险和不利于 JavaScript 引擎优化执行速度,所以一般不推荐使用。通常情况下,eval最常见的场合是解析 JSON 数据的字符串,不过正确的做法应该是使用原生的JSON.parse方法

递归

阶乘

  • 找规律 n! = n * (n-1)!
  • 找出口 n=1 ```javascript function mul(n) { if (n == 1 || n == 0) {
    1. return 1
    } return n * mul(n - 1) }

console.log(mul(5))

  1. **斐波那契额数列**
  2. - 找规律 fb(n) = fb(n-1) + fb(n-2)
  3. - 找出口 n=1 || n=2 return 1
  4. ```javascript
  5. function fb(n) {
  6. if (n == 1 || n == 2) {
  7. return 1
  8. }
  9. return fb(n - 1) + fb(n - 2)
  10. }
  11. console.log(fb(5))