# 12、函数数据类型
函数
函数:具备一定功能的方法; 创建函数: 1、声明一个函数名(函数用function,变量用var) 2、开辟一个新的内存空间(有一个16进制的地址),然后把函数体中实现功能的JS代码按照"字符串"的格式存储在内存中 3、把空间地址赋值给函数名,此时函数名就可以和函数体本身关联到一起了;
function 函数名(){
函数体:实现某一功能的JS代码
}
函数执行: 1、函数执行的时候会形成一个私有的作用域,提供一个环境供函数体中的代码执行; 2、把创建的时候存储的字符串变为真正的JS代码,在私有作用域中自上而下执行
注意: 一个函数可以被执行N次,每一次执行相互之间互不干扰(后面会说明两者之间建立的间接关系) 形参:形式参数(变量),函数的入口,形参也相当于创建了一个私有变量 实参:函数执行传递给函数的具体值就是实参
# 函数自带属性
函数也是对象,也会有一些天生自带的属性
length
:0 形参的个数
name
:"Fn" 函数名
prototype
:类的原型,在原型上定义的方法都是当前Fn这个类实例的公
有方法
__proto__
:把函数当做一个普通的对象,指向Function这个类的原型
# call
apply
bind
call
fn.call(context,para1,…)
在借用数组原型上的方法把类数组转化为数组的时候,IE低版本浏览器中,元素集合或者节点集合这些类数组是无法借用slice转换的会报错“不是js对象”;
// ES5版
Function.prototype._call = function(ctx){
ctx = ctx || window
ctx._thisFn = this
var args = []
// 从索引为1开始,取参数
for(var i = 1; i < arguments.length; i++){
args.push('arguments[' + i + ']')
}
var res = eval('ctx._thisFn(' + args + ')')
delete ctx._thisFn
retrun res
}
// ES6版
Function.prototype._call = function(context, ...args){
// 1、给 context 默认值 一般是 window
context = context || window
// 2、生成一个context没有的属性名fn,把当前调用的函数this 作为一个属性fn挂到context
const fn = Symbol()
context[fn] = this
// 3、调用context[fn](...args)并传入参数,此时fn(当前调用函数)中的this就替换为了context
const res = context[fn](...args)
// 4、删除属性,返回结果
delete context[fn]
return res
}
function fn1(){
console.log(1);
}
function fn2(){
console.log(2);
}
fn1.call(fn2);//1
fn1.call.call.call.call(fn2);//2
把方法执行,并且让方法中的this改变为context,都是给fn传递的实参
//非严格模式下
function fn(){
console.log(this);
}
fn.call(1);//1
fn.call();//不传递参数时默认为window
fn.call(null);//传递null也为window
fn.call(undefined);//传递undefined也为window
//JS严格模式下
如果不传则默认为undefined
传的话传的是什么就修改为什么;
apply
apply的语法和call基本一致,作用原理也基本一致,唯一的区别:apply把传递给函数的实参以数组形式存放(但是也相当于在给函数一个个的传递实参值)
// ES5版本
Function.prototype._apply = function(ctx, args){
ctx = ctx || window
ctx._thisFn = this
args instanceof Array ? null : args = []
var vaules = []
for(var i = 0; i < args.length; i++){
vaules.push('args['+ i +']')
}
var res = eval('ctx._thisFn(' + vaules + ')')
delete ctx._thisFn
retrun res
}
// ES6版本
Function.prototype._apply = function(context, args){
// 1、给 context 默认值 一般是 window
context = context || window
// 2、生成一个context没有的属性名fn,把当前调用的函数this 作为一个属性fn挂到context
const fn = Symbol()
context[fn] = this
// 3、调用context[fn](...args)并传入参数,此时fn(当前调用函数)中的this就替换为了context
const res = context[fn](...args)
// 4、删除属性,返回结果
delete context[fn]
return res
}
bind
(bind方法返回的是一个新的函数和原函数空间地址不同)第一个参数为要改变的执行主体中的this关键字, 第一个之后的参数都是执行主体执行的时候需要传递给执行主体的参数,传递给执行主体的参数会放在执行主体自带的参数之前; 改变函数中this关键字,在IE6-8下不兼容;它和call(以及apply)改变this的原理不一样 预先让fn中的this指向opp,此时fn没有执行,只有fn执行的时候才起作用;
// ES5版
Function.prototype._Bind = function (ctx) {
ctx = ctx || window
var fn = this
var outerArgs = Array.prototype.slice.call(arguments,1)
return function(){
return fn._apply(ctx,outerArgs.concat(Array.prototype.slice.call(arguments)))
}
}
// ES6版
Function.prototype._Bind = function (context,...outerArgs) {
context = context || window
const fn = Symbol()
context[fn] = this
return (...innerArgs) => {
const args = outerArgs.concat(innerArgs)
context[fn](...args)
delete context[fn]
}
}
// 例如:点击盒子的时候,执行fn,并让其中的this改变为opp
function fn(){
console.log(this);
}
oBox.onclick = fn.call(opp);/无法实现,还没有点击的时候,fn已经执行了,只是把fn的返回结果给了onclick
oBox.onclick = fn.bind(opp);//完美实现,点击的时候fn才执行,并预先让fn中的this变为opp;
在真实项目中,我们把实现一个具体功能的代码封装在函数中:
好处:
1:减少了冗余代码,开发效率高;
2:封装在一个函数中,页面中就基本上很难出现重复一样的代码了,减少了页面中代码的冗余度,提高了代码的重复利用率:低耦合高内聚;
我们把以上的特点称之为 函数封装 (OOP面向对象编程思想,需要我们掌握的就是类的继承、封装、多态)
# 函数的核心原理
函数作为引用数据类型的一种,也是按照地址来操作的,私有作用域中不带var的声明都是给window设置的属性;
# 栈内存
栈内存
1、作用域(全局/私有作用域):提供一个供JS代码执行的环境;执行JS代码的 2、存储基本类型的值
释放 一般情况下,函数执行形成栈内存,函数执行完,浏览器会把形成的栈内存释放掉;
特殊情况:
- 1、函数执行完,但是函数的私有作用域内有内容被栈内存以外的东西(变量、元素事件)占用,那么这个栈内存就不能被释放掉
- 2、全局下的栈内存只有在页面关闭的时候才会被释放。
# 堆内存
堆内存
存储引用数据类型值的,对象类型就是键值对,函数类型就是字符串。
- 对象会把键值对储存起来
- 函数会把JS代码当做字符串储存起来
释放:将引用类型的空间地址赋值为null,或没有变量占用堆内存,浏览器就会释放这个地址。
var o = {}
; 当前对象对应的堆内存被变量o占用着呢,堆内存是无法销毁的o = null
; null空对象指针(不指向任何的堆内存),此时上一次的堆内存就没有被占用了,谷歌浏览器会在空闲时间把没有被占用的堆内存自动释放(销毁/回收)
# 定义变量的时候带var和不带var的区别?
TIP
在全局作用域中,带不带var
都一样,都相当于声明了一个全局变量,给全局对象设置了一个新的属性名,但是不带var
的不能提前声明,所以在赋值前不能提前调用;
在局部作用域中,带var
的话声明的都是私有变量,不带var
的话声明的都是全局变量,也相当于给window
设置了一个属性,也不能提前调用。
# 普通函数执行步骤
开辟一个私有作用域,然后把函数中的代码拿到私有作用域中执行
- 1、形参赋值
- 2、变量提升(形参赋值后,如果有函数声明且与形参名字相同,则覆盖形参的值,var声明相当于重复声明,不会覆盖形参的值)
- 3、代码从上到下执行
- 4、栈内存销毁、不销毁;
# 变量提升
预解释
在 当前作用域 中,JS代码从上而下执行之前,浏览器会把所有带var
和function
关键字的进行 提前声明,函数声明和定义同时完成;(只对当前作用域下的变量或者函数起作用)
预解释 : (所有声明变量或声明函数都会被提升到当前函数的顶部)
1、发现重复的,不重复声明,只定义,重复定义只会 替换 之前的值;(函数声明加定义一起完成)
2、预解释不管条件;(在最新的浏览器(IE11及以上),不管条件成立与否都会提前声明,不会提前定义,然后再看条件是否成立,如果成立则看有没有函数,如果有函数,则先定义,如果没有,JS代码从上到下执行成立的代码。如果不成立,则走不成立的代码)IE10及以下(不管条件是否成立函数都会进行声明+定义);
3、只对等号左边的进行变量提升,右边是值,不会提前声明什么的;
4、预解释发生在同一个脚本块中。
5、return
后面跟着的是值,不会进行预解释,但是return
下面的代码要进行预解释;
# 作用域
作用域
作用域就是查找变量的 **(去哪儿找,怎么找)
**的一套规则。
# 词法作用域
词法作用域
词法作用域我们也叫上级作用域。词法作用域是你在写代码的时候将变量和块级作用域写在哪里来决定的,因此当词法分析器处理代码时会保持作用域不变。所以通过变量定义的位置就能知道变量的作用域。
函数的上级作用域:看函数在哪个作用域下定义的,他的上级作用域就是谁,和函数在哪执行的没有关系。只有函数执行才会产生私有作用域
console.log(x,y);//undefined undefined
var x=10,
y=20;
function fn() {
console.log(x,y);//undefined 20(为什么不是100而是20,因为下一行的y=100还没有执行 哈哈哈被骗了吧)!
var x=y=100;
console.log(x,y);//100 100
}
fn();
console.log(x,y);//10 100 为什么x是10而不是100,因为闭包里的x=100外界无法拿到;
# 作用域链
TIP
作用域是根据名称查找变量的一套规则。实际情况中,通常需要同时顾及几个作用域。
在写代码时,当一个块或者函数嵌套在另一个块或函数中时,就发生了作用域的嵌套。因此,js在执行的时候,在当前作用域中无法找到某个变量时,引擎就会在外层嵌套的作用域中继续查找,直到找到该变量,或找到最外层作用域(也就是全局作用域)为止。
在私有作用域中 声明的变量 和 函数的形参 都是私有的变量; 在私有作用域中,代码执行的时候遇到一个变量,首先看它是否为私有变量:
是私有变量
- 则和外面没有任何关系,以后在这个作用域中操作的当前变量都按照私有的处理;
不是私有变量
- 则往当前作用域的上级作用域进行查找,如果上级作用域中有,我们操作的都是上级作用域的中的变量(假如我们在当前作用域把值改了,相当于把上级作用域中的这个值给修改了)。如果上级作用域也没有,则继续往上查找,一直照到
window
为止,这就是作用域链; - 如果找到window下也没有,分两种情况
- 我们是获取值:console.log(total);->报错,下面代码不再执行
- 我们是设置值:total=100;->相当于给window增加了一个属性名叫total,属性值为100;
- 则往当前作用域的上级作用域进行查找,如果上级作用域中有,我们操作的都是上级作用域的中的变量(假如我们在当前作用域把值改了,相当于把上级作用域中的这个值给修改了)。如果上级作用域也没有,则继续往上查找,一直照到
# 作用域是否销毁
TIP
堆内存 1、对象数据类型或者函数数据类型在定义的时候首先都会先开辟一个堆内存,堆内存有一个引用地址,如果有变量或者元素事件知道了这个地址,我们就说这个堆内存被占用了,那么就要考虑这个堆内存是否会销毁;
栈内存
- 全局作用域:只有当页面关闭的时候全局作用域才会销毁。
- 私有作用域:一般来说,函数体中的代码执行完成,形成的栈内存会立即释放(自执行函数也不例外),当然也有不释放的。
当私有作用域中的堆内存的地址被作用域以外的东西占用了,那么当前的这个作用域就不能被销毁(下面为三种情况)
- 函数执行形成一个私有作用域,如果私有作用域中的部分内容被以外的变量占用了,当前作用域不销毁
如:函数返回了一个
引用数据类型
的值,而且在外面有一个变量接受了这个返回值,此时当前作用域就不能销毁,想要销毁只需把外面的变量赋值为null,即解除占用即可;
如果返回的是一个基本数据类型的值,而且外面有一个变量接收,当前私有作用域是否会销毁?
会销毁,因为返回引用数据类型的值会在私有作用域中开辟一个堆内存,而且这个堆内存被外面的变量或元素元素事件占用着,所以这个堆内存不销毁,进而导致这个堆内存所在的私有作用域也不会销毁。而基本数据类型值是直接把值拷贝一份给外面的变量,所以返回完成后,函数中的堆内存没有被占用,就会被销毁掉,形成的私有作用域也会被销毁掉;
- 在一个私有的作用域中给DOM元素的事件绑定方法,一般情况下我们的私有作用域都不销毁
- 下述情况属于不立即销毁 函数执行返回一个函数没有被其他东西占用,但是还需要执行一次,所以暂时不销毁,当返回的函数执行完成后,浏览器会在空闲的时候把它销毁了;
- 函数执行形成一个私有作用域,如果私有作用域中的部分内容被以外的变量占用了,当前作用域不销毁
如:函数返回了一个
总结:作用域是否销毁就看两步:
- 1、这个作用域是否产生了堆内存
- 2、这个作用域产生的堆内存是否被这个作用域以外的变量或事件占用了,如果被占用了,那么这个堆内存不能被销毁,进而导致这个私有作用域也不能被销毁。
如果产生的堆内存被自身作用域中的变量占用了,那么这个堆内存会在被使用后销毁,所在的私有作用域也会被销毁
# 垃圾清除机制
标记清除
js 中最常用的垃圾收集方式是标记清除。垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记,然后它会去掉正在被使用变量的标记。而没有被去掉标记的将被视为准备删除的变量;最后,垃圾收集器完成内存清除工作,销毁那些带标记的值并回收他们所占用的内存空间; IE、Firefox、Opera、Chrome 和 Safari 的 js 实现使用的都是标记清除式的垃圾收集策略(或类似的策略),只不过垃圾收集的时间间隔互有不同
# 函数存在了多面性
函数的多面性
(1)、作为普通函数 fn()
:它本身就是一个普通的函数,执行的时候形成私有的作用域(闭包),形参赋值,预解释,代码执行,执行完成后栈内存销毁不销毁
(2)、作为类,即构造函数 new Fn()
:它有自己的实例,也有一个叫做prototype
属性是自己的原型,它的实例都可以指向自己的原型
(3)、作为普通对象 fn.aaa
:和var obj={}
中的obj一样,就是一个普通的对象,它作为对象可以有一些自己的私有属性,也可以通过__proto__
找到Function.prototype
- 所有的函数都可以调取
Function.prototype
(类也是函数)上的方法:如call、apply、bind
- 所有的对象都可以调取
Object.prototype
(函数也是对象)上的方法:如toString、hasOwnProperty
# 普通函数执行和构造函数执行的区别
区别
构造函数执行的时候,也是先形成一个私有作用域,形参赋值,变量提升,在代码从上而下执行之前,构造函数有特殊的操作:浏览器会在当前的作用域中默认创建一个对象数据类型的值,并且会让构造函数中的this指向创建的这个对象。 然后JS代码再执行,代码执行完成后,即使函数中没有写return,在构造函数模式中:浏览器会默认的把创建的对象返回到函数外面
总结:
- 构造函数执行期间,既具备函数执行的一面,也同时具备自己独有的操作:在构造函数执行期间,浏览器会默认创建一个对象,这个对象就是当前这个构造函数(类)实例,函数执行完成后,浏览器会默认的把这个实例返回。所以
new Fn()
执行,Fn是一个类,返回的结果就是Fn这个类的一个实例。
# 阿里面试题执行顺序总结
new Foo.getName();
new Foo().getName();
new new Foo().getName();
执行顺序总结:(经验之谈目前来说准确)
1、new 操作符只能new构造函数,否则会报错,xxx is not a constructor
2、new碰到()就会把new与()之间的当成构造函数处理,所以会先执行new与()之间的运算,之后再new。
# 闭包
闭包
闭包简单的说就是私有作用域的快照。函数(A)
嵌套函数(B)
时,为了让函数(B)
顺利执行,会对作用域链进行了tree-shaking
,只留下函数(B)
执行所需要的外部引用,保存在堆内存中,作为函数(B)
的[[Scopes]]
属性的值,让函数(B)
不管在哪执行,都能随时访问用到的外部引用。 形成的这个堆内存叫做闭包。
闭包解决了什么问题?
- 1、闭包解决了函数作用域的瞬时性的问题(保存需要用到的数据),比如计数器、校验邮箱函数
- 2、闭包解决了作用域共享的问题(保护私有变量不受外界干扰),比如选项卡
什么是函数作用域的瞬时性问题? 函数执行形成私有作用域,函数执行完成之后,如果没有返回函数被外部占用,私有作用域就会被释放。对于一些之后的操作,就无法再引用私有作用域中的一些数据。这就是函数作用域的瞬时性。
函数执行会形成一个私有的作用域,保护私有变量不受外部影响,从外部拿不到里面的变量,此时我们可以理解为私有作用域把私有变量保护了起来,这种保护机制称之为'闭包'。由方法运行而产生的私有作用域就叫闭包,为了让变量更安全。闭包是一种机制而不是某种形式;
闭包面试总结:
- 闭包是指有权访问另一个函数作用域中变量的函数
- 闭包就是连接私有作用域和外部作用域之间的一座桥梁,它不仅能保护私有变量不受外界干扰,还能保存一些内容,而且还能把私有作用域中的东西拿到外部使用。
# 如果我想在外部使用闭包中的值怎么办?
1.在闭包中设置返回值,在外部声明一个变量接收。
2.把闭包中的值赋值给window的一个属性。
# 闭包的作用
1、保护
形成私有作用域,保护里面的私有变量不受外界的干扰,真实项目中,我们利用这种保护机制,实现团队协作开发(避免了多人同一个命名,导致代码冲突的问题)
- jQuery:常用的JS类库,提供了很多项目中常用的方法(兼容所有浏览器)
- Zepto:小型JQ,专门为移动端开发准备的
- 用闭包代替全局变量
//JQ代码片段
(funciton(window)){
var jQuery=function(){
....
}
....
window.jQuery=window.$=jQuery;
}(window);
jQery()
$()
//Zepto代码片段
var Zepto=(function(){
var Zepto=funciton(){
...
};
....
return Zepto;
})();
var $=Zepto
Zepto();
$();
真实项目中,我们利用这种保护机制,实现团队协作开发(避免了多人同一个命名,导致代码冲突的问题)
//A
(funciton()){
//A写的代码
function fn(){
....
}
....
window.fn=fn;
}();
//B
(function(){
//B写的代码
funciton fn(){
...
};
//B想要调取A写的fn
window.fn();
})();
2、保存
函数执行形成一个私有作用域,函数执行完成,形成的这个栈内存一般情况都会自动释放
但是当栈内存中的内容被栈内存以外的其他东西(变量/元素的事件)占用了,那么这个栈内存就不能被释放掉,也就形成了不销毁的私有作用域(里面的私有变量也不会销毁);
应用:高级单例模式,封装插件;
# i++与++i的区别:
TIP
i++
,先拿原有的值和其他值运算,运算后再累加1;
++i
,先累加1,再拿结果进行运算;
++i
和i=i+1
的区别
++i
在拿到i的值的时候,会先用Number()
转化一下,然后再累加1,之后再运算,而i=i+1
不会;
# 函数的中的 this
指向问题
函数中的
this
指向是函数运行时决定的,不是编写代码时绑定的。
this的绑定规则:
- 1、默认绑定
在非严格模式下,函数在独立调用时,函数中的this
使用默认绑定,this
指向全局对象。如果使用严格模式,则this
会绑定到undefined
。可以把这条规则看做是无法应用其他规则时的默认规则。 - 2、隐式绑定
当函数引用有上下文对象时,隐式绑定会把函数调用中的this
绑定到这个上下文对象。而且对象属性引用链上只有上一层或者说最后一层才会有隐式绑定的作用。function foo(){ console.log(this.a) } var obj1 = { a:32, foo:foo } var obj2 = { a:42, obj1:obj1 } obj2.obj1.foo(); // 32
- 隐式丢失问题:间接引用的函数执行时隐式绑定的
this
会丢失,丢失后会应用默认绑定
参数传递其实就是一种隐式赋值。function foo(){ console.log(this.a) } var obj = { a:32, foo:foo } var bar = obj.foo // 间接引用,函数别名 var a = 'oops, global' bar(); // oops, global
- 隐式丢失问题:间接引用的函数执行时隐式绑定的
- 3、显示绑定
使用bind、call、apply
可以强制的把函数中的this
绑定到提供的对象上。(箭头函数不适用) - 4、
new
绑定
当使用new
操作符来执行函数时,我们会构造一个新对象,并把它绑定到函数中的this
上。
绑定规则优先级
new
绑定 > 显示绑定 > 隐式绑定 > 默认绑定
全局中的this是window,我们都研究函数内部的this的指向问题
在JS的非严格模式下
- 1、自执行函数中的this都是window
- 2、给元素的某个事件绑定方法,当事件触发执行对应方法的时候,方法中的this一般都是当前操作的元素本身(在IE6-8下DOM2级事件用attachEvent绑定方法时,方法中的this是window,而不是当前操作元素本身)
- 3、函数执行前的主体
fn()
;this
为windowsobj.fn()
;this
为obj
- 4、ES6中箭头函数中的this继承宿主环境中的this:看方法在哪定义的,宿主环境就是谁,箭头函数中的this就是宿主环境中的this;
var obj={
fn:function(){
//=>this:obj
setTimeout(function(){
//=>this:window 不管在哪执行,定时器中的this是window
},1000);
//=>想让定时器函数中的this也是obj
setTimeout(function(){
//=>this:obj
}.bind(this),1000);
var _this=this;
setTimeout(function(){
//=>_this:obj
_this.name ='xxx';
},1000);
setTimeout(()=>{
//=>this:obj 箭头函数中的this继承宿主环境(上级作用域中)的this
},1000);
}
};
obj.fn();
在JS严格模式下(让JS更加严谨)
开启严格模式:在当前作用域的第一行加 use strict
。开启后所有作用域下再执行的JS代码都按照严格模式处理
- 严格模式下,没有写执行主体的话,
this
就是undefined
;
# arguments实参集合
只有传递了实参才会保持映射,不传递一直都是undefined
当我们不知道用户具体要传递几个值得时候(传递几个值都可以),此时我们无法设置形参的个数;遇到此种情况,需要使用函数内置的实参集合:arguments
1、arguments
只有函数才有,是一个类数组集合
- 1、以数字作为索引(属性名),从零开始
- 2、有一个
length
属性,存储的是当前实参的个数arguments.length
- 3、
arguments.callee
动态的得到当前执行函数的方法名 2、不管执行函数是否传递参数,arguments
天生就存在,没有传递参数时arg是个空的集合,传递了参数的arg中包含了所有传递的参数信息,如果没有传递实参,那么形参的值在arguments
中就是undefined
如果有变量名与形参冲突,那么操作的一直都是私有变量,argument中没有传递实参的形参的值还是undefined
var a=12,b=13,c=14;
~function(a,b){
//b没有传递实参所以在arguments中的值一直都是undefined
b=b||0;
arguments[0]=100;
var b=c=200;
console.log(a);
console.log(arguments[1]);//undefined
}(a);
console.log(a);
console.log(b);
console.log(c);
非严格模式下,如果传递的有实参,那么arguments的值永远和对应命名参数的值保持同步,如果没有传递实参,则arguments中形参对应的值就一直是undefined(来自高程3第66页,在严格模式下arguments将不与实参保持映射关系。重写arguments 的值会导致语法错误(代码将不会执行)),但是如果使用delete 删除了arguments中某一个索引对应的值,再去修改实参,那么实参和arguments将不再保持同步;
(function (a) {
delete arguments[0];
a=1;
console.log(a);//1
console.log(arguments);
//[empty × 1, callee: ƒ, Symbol(Symbol.iterator): ƒ]
// callee:ƒ (a)
// length:1
// Symbol(Symbol.iterator):ƒ values()
// __proto__:Object
})(2);
# JS中函数的返回值return
return
函数中是有返回值的,我们如果想在外面使用函数私有的一些信息,那么就需要通过return,把这些信息返回出来供外面使用。如果不写return
或者return
后面什么都不写,则默认返回undefined
。
在函数体中,return
后面返回的都是值:如果是一个表达式的话会先计算出结果之后再把结果返回
function sum(){
var val=0;
return val;//返回的不是val变量,而是val所存储的值
}
var a=function(){
return function(){
var a=1,b=2;
return a+b;
}();
}
console.log(a());//输出结果为 3,因为后面是一个自执行函数表达式,所以需要计算出结果之后再把结果返回
sum //代表函数本身 sum() //函数执行,代表的是当前函数执行后返回的结果(return后面是什么函数返回的就是什么)
# 具名函数表达式
(function A(){
A = 1;
console.log(A);//[FunctionA]
})()
具名函数表达式的标识符只能在函数内部访问,函在函数外部访问不到。
绑定为函数名的标识符不能再绑定为其他值,即该标识符绑定的值是不可更改的;
# JS中的匿名函数
没有名字的函数
函数表达式—>把一个匿名函数(有名字也可以)作为值赋值给一个变量或者一个元素的某个事件)
- 声明的变量指向函数体时(即函数表达式),变量的声明会被提升(foo的声明会被提升),但是它指向的函数体只会在执行的时候才被赋值。对于
var bar = function foo(){}
;语句,其实就是一个有效的命名函数表达式,但有一点需要记住:命名函数表达式的标示符(即函数名称)在外部作用域是无效的,在其内部作用域是有效的。 自执行函数—>创建函数和执行函数放在一起了,创建完成后立马执行;(自执行函数什么时候执行?预解释之后,JS代码从上到下执行,碰到自执行函数时,才会创建和执行;) 以下都是自执行函数,符号只是控制语法规范 除了第一种,其他4种方式都会改变自执行函数的返回结果 (function(){})()
;~function(n){}(10)
; ~:按位非,执行按位非的结果就是返回数值的反码-function(n){}(10)
;+function(n){}(10)
;!function(n){}(10)
;