函数的五种声明方式
具名函数
function name(x,y){
}
- 函数会自动加上
return undefined - consolo.log()永远返回undefined
匿名函数(函数表达式)
var a
a=function (x,y){
}
- 不能单独使用,必须赋值给一个变量
具名函数赋值
var a
a=function name(x,y){
}
tips:其中 name 为函数标示符,只在函数体内可访问,辣鸡
构造函数声明
var a=new Function('x','y','函数体')
箭头函数
-
标准语法
let a=(x,y)=>{return x+y} -
如果只有一个函数体,return和花括号可以省略,同时省略
let a=(x,y)=>x+y -
如果只有一个参数,圆括号可以省略,同时省略return
let a=n=>n*n
为什么要出现箭头函数
因为this大家看不懂-。-,箭头函数没有this,如果你使用this,那么箭头函数里面的this就会像变量一样,从外面去找
函数的name
function f(){}
//f.name='f'
var f1=function(){}
//f1.name='f1'
var f2=function f3(){}
//f2.name='f3'
var f4=new Function('x','y','return x+y')
//f4.name='anonymous'
js是真滴辣鸡,兄弟,当做函数内部的名字优先级高吧
函数的调用
var a=function(){}
a()===a.call(this,argumens)
左边是是右边的语法糖,右边的才是真正的调用,call牛逼
call stack 调用堆栈
由于js是单线程的,所以js在执行一长串代码的时候,它会把当前的环境都记住,当它看一个函数的时候,它将进入函数内部,并且留下记号,记录位置,放在调用栈顶部,执行完成后再从栈中移除这个函数
函数的调用机制,先进后出,也就是LIFO(last in first out)
栈的数目是有限的,当你堆栈上限溢出时,就会stack overflow
function c() {
console.log('c');
return 'c'
}
function b() {
console.log('b');
c();
return 'b'
}
function a() {
console.log('a');
b();
return 'a'
}
a.call();
执行过程a.call→console.log('a')→b.call()→console.log('b')→c.call()→console.log('c')→return 'c'→returbn 'b'→ return 'a'
callback
- 名词形式:被当做参数的函数就是回调,必须要另外一个函数B里面调用这个函数参数A
- 动词形式:调用这个回调
- 注意回调跟异步没有任何关系
- 举个栗子,我要买个手办,给淘宝老板说,有货了喊我,那么我给老板留个电话,喊他有货了call我,那么在代码中就是这样
我传一个函数A(我的电话号码)给函数B(老板的店铺),老板一有货了,就会在函数B(老板的店铺)中call函数A(给我打电话),这就是回调。
Don't call me; I'll call you!
词法作用域
又名静态作用域
传说中的在函数中声明变量不加var是声明全局变量[(a=1),优先当作赋值好吧],错误!
如果作用域中没有当前变量的声明,把a=1当作赋值,然后找他父作用域,如果沿着作用域树找,如果全局范围内有a,就是a=1的赋值,如果全局范围内都没有当前变量的声明,就会当成全局变量的声明并赋值!!
var a=1
function f(){
console.log(a)//a=2
}
a=2
f.call()
!!!!作用域是指,我们能确定的是f中的a是那个的a,但是并不能确定他的值!!!
分析代码是先提升变量
变量提升
看到一个挺好的例子,引用一下 函数声明和变量声明都会提升,且函数声明更置顶
var a = 100
function a(){
console.log(a)
}
a()//a is not a function
上面的代码等于
function a(){
console.log(a)
}
var a
a = 100
a()
a被重新赋值了,所以a is not a function
foo()//TypeError
bar()//ReferenceError
var foo = function bar(){
//
}
可以看成这种形式
var foo
foo()
bar()
forr = function (){
//
}
根本原因
- 函数声明会提升
- 变量声明也会提升
- 函数表达式不会提升
- 函数声明比变量声明更置顶:(函数在变量上面)
- 变量和赋值语句一起书写,在js引擎解析时,会将其拆成声明和赋值2部分,声明置顶,赋值保留在原来位置
- 声明过的变量不会重复声明
执行上下文和作用域
概念
- 作用域:作用域代表着已声明变量或者函数的访问范围,在作用域内使用变量会先从当前作用域寻找,如果没有会往作用域上端寻找,直到全局作用域
- 执行上下文:每一个函数都有自己的执行上下文EC(执行环境 execution context),并且每个执行上下文中都有它自己的变量对象VO(Variable object),用于存储执行上下文中的变量 、函数声明 、函数参数,这解释了js如何找到我们定义的函数和变量。并且函数是js中唯一一个能创建出作用域的(ES5)
- 作用域和执行上下文之间最大的区别是:执行上下文在运行时确定,随时可能改变;作用域在定义时确定,永远不会改变。
- V0和A0:未进入执行阶段之前,变量对象(VO)中的属性都不能访问!但是进入执行阶段之后,变量对象(VO)转变为了活动对象(AO),里面的属性都能被访问了,然后开始进行执行阶段的操作。 它们其实都是同一个对象,只是处于执行上下文的不同生命周期
执行上下文
又称词法环境,它是存储和处理数据的栈,主要分为两部分
一个例子来说明:
function foo(a) {
var b = 2;
function c() {}
var d = function() {};
b = 3;
}
foo(1);
- 预处理阶段:
- 在这个阶段它会将所有的var类型的变量存入上下文中并给它赋值为undefined;
- 此之外,它还会将声明的函数存入栈中,并将函数体赋值给它。
- 还有一个就是this对象
- 函数上下文的变量对象初始化只包括 Arguments 对象
AO = {
arguments: {
0: 1,
length: 1
},
a: 1,
b: undefined,
c: reference to function c(){},
d: undefined
}
- 执行阶段:
- 给词法环境中的变量进行赋值
- 如果词法环境中不存在这个变量,就把它加入到词法环境并给它赋予相应的值。
AO = {
arguments: {
0: 1,
length: 1
},
a: 1,
b: 3,
c: reference to function c(){},
d: reference to FunctionExpression "d"
}
函数执行上下文中作用域链和变量对象的创建过程
以下面这个函数为例
var scope = "global scope";
function checkscope(){
var scope2 = 'local scope';
return scope2;
}
checkscope();
- 函数被创建保存作用域链到内部属性[[scope]
checkscope.[[scope]] = [
globalContext.VO
];
- 执行函数,创建函数执行上下文,函数的执行上下文被压入执行上下文栈
ECStack = [
checkscopeContext,
globalContext
];
- 函数并不立即执行,开始做准备工作,复制词法环境所生成的作用域链到自己的作用域链
checkscopeContext = {
Scope: checkscope.[[scope]],
}
- 用arguments 创建AO,随后初始化活动对象,加入形参、函数声明、变量声明
checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope2: undefined
},
Scope: checkscope.[[scope]],
}
- 将AO压入作用域链顶端
checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope2: undefined
},
Scope: [AO, [[Scope]]]
}
- 准备工作做完,开始执行函数,随着函数的执行,修改 AO 的属性
checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope2: 'local scope'
},
Scope: [AO, [[Scope]]]
}
- 查找到 scope2 的值,返回后函数执行完毕,函数上下文从执行上下文栈中弹出
ECStack = [
globalContext
];
闭包
如果一个函数使用了它范围外的变量,那么这个(函数+这个变量)=闭包
意义:隐藏变量,操作一个你访问不到的变量
function foo(){
var local = 1
function bar(){
local++
return local
}
return bar
}
foo()()
ECMAScript中,闭包指的是:
- 从理论角度:所有的函数。因为它们都在创建的时候就将上层上下文的数据保存起来了。哪怕是简单的全局变量也是如此,因为函数中访问全局变量就相当于是在访问自由变量,这个时候使用最外层的作用域。
- 从实践角度:以下函数才算是闭包:
即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回)
在代码中引用了自由变量
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f;
}
var foo = checkscope();
foo();
执行过程
进入全局代码,创建全局执行上下文,全局执行上下文压入执行上下文栈
全局执行上下文初始化
执行 checkscope 函数,创建 checkscope 函数执行上下文,checkscope 执行上下文被压入执行上下文栈
checkscope 执行上下文初始化,创建变量对象、作用域链、this等
checkscope 函数执行完毕,checkscope 执行上下文从执行上下文栈中弹出
执行 f 函数,创建 f 函数执行上下文,f 执行上下文被压入执行上下文栈
f 执行上下文初始化,创建变量对象、作用域链、this等
f 函数执行完毕,f 函数上下文从执行上下文栈中弹出
当 f 函数执行的时候,checkscope 函数上下文已经被销毁了啊(即从执行上下文栈中被弹出),怎么还会读取到 checkscope 作用域下的 scope 值呢?
当我们了解了具体的执行过程后,我们知道 f 执行上下文维护了一个作用域链:
fContext = {
Scope: [AO, checkscopeContext.AO, globalContext.VO],
}
什么是this
this就是call的第 一个参数
this必须是对象,this是对象和函数之间的羁绊,一切都是命运石之门的选择呀,岂可修.
var a=function(){}
a.call(this,arguments)
tips:在非严格模式下,null和undefined的this值会自动指向window
-
this就指向调用函数的那个对象
-
Array.prototype.slice.call(arguments)可以将伪数组转化为数组,因为arguments是个伪数组没有slice方法,我们call(arguments)把this指向这个对象,相当于让arguments有了slice这个方法(arguments.slice()) -
那么
Array.prototype.slice.call(arguments,1);这句话的意思就是说把调用方法的参数截取出来。(arguments.slice(1))
var value = 1;
var foo = {
value: 2,
bar: function () {
return this.value;
}
}
//示例1
console.log(foo.bar())//2
//示例2
console.log((foo.bar)())//2
//示例3
console.log((foo.bar = foo.bar)())//1
//示例4
console.log((false || foo.bar)())//1
//示例5
console.log((foo.bar, foo.bar)())//1
|| , =等运算符会直接 返回一个匿名函数 function () { return this.value;} ,那么就相当于变成了全局调用,那么this在非严格模式下指向window
判断this,关键在于判断表达式结果是否是Reference类型,深入了解
Reference
这里的 Reference 是一个 Specification Type,也就是 “只存在于规范里的抽象类型”。它们是为了更好地描述语言的底层行为逻辑才存在的,但并不存在于实际的 js 代码中
Reference由三个部分组成
base value
referenced name
strict reference
举个例子
var foo = 1;
// 对应的Reference是:
var fooReference = {
base: EnvironmentRecord,
name: 'foo',
strict: false
};
再来一个
var foo = {
bar: function () {
return this;
}
};
foo.bar(); // foo
// bar对应的Reference是:
var BarReference = {
base: foo,
propertyName: 'bar',
strict: false
};
获取 Reference 组成部分的方法,比如 GetBase 和 IsPropertyReference。
- GetBase
返回 reference 的 base value。
- IsPropertyReference
简单的理解:如果 base value 是一个对象,就返回true。
判断是否是Reference
1. 如果 ref 是 Reference,并且 IsPropertyReference(ref) 是 true, 那么 this 的值为 GetBase(ref)
2. 如果 ref 是 Reference,并且 base value 值是 Environment Record, 那么this的值为 ImplicitThisValue(ref)
3. 如果 ref 不是 Reference,那么 this 的值为 undefined
call/apply/bind
- 用来改变this的指向
call()和apply()的区别在于就是call()方法接受的是若干个参数的列表,而apply()方法接受的是一个包含多个参数的数组或者为伪数组(要有lenght).- bind()不会立即执行,bind()相当于创建一个新的函数,与调用函数拥有相同的函数体,同时,当新函数被调用时 this 值绑定到 bind() 的第一个参数,同时调用时的参数被提供给新函数.
高阶函数
- 在数学和计算机科学中,高阶函数是至少满足下列一个条件的函数:
- 接受一个或多个函数作为输入:forEach sort map filter reduce
- 输出一个函数:lodash.curry
- 不过它也可以同时满足两个条件:Function.prototype.bind
- 高阶函数的作用:可以任意组合函数
arguments
auguments是函数的参数,是个伪数组,not array。 我们可以使用一些技巧把他变成真数组
let args=[].slice.call(arguments) //ES5
let args=Array.from(arguments) //ES6
let args=[...arguments] //ES6
new的作用
new的本质是Brendan Eich为了js模拟类的一个语法糖
- 创建一个空对象,
- this=空对象
- 绑定原型,this.proto===构造函数的.protype
- 构造函数.protype={constructor:构造函数A}
- 执行fn.call(this.arguments)
- return this
function 构造函数A(x){
this.属性a=x
this.属性b=xxxxxxxxx
var temp={}
this=temp
构造函数A.protype={
constructor:构造函数A,
属性a:()=>{},
属性b:()=>{},
...
}
this.__proto__ === 构造函数A.protype
return this
}
prototype就是共有属性
constructor 属性
我们需要记录下来是临时对象是由哪个函数创建的,默认在构造函数.protyped的属性中加了一个constructor 如果我们直接对protype赋值,那么这个constructor这个属性将覆盖掉,所以我们直接在protype上加属性,或者赋值的时候添加constructor属性
构造函数A.protype={
constructor:构造函数A
a:a,
b:b,
c:c,
}
------------我4分割线--------------
构造函数A.protype.a=a
构造函数A.protype.b=b
构造函数A.protype.c=c
立即执行函数
在es6 的let出现之前,使用局部变量得这样
立即调用函数
避免全局污染
(function(){}).call()(function(){}.call())-function(){}.call()+function(){}.call()!function(){}.call()~function(){}.call()
立即执行函数用来创建局部作用域,闭包用来间接访问一个变量,但是不能直接操作这个变量,
这个可以return在赋值给全局变量,也可以直接赋值给window
!function (){
var local = 1
window.bar=function (){
local++
return local
}
}.call()
bar()
var bar=(function (){
var local = 1
function bar (){
local++
return local
}
return bar
}).call()
bar()