JS系列 -- Function 浅析

概述

函数是一个可以执行代码的对象。每个函数都是 Function 类型的实例,并且都与其他引用类型一样具有属性和方法。

Function

每个 Function 的实例的 __proto__ 都指向了 Function.prototype(原型),函数原型(Function.prototype)的 __proto__ 指向了 Object.prototype(对象的原型)

定义函数

  1. 函数声明

    function name( param1[ , param2[, ...] ] ){
        return
    }
    

    如果 return 省略,那么浏览器将自动添加 return undefined
    function 是关键字,它只能用来声明函数,而 var 则可以用来声明7 种数据类型(number、string、boolean、null、undefined、symbol、 object)中的任意一种。

  2. 函数表达式

    var x = function( param1[ , param2[, ...] ] ){
        return 
    }
    

    匿名函数需要赋给变量,之后才能调用。这个匿名函数称为函数表达式

  3. 混合式

    var fn = function name( param1[ , param2[, ...] ] ){
        return 
    }
    

    混合式中,函数名 name 只在函数体内部有效,指代函数表达式本身,在函数体外部无效。

  4. Function 构造函数
    最后一个参数被始终看成函数体,而前面的参数则枚举了新函数的参数。

    var fn = new Function( 'param1'[ , 'param2'[,' ...'] ] , functionBoby ){
        return 
    }
    

    例子:

    let n = 1;
    let fn = new Function( 'x' , 'y' , 'return x + ' + n + ' + y' );
    fn( 1,2 )  //4,此时的 n 是1
    
  5. 箭头函数(ES6)

    (param1[ , param2[, ...] ])=> { return  }
    

形参和实参

  • parameters ==> 形参,定义时的占位符
  • arguments ==> 实参,调用时传入的参数

说明

  • 函数会在执行完 return 语句之后停止并立即退出,因此,位于 return 语句之后的任何代码都永远不会执行。
  • 没有传递值的命名参数将自动被赋值 undefined
  • 所有参数传递的都是值,不可能通过引用传递参数
  • 验证函数,使用 typeof xxx === 'function'

函数声明和函数表达式

  • 函数声明
    浏览器解析器会率先读取函数声明(声明前置),并使其在执行任何代码之前可用
  • 函数表达式
    必须要等到浏览器解析器执行到它所在的代码行,才会被解析执行

声明前置

先声明变量,后声明 function,如果 function 名字和 var 声明名字一样
function 优先。如果赋值就按照赋值的类型。

声明前置

API

自身 API

自身属性 API
  • name ==> 返回函数实例的名称
fn.name
  • length ==> 指明函数的形参个数

Function.prototype API

Function.prototype API
  • call() ==> 调用一个函数, 其具有一个指定的this值和分别地提供的参数(参数的列表)
    作用:

    1. 改变 this 值


      改变 this 值
    2. 操作参数

    语法:

    fun.call(this, arg1, arg2, ...)
    

    参数:

    1. this :函数运行时的指定的 this 值。

      • 非严格模式下,若 this 为 null 或者 undefined ,则 this 自动指向全局对象(global[ window ]),同时值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的自动包装对象

        非严格模式

      • 严格模式下,若 this 为 undefined ,则 this === undefined

        严格模式

        此处 call 的时候指明了 this === 1,例子不明显,应该 fn.call(),此时便可以打印出 undefined

    2. arguments :伪数组,元素为函数的参数

      • 函数中的参数在内部是用一个伪数组(arguments)来表示,在函数内部通过 arguments 对象来访问参数从而获取传递给函数的每一个参数。
      • arguments 可以被用作被调用对象的所有未指定的参数。可以使用arguments来把所有的参数传递给被调用对象。
      • arguments 对象的长度是由传入的参数个数决定的,不是由定义函数时的命名参数的个数决定的。
  • apply() ==> 调用一个函数, 其具有一个指定的this值,以及作为一个数组(或类似数组的对象)提供的参数。
    作用:和 call() 一样

    1. 改变 this 值
    2. 操作参数

    语法:

    fun.apply(this, arguments)
    

    使用实例:

    1. 实例1:不关心 this ,所以将 this 传为 null

      apply 实例1

    2. 实例2:关心 this ,this === arr1

      Apply 实例2

    说明:call()apply() 的区别
    apply()call() 作用相同,不同之处在于提供参数的方式
    apply() 使用参数数组,可以将数组里的元素拆分传入参数
    call() 使用一组参数列表,一个一个传递参数
    apply可以使用数组字面量,也可以使用 arguments对 象, arguments 是一个函数的局部变量。

  • bind() ==> 创建一个新的函数, 当被调用时,将其 this 关键字设置为提供的值,返回新创建的函数。在调用新函数时,在任何提供之前提供一个给定的参数序列。
    作用:

    1. 切换上下文(this)
    2. 科里化

    语法:

    fun.bind( this [ , arg1 [ , arg2 [ , ... ] ] ] )
    

    使用实例:

    1. 实例1:基本用法
      bind 基本用法
    2. 实例2:实际应用
      bind 实际应用
    3. 实例3:利用 this + apply 实现 bind
      重构 bind

new

  1. 创建一个新对象 obj ,对象 __proto__ 指向构造函数的 prototype
    对象. __proto__ === 构造函数.prototype
    
  2. 调用 call()/apply()/bind()

Call Stack(调用栈)

栈是一种数据结构,特点是先进后出,js 代码执行时都遵循 Call Stack 的规则。如果压栈太多会导致错误(Stack Overflow[ 栈溢出 ])

作用域 和 作用域链

scope(范围、视野、眼界)

如果在函数作用域中没有写 var 直接写 a = 3
1. 优先赋值
2. 沿着作用域链寻找 a 的声明
3. 若作用域链中没有 a 的声明,则声明全局变量并赋值

变量提升

将作用域链中首先要做变量提升

例子1:
var a = 1;
function fn(){
    console.log( a );
    var a = 2;
}
fn.call();  //  a === undefined
例子2:
var a = 1;
function fn1(){
    console.log( a );  //  a === undefined
    var a = 2;
    fn2.call();
}
function fn2(){
    console.log( a );  //  a === 1
}
fn1.call();
例子3:
var obj = { name: ' obj ' }
function fn1(){
    function fn2(){
        console.log( this );   // this === window 
    }
    fn2.call();
}
fn1.call( obj );
例子4:
var liTags = document.querySelectAll( ' li ' );
for( let i = 0, len = liTags.length; i < len; i++ ){
    liTags[ i ].onclick = function(){
        console.log( i )  // 打印出的 i 都是最后的数字
    }
}

this 值

this 的调用主要有两种方式,一种是函数(function),另一种是作为对象的方法(methods)

判断 this 值就看函数怎么被调用,之后转化为 call 形式

例子1:作为对象的方法调用
function fn(){
    console.log(this)
}
let obj = {
    a: 'a',
    fn: fn
}
obj.fn()  // this === obj
例子2:作为对象属性的深层次嵌套
function fn(){
    console.log(this)
}
let obj = {
    a: 'a',
    wrapper: {
        b: 'b',
        fn: fn
    }
}
obj.wrapper.fn()  // this === { b: 'b', fn: fn } === obj.wrapper
例子3:
let obj = {
    a: 'a',
    fn () {
        console.log(fn)
    }
}
function fncb(cb){
    cb()
}
fncb(obj.fn)  // this === 全局变量

闭包

如果一个函数使用了它范围外的值,那么这个函数 + 这个变量就叫做闭包。

声明变量

立即调用函数

window下有很多全局属性


window

当定义变量的时候,有可能不小心覆盖了 window 的全局属性,为了避免覆盖 window 上的属性,可以:

  1. 不用全局属性
  2. 用局部变量
  3. 使用立即调用函数,从而使用局部变量
    () => { }.call()
    () => { }()
    
  4. 但是如果像上述写法,浏览器会报错,所以要这么写:
    (() => { }.call() )
    (() => { } ).call()
    -() => { }.call()
    +() => { }.call() 
    !() => { }.call()
    ~() => { }.call()
    { let  }  // let 作用域在块级作用域中
    

套路

  1. 如果同一个函数被多次声明,后面的声明就会覆盖前面的声明

  2. 递归:函数调用自身

  3. JavaScript将函数看做一种值,函数只是一个可以执行的值

  4. 函数表达式函数声明同时有的时候

    function

  5. 不能在条件语句中声明函数(变量提升,是的 if 无效)

  6. 函数执行时所在的作用域,是定义时的作用域,而不是调用时所在的作用域。

  7. 参数传值传递,内存中体现原始值的拷贝
    参数传址传递(复杂类型的值),内存中体现原始值的地址,唯一一种不改变参数的是替换掉整个参数,这时不会影响到原始值(添加属性或方法会影响到原始值)

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

  9. eval命令的作用是,将字符串当作语句执行。eval没有自己的作用域,都在当前作用域内执行,因此可能会修改当前作用域的变量的值,造成安全问题。为了防止这种风险,JavaScript 规定,如果使用严格模式eval内部声明的变量,不会影响到外部作用域

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容

  • Lua 5.1 参考手册 by Roberto Ierusalimschy, Luiz Henrique de F...
    苏黎九歌阅读 13,729评论 0 38
  • 1.函数参数的默认值 (1).基本用法 在ES6之前,不能直接为函数的参数指定默认值,只能采用变通的方法。
    赵然228阅读 683评论 0 0
  • 工厂模式类似于现实生活中的工厂可以产生大量相似的商品,去做同样的事情,实现同样的效果;这时候需要使用工厂模式。简单...
    舟渔行舟阅读 7,717评论 2 17
  • 三、闭包和高阶函数 3.1 闭包 3.1.1 变量的作用域 所谓变量的作用域,就是变量的有效范围。通过作用域的划分...
    梁同学de自言自语阅读 1,444评论 0 6
  • 单例模式 适用场景:可能会在场景中使用到对象,但只有一个实例,加载时并不主动创建,需要时才创建 最常见的单例模式,...
    Obeing阅读 2,054评论 1 10