You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
var name = '我是全局变量 name';
// 声明式
function a () {
var name = '我是函数a中的name';
return name;
}
console.log(a()); // 打印: "我是函数a中的name"
// 表达式
var b = function() {
var name = '我是函数b中的name';
return name; // 打印: "我是函数b中的name"
}
console.log(b())
// Function构造函数
function c() {
var name = '我是函数c中的name';
return new Function('return name')
}
console.log(c()()) // 打印:"我是全局变量 name",因为Function()返回的是全局变量 name,而不是函数体内的局部变量。
var start = new Date().getTime()
for(var i = 0; i < 10000000; i++) {
var fn = new Function('a', 'b', 'return a + b')
fn(i, i+1)
}
var end = new Date().getTime();
console.log(`使用Function构造函数方式所需要的时间为:${(end - start)/1000}s`)
// 使用Function构造函数方式所需要的时间为:8.646s
start = new Date().getTime();
var fn = function(a, b) {
return a + b;
}
for(var i = 0; i < 10000000; i++) {
fn(i, i+1)
}
end = new Date().getTime();
console.log(`使用表达式的时间为:${(end - start)/1000}s`)
// 使用表达式的时间为:0.012s
由此可见,在循环体中,使用表达式的执行效率比使用 Function()构造函数快了很多很多。所以在 Web 开发中,为了加快网页加载速度,提高用户体验,我们不建议选择 Function ()构造函数方式来定义函数。
console.log(typeof f) // function
console.log(typeof c) // undefined
console.log(typeof d) // undefined
function f () {
return 'JS 深入浅出'
}
var c = function () {
return 'JS 深入浅出'
}
console.log(typeof c) // function
var d = new Function('return "JS 深入浅出"')
console.log(typeof d) // function
function sum1 (x, y) {
var total = x + y
}
console.log(sum1()) // undefined
function sum2 (x, y) {
return x + y
}
console.log(sum2(1, 2)) // 3
如果函数调用时在前面加上了new前缀,且返回值不是一个对象,则返回this(该新对象)。
function Book () {
this.bookName = 'JS 深入浅出'
}
var book = new Book();
console.log(book); // Book { bookName: 'JS 深入浅出' }
console.log(book.constructor); // [Function: Book]
如果返回值是一个对象,则返回该对象。
function Book () {
return {bookName: JS 深入浅出}
}
var book = new Book();
console.log(book); // { bookName: 'JS 深入浅出' }
console.log(book.constructor); // [Function: Book]
var blobal1 = 1;
function fn1 (param1) {
var local1 = 'local1';
var local2 = 'local2';
function fn2(param2) {
var local2 = 'inner local2';
console.log(local1)
console.log(local2)
}
function fn3() {
var local2 = 'fn3 local2';
fn2(local2)
}
fn3()
}
fn1()
阿里云最近在做活动,低至2折,有兴趣可以看看:
https://promotion.aliyun.com/ntms/yunparter/invite.html?userCode=pxuujn3r
函数是什么
函数是完成某个特定功能的一组语句。如没有函数,完成任务可能需要五行、十行、甚至更多的代码。这时我们就可以把完成特定功能的代码块放到一个函数里,直接调用这个函数,就省重复输入大量代码的麻烦。
函数可以概括为:一次封装,四处使用。
函数的定义
函数的定义方式通常有三种:函数声明方式、函数表达式、 使用Function构造函数 。
函数声明方式
语法:
例:
函数表达式
语法:
例:
使用Function构造函数
Function构造函数可以接收任意数量的参数,最后一个参数为函数体,其他的参数则枚举出新函数的参数。其语法为:
例:
三种定义方式的区别
三种方式的区别,可以从作用域、效率以及加载顺序来区分。
从作用域上来说,函数声明式和函数表达式使用的是局部变量,而
Function()
构造函数却是全局变量,如下所示:从执行效率上来说,
Function()
构造函数的效率要低于其它两种方式,尤其是在循环体中,因为构造函数每执行一次都要重新编译,并且生成新的函数对象。来个例子:
由此可见,在循环体中,使用表达式的执行效率比使用
Function()
构造函数快了很多很多。所以在 Web 开发中,为了加快网页加载速度,提高用户体验,我们不建议选择Function ()
构造函数方式来定义函数。最后是加载顺序,
function
方式(即函数声明式)是在 JavaScript 编译的时候就加载到作用域中,而其他两种方式则是在代码执行的时候加载,如果在定义之前调用它,则会返回undefined
:函数的参数和返回值
函数的参数-arguments
JavaScript 中的函数定义并未指定函数形参的类型,函数调用也未对传入的实参值做任何类型检查。实际上,JavaScript 函数调用甚至不检查传入形参的个数。
当实参比形参个数要多时,剩下的实参没有办法直接获得,需要使用即将提到的
arguments
对象。JavaScript中的参数在内部用一个数组表示。函数接收到的始终都是这个数组,而不关心数组中包含哪些参数。在函数体内可以通过
arguments
对象来访问这个参数数组,从而获取传递给函数的每一个参数。arguments
对象并不是Array的实例,它是一个类数组对象,可以使用方括号语法访问它的每一个元素。arguments
对象的length
属性显示实参的个数,函数的length
属性显示形参的个数。函数的参数-arguments
JavaScript 中的函数定义并未指定函数形参的类型,函数调用也未对传入的实参值做任何类型检查。实际上,JavaScript 函数调用甚至不检查传入形参的个数。
函数的参数-同名参数
在非严格模式下,函数中可以出现同名形参,且只能访问最后出现的该名称的形参。
而在严格模式下,出现同名形参会抛出语法错误。
函数的参数-参数个数
当实参比函数声明指定的形参个数要少,剩下的形参都将设置为
undefined
值。函数的返回值
所有函数都有返回值,没有
return
语句时,默认返回内容为undefined
。如果函数调用时在前面加上了
new
前缀,且返回值不是一个对象,则返回this
(该新对象)。如果返回值是一个对象,则返回该对象。
函数的调用方式
JS 一共有4种调用模式:函数调用、方法调用、构造器调用和间接调用。
函数调用
当一个函数并非一个对象的属性时,那么它就是被当做一个函数来调用的。对于普通的函数调用来说,函数的返回值就是调用表达式的值
使用函数调用模式调用函数时,非严格模式下,
this
被绑定到全局对象;在严格模式下,this
是undefined
方法调用
当一个函数被保存为对象的一个属性时,称为方法,当一个方法被调用时,
this
被绑定到该对象。咱们注意到,当调用
printValue
时,this
绑定的是全局对象(window),打印全局变量value
值1
。但是当调用myObject.m()
时,this
绑定的是方法m
所属的对象Object,所以打印的值为Object.value
,即2
。构造函数调用
如果函数或者方法调用之前带有关键字
new
,它就构成构造函数调用。参数处理:一般情况构造器参数处理和函数调用模式一致。但如果构造函数没用形参,JavaScript构造函数调用语法是允许省略实参列表和圆括号的。
如:下面两行代码是等价的。
函数的调用上下文为新创建的对象。
1.第一次调用
Book()
函数是作为构造函数调用的,此时调用上下文this
被绑定到新创建的对象,即book
。所以全局变量bookName
值不变,而book
新增一个属性bookName
,值为'ES6 深入浅出'
;2.第二次调用
Book()
函数是作为普通函数调用的,此时调用上下为this
被绑定到全局对象,在浏览器中为window
。所以全局对象的bookNam
值改变为' 新版JS 深入浅出'
,而book
的属性值不变。间接调用
JS 中函数也是对象,函数对象也可以包含方法,
call()
和apply()
方法可以用来间接地调用函数。这两个方法都允许显式指定调用所需的
this
值,也就是说,任何函数可以作为任何对象的方法来调用,哪怕这个函数不是那个对象的方法。两个方法都可以指定调用的实参。call()
方法使用它自有的实参列表作为函数的实参,apply()
方法则要求以数组的形式传入参数。词法(静态)作用域与动态作用域
作用域
通常来说,一段程序代码中所用到的名字并不总是有效/可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域。
词法作用域
词法作用域,也叫静态作用域,它的作用域是指在词法分析阶段就确定了,不会改变。而与词法作用域相对的是动态作用域,函数的作用域是在函数调用的时候才决定的。
来个例子,如下代码所示:
当浏览器看到这样的代码,不会马上去执行,它会先生成一个抽象语法树。上述代码生成的抽象语法树大概是这样的:
执行
fn1
函数,fn1
中调用fn3()
,从fn3
函数内部查找是否有局部变量local1
,如果没有,就根据抽象树,查找上面一层的代码,也就是local1
等于'local1'
,所以结果会打印'local1'
。同样的方法查找是否有局部变量
local2
,发现当前作用域内有local2
变量,所以结果会打印'inner local2
。思考
有如下的代码:
两个问题:
fn
里面的变量a
, 是不是外面的变量a
。fn
里面的变量a
的值, 是不是外面的变量a
的值。对于第一个问题:
分析一个语法,就能确定函数
fn
里面的a
就是外面的a
。对于第二个问题:
函数
fn
里面的变量a
的值, 不一定是外面的变量a
的值,假设咱们这样做:这时候当咱们执行
fn()
的时候,打印a
的值为2
。所以如果没有看到最后,一开始咱们是不知道打印的a
值到底是什么。所以词法作用域只能确定变量所在位置,并不能确定变量的值。
调用栈(Call Stack)
什么是执行上下文
执行上下文就是当前JavaScript代码被解析和执行是所在环境的抽象概念,JavaScript中运行任何的代码都是在执行上下文中运行。
执行上下文的类型,主要有两类:
全局执行上下文:这是默认的,最基础的执行上下文。不在任何函数中的代码都位于全局执行上下文中。共有两个过程:1.创建有全局对象,在浏览器中这个全局对象就是
window
对象。2.将this
指针指向这个全局对象。一个程序中只能存在一个执行上下文。函数执行上下文:每次调用函数时,都会为该函数创建一个新的执行上下文。每个函数都拥有自己的执行上下文,但是只有在函数被调用的时候才会被创建。一个程序中可以存在多个函数执行上下文,这些函数执行上下文按照特定的顺序执行一系列步骤,后文具体讨论。
调用栈
调用栈,具有
LIFO
(Last in, First out 后进先出)结构,用于存储在代码执行期间创建的所有执行上下文。当JavaScript引擎首次读取脚本时,会创建一个全局执行上下文并将其
push
到当前执行栈中。每当发生函数调用时,引擎都会为该函数创建一个新的执行上下文并push
到当前执行栈的栈顶。引擎会运行执行上下文在执行栈栈顶的函数,根据
LIFO
规则,当此函数运行完成后,其对应的执行上下文将会从执行栈中pop
出,上下文控制权将转到当前执行栈的下一个执行上下文。看看下面的代码:
有几个点需要注意:
a
调用下面定义的函数b
, 函数b
调用函数c
当它被执行时你期望发生什么? 是否发生错误,因为
b
在a
之后声明或者一切正常?console.log
打印的变量又是怎么样?以下是打印结果:
1. 变量和函数声明(创建阶段)
第一步是在内存中为所有变量和函数分配空间。 但请注意,除了
undefined
之外,尚未为变量分配值。 因此,myVar
在被打印时的值是undefined
,因为JS引擎从顶部开始逐行执行代码。函数与变量不一样,函数可以一次声明和初始化,这意味着它们可以在任何地方被调用。
所以以上代码在创建阶段时,看起来像这样子:
这些都存在于JS创建的全局上下文中,因为它位于全局作用域中。
在全局上下文中,JS还添加了:
window
对象,NodeJs 中是global
对象)this
指向全局对象2. 执行
接下来,JS 引擎会逐行执行代码。
myOtherVar = 10
在全局上下文中,myOtherVar
被赋值为10
已经创建了所有函数,下一步是执行函数
a()
每次调用函数时,都会为该函数创建一个新的上下文(重复步骤1),并将其放入调用堆栈。
如下步骤:
a
函数里面没有声明变量和函数this
并指向全局对象(window)myVar
,myVar
属于全局作用域的。函数 b
,函数b
的过程跟a
一样,这里不做分析。下面调用堆栈的执行示意图:
代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具Fundebug。
交流
阿里云最近在做活动,低至2折,有兴趣可以看看:https://promotion.aliyun.com/ntms/yunparter/invite.html?userCode=pxuujn3r
干货系列文章汇总如下,觉得不错点个Star,欢迎 加群 互相学习。
因为篇幅的限制,今天的分享只到这里。如果大家想了解更多的内容的话,可以去扫一扫每篇文章最下面的二维码,然后关注咱们的微信公众号,了解更多的资讯和有价值的内容。
每次整理文章,一般都到2点才睡觉,一周4次左右,挺苦的,还望支持,给点鼓励
The text was updated successfully, but these errors were encountered: