- JavaScript的特点与特性
JavaScript是一种解释型的脚本语言,C、C++等语言先编译后执行,而JavaScript是在程序的运行过程中逐行进行解释。
在javascript变量中可以存放两种类型的值:原始值和引用值。
原始值存储在栈上的简单字段,也就是值直接存储在变量所标示的位置内。
引用值存储在堆内的对象,栈内变量保存的是指向堆内对象的指针值。
在javascript中有5种基本类型:Undefined,Null,Boolean,Number,String。
引用类型其实就是对象,类似其他语言中类实例的概念。
var b = true; // 存储在栈上
var num = 20; //存储在栈上
var b = new Boolen(true); //存储在堆内
var num = new Number(20); // 存储在堆内
- 堆(heap)与栈(stack)
heap是没有结构的,数据可以任意存放。heap用于复杂数据类型(引用类型)分配空间,例如数组对象、object对象。
stack是有结构的,每个区块按照一定次序存放(后进先出),stack中主要存放一些基本类型的变量和对象的引用,存在栈中的数据大小与生存期必须是确定的。可以明确知道每个区块的大小,因此,stack的寻址速度要快于heap。
程序运行时,每个线程分配一个stack,每个进程分配一个heap,也就是说,stack是线程独占的,heap是线程共用的。此外,stack创建的时候,大小是确定的,数据超过这个大小,就发生stack overflow错误,而heap的大小是不确定的,需要的话可以不断增加。所以这里只看stack的大小限制。
在javascript中,函数就是对象, this引用的是调用当前函数的函数上下文。
可以通过call()和apply()方法来显式的指定函数上下文。Call的第一个参数被用来作为调用函数的上下文,其他参数作为被调用函数的参数传入被调用函数。Apply()和Call()差不多,只不过第二个参数是数组。
- 闭包
要理解闭包,首先必须理解Javascript特殊的变量作用域。
变量的作用域无非就是两种:全局变量和局部变量。
Javascript语言的特殊之处,就在于函数内部可以直接读取全局变量。
函数内部声明变量的时候,一定要使用var命令。如果不用的话,你实际上声明了一个全局变量!
闭包的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。
闭包developer.mozilla.org
由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
- js是单线程
总所周知,JavaScript 是以单线程的方式运行的。
进程和线程都是操作系统的概念。进程是应用程序的执行实例,每一个进程都是由私有的虚拟地址空间、代码、数据和其它系统资源所组成;进程在运行过程中能够申请创建和使用系统资源(如独立的内存区域等),这些资源也会随着进程的终止而被销毁。而线程则是进程内的一个独立执行单元,在不同的线程之间是可以共享进程资源的,所以在多线程的情况下,需要特别注意对临界资源的访问控制。在系统创建进程之后就开始启动执行进程的主线程,而进程的生命周期和这个主线程的生命周期一致,主线程的退出也就意味着进程的终止和销毁。主线程是由系统进程所创建的,同时用户也可以自主创建其它线程,这一系列的线程都会并发地运行于同一个进程中。
function foo() {
console.log( 'first' );
setTimeout( ( function(){ console.log( 'second' ); } ), 5);
}
for (var i = 0; i < 1000000; i++) {
foo();//先输出 first 随后都是 second
}
因为setTimeout 是异步的,浏览器是
事件驱动的(Event driven),浏览器中很多行为是
异步(Asynchronized)的,会创建事件并放入执行队列中。javascript引擎是单线程处理它的任务队列,你可以理解成就是普通函数和回调函数构成的队列。当异步事件发生时,如mouse click, a timer firing, or an XMLHttpRequest completing(鼠标点击事件发生、定时器触发事件发生、XMLHttpRequest完成回调触发等),将他们放入执行队列,等待当前代码执行完成。
执行完循环,才会执行setTimeout的内容
- js栈、任务队列
实例图
(1)所有同步任务都在主线程上执行,形成一个 执行栈(execution context stack)。
(2)主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
(3)一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
(4)主线程不断重复上面的第三步。
Js 中,有两类任务队列:宏任务队列(macro tasks)和微任务队列(micro tasks)。宏任务队列可以有多个,微任务队列只有一个。那么什么任务,会分到哪个队列呢?
宏任务:script(全局任务), setTimeout, setInterval, setImmediate, I/O, UI rendering.
微任务:process.nextTick, Promise, Object.observer, MutationObserver.
- js解释器
1.词法分析器
2.句法解析器
3.字节码生成器
4.字节码解释器
- JavaScript中容易踩的坑
JavaScript 都有什么坑?www.zhihu.com
- var 与 let
如下:
b() // call b
console.log(a) // undefined a不会报错,因为下文有定义,但是又是在定义之前调用
var a = 'Hello world'
function b() {
console.log('call b')
}
b是函数,同样在定义之前使用,但是函数提升是优先于变量提升的
b() // call b second
function b() {
console.log('call b fist')
}
function b() {
console.log('call b second')
}
var b = 'Hello world'
ES6中引入了
let,let
不能在声明前使用,防止出现var的错误
- 作用域问题
function b() {
console.log(value)
}
function a() {
var value = 2
b()
}
var value = 1
a()//输出1
可以考虑下 b 函数中输出什么。你是否会认为 b 函数是在 a 函数中调用的,相应的 b 函数中没有声明
value
那么应该去 a 函数中寻找。其实答案应该是 1。
当在产生执行环境的第一阶段时,会生成
[[Scope]]
属性,这个属性是一个指针,对应的有一个作用域链表,JS 会通过这个链表来寻找变量直到全局环境。这个指针指向的上一个节点就是该函数声明的位置,因为 b 是在全局环境中声明的,所以
value
的声明会在全局环境下寻找。如果 b 是在 a 中声明的,那么 log 出来的值就是 2 了。
function a() {
var value = 2
function b() {
console.log(value)
}
b()
}
var value = 1
a()//输出2
- JavaScript的语法词法特性
JS 共有 6 个原始值,分别为
Boolean, Null, Undefined, Number, String, Symbol
,这些类型都是值不可变的。
有一个易错的点是:虽然
typeof null
是 object 类型,但是 Null 不是对象,这是 JS 语言的一个很久远的 Bug 了。
对于对象来说,直接将一个对象赋值给另外一个对象就是浅拷贝,两个对象指向同一个地址,其中任何一个对象改变,另一个对象也会被改变。记得曾经对两个数组进行排序,由于两个数组拷贝的同一个数据,导致数组的排序相互影响
var a = [1, 2]
var b = a
b.push(3)
console.log(a, b) // -> 都是 [1, 2, 3]
//TODO - 其他
参考:
闭包developer.mozilla.org
学习Javascript闭包(Closure) - 阮一峰的网络日志www.ruanyifeng.com 人类身份验证 - SegmentFaultsegmentfault.com JavaScript 标准参考教程(alpha)javascript.ruanyifeng.com JavaScript运行原理解析 · JS重塑学习 · 看云www.kancloud.cn