js里的函数

版权声明:本文为博主原创文章,转载请附上原文地址 https://blog.csdn.net/writing_happy/article/details/79665824

js中的函数

函数是一段可以重复调用的代码块,也是一个对象,为了解决代码重复的问题。

函数的5种声明方式

1.具名函数

function f(x,y){
  return x+y
}

  这里function的作用相当于var,var用来声明一个变量,而function用来声明一个函数。var声明的变量可以有多种类型,而function声明的函数只能是function。
2. 匿名函数赋给变量

var f = function(x,y){
  return x+y
}

  在内存中开辟一段空间用来存储这个匿名函数,然后把它的地址赋给var声明的变量f。如果此时执行f = 1,那么这个匿名变量就失去了与栈内存的联系,会被浏览器回收。
3. 具名函数赋给变量

var f = function f2(x,y){
  return x+y
}

  这里比较容易迷惑,但是确实有这么变态的声明方式。你以为同时声明了f2和f两个函数吗?不是的,f2根本就不存在!!!f2只活在这个该死的函数内部。在函数的外部,你只能通过f.name看见f2这个字眼。也就是说,这个需要用f.call()来调用的函数,函数名居然是f2!!
4. 使用全局对象window.Function声明

var f = new Function('x','y','return x+y')

  加不加new效果相同,反正也没人用这种诡异的东西来声明一个函数。前边不论有多少个字符串都是这个函数的参数,只有最后一个字符串是函数体。
  字符串拼接的时候可以加变量,比如

var n = 2
var f = new Function('x','y','return x+' + n + '+y')
f(1, 2)  //1+2+2=5

5.箭头函数

var f = (x,y) => {
  return x+y
}

  这个应该是比较省事的一种吧!括号里边的是参数,后边大括号里边是函数体。如果函数体里边只有一句你可以将大括号和return一起省掉,变成这种形式var f = (x,y) => x+y,要么不要去掉要么return和大括号一起去掉。如果参数只有一个那么你还可以省掉小括号var f = n => n*n

tips

  函数一定会有一个返回值,就算你不写返回值,浏览器也会自动给你加上一个return undefined。也因此,你在控制台写语句的时候经常返回undefined。像下图:

  我们在控制台打印出'打印'这两个字,这条语句的返回值是undefined。console.log()的返回值和打印出的东西并不相同,打印出的东西是你传入的东西,而这条语句的返回值总是undefined。

  所有的函数都有一个name属性。

function f(){}   //f.name === 'f'
var f = function(){}   //f.name === 'f'
var f = function f2(){}   //f.name === 'f2'
var f = new Function()    //f.name === 'anonymous'  !!!!!这个单词的中文意思是“匿名”
var f = () => {}    //f.name === 'f'

函数的使用

  一个基本类型的数据,声明了之后便可以直接使用。比如var n = 3; n = n + 1,而函数的调用需要用到函数的共有方法call。但是如果你只写一个f,那它只是一个简单的对象,并不能做什么。函数有一种简单的调用方法f(),这里不提。我们要说的是f.call(),通过调用call方法来使用这个函数。
  函数在内存中的存储方式:

  在堆内存中函数开辟了一段空间,里边存有函数的参数和函数体,以及__proto__属性,整个的函数体是以字符串的形式存储的,也就是那种很鸡肋的声明方法中的最后一个参数。函数的共有属性里包括有调用函数的call方法,call方法可以把函数体存的字符串当成代码执行。eval()有着同样的效果,可以将字符串转成代码执行。比如eval('1+1')得到的结果是2,eval('1+"1"')得到的结果是11。
  所以我们可以把call方法想象成这个样子

f.call = function(){
  eval(f.fbody)
}

  //////f是一个变量,f.call是一个属性,f.call()是一个方法
  使用f.call()进行函数的调用时,第一个参数是这个函数的本身,也就是在函数体内部的this。后边的参数才是正经传入的参数,也就是组成伪数组arguments的数据。你可以用this得到第一个参数,用arguments[i](i为参数的index)得到后边的参数。
  普通模式下第一个参数如果是undefined浏览器会将其封装为window对象(看上去是Window但this===window的结果为true),但如果在严格模式下

function f(x,y){
  'use strict'
  console.log(this)
  console.log(arguments)
  return undefined
}

  你传入的是什么得到的就是什么,而不会多此一举地把它们封装成对象。因为this本来就是一个参数,并不是对象。

call stack

function a(){
    console.log('a1')
    b.call()
    console.log('a2')
  return 'a'  
}
function b(){
    console.log('b1')
    c.call()
    console.log('b2')
    return 'b'
}
function c(){
    console.log('c')
    return 'c'
}
a.call()

  上边的代码执行结果依次是a1 b1 c b2 a2,什么是call stack?在你进入到c.call()之后怎么知道是回去打印a2还是b2呢?正在执行的语句被压入stack中,然后等待它上边的都走了再继续执行出栈。这就是call stack(调用栈)。所谓的栈溢出(stack over flow)就是栈里存的东西太多溢出来了。

作用域

  不知道你们有没有听过一句话:如果不写var那声明的就是全局变量。事实上,如果你写了一句a = 1浏览器会优先认为它是一条赋值语句,然后沿着作用范围的这棵树找,如果找到了最上层还没找到变量a,那么才会在最上层声明变量a。所以我们看到的结果就是声明了全局变量。
  js中一个函数就是一个作用范围,我们可以把函数看作是树的结点,这就构成了一个表示作用范围的树形结构。在使用一个变量的时候,它会沿着这个作用范围一层一层地找直至找到这个变量的声明为止,采用的是就近原则。
  如果一个函数使用了它范围之外的值,那么这个函数和这个变量就构成了闭包。

一个很经典的题

var liTags = document.querySelectorAll('li') //假设这个页面有6个li
for(var i = 0; i<liTags.length; i++){
    liTags[i].onclick = function(){
        console.log(i) // 点击第3个 li 时,打印出来的是 2 还是 6 ?
    }
}

  事实上,在页面加载完之后onclick这个事件还没有执行,也就是说,在for循环的i已经等于6的时候,onclick这个事件还没有发生。function这个容器里边的内容一直都在改变,它是动态的,执行这个函数的时候,我们打印出来的是i最终的值6,而不是你想当然的结果。
  本来想把之前的解释删掉的,因为之前虽然强行解释了但是对打印出来的i的结果都还有些懵。但是好像删掉之后又没什么好写的了,因为这个问题懂的时候好像就没什么好解释的了,似乎最后打印出的是6是自然而然的结果。而且写的好像也没啥错2333
  为了更好理解一些可以画内存图如下(emmm点击事件什么的其实是存在heap里边的,画错了蓝鹅不想改了好费劲而且对要表达的东西没什么太大的影响,所以意会一下就好……)
这里写图片描述
  变量i和事件都存储在stack内存中,而函数都存在了heap内存里,在事件发生的时候进行函数的调用,此时函数对全局变量i进行调用,所以打印出的只能是i的最终结果,也就是6。

猜你喜欢

转载自blog.csdn.net/writing_happy/article/details/79665824