一、JavaScript 简介
JavaScript 诞生于 1995年,是一种专为与网页交互而设计的脚本语言。由三种不同部分组成:
- ECMAScript,提供核心语言功能;
- 文档对象模型(DOM),提供访问和操作网页内容的方法和接口;
- 浏览器对象模型(BOM),提供和浏览器交互的方法和接口
1.1 ECMScript
ECMScript 是这本语言的基础,规定了这门语言的下列组成部分:
- 语法
- 类型
- 语句
- 关键字
- 保留字
- 操作符
- 对象
1.2 文档对象模型(DOM)
文档对象模型(DOM, Document Object Model) 是针对XML但经过扩展用于HTML的应用程序编程接口。提供了以下方法和接口的定义:
- DOM 视图;
- DOM 事件;
- DOM 样式;
- DOM 遍历和方法;
- SVG;
- MathML;
- SMIL;
1.3 浏览器对象模型(BOM)
浏览器对象模型(BOM, Browser Object Model) 用来访问和操作浏览器窗口,有以下的扩展:
- 弹出新浏览器窗口的功能;
- 移动、缩放和关闭浏览器窗口的功能;
- 提供浏览器详细信息的 navigator 对象;
- 提供浏览器所加载页面的详细信息的 location 对象;
- 提供用户显示器分辨率详细信息的 screen 对象;
- 对 cookies 的支持;
- 像 XMLHttpRequest 和 IE 的 ActiveXObject 这样自定义对象;
二、在 HTML 中使用 JavaScript
2.1 内嵌脚本
在 HTML 内部直接写入 <script>
片断,这种写法要注意JavaScript 是单线程的,如果在初始化页面的时候,你的代码中存在大量同步运行的代码,导致 JS 线程一直处于繁忙状态,这时候用户在页面上进行交互时将不会得到任何反应,就像是卡死了一样。想了解具体原因,参考文章浏览器渲染原理
2.2 引入外部脚本
<script src="example.js" src="defer"></script>
- defer属性让脚本在文档完全呈现之后再执行
- async属性表示当前文本不用等待其他文本,异步执行。
三、基本概念
3.1 变量
ESCAScript 的变量是 松散类型 的,就是说可以用来保存任何类型的数据。也就是说一个变量就是一个占位符。
var a = 'hi';
a = 10;
定义变量可以使用 var 操作符,定义多个变量,每个变量之间用 逗号 分开,如果 省略 var 操作符可以定义 全局变量,未经初始化的变量,会保存一个特殊的值—— undefined
function show() {
var _message = '局部变量',
_name = 'lily';
g_message = '全局变量';
}
show();
console.log(g_message); // 全局变量
console.log(_message); // _message is not defined
在 ESCScript 中变量会被自动 提升
console.log(enhance); // 变量提升
var enhance = "变量提升";
3.2 数据类型
ECMAScript 有6种数据类型,其中5种基本的数据类型: Undefined、Null、Boolean、Number和String,还有1种复杂的数据类型: Object。一般使用 typeof 检查5种基本的数据类型,使用 instanceof 检查是哪种复杂的数据类型。
-
typeof
常见的就不说了,说几个可能容易混或者错的。值 使用typeof操作符返回 值未初始化 ‘undefined’ 值对象、null ‘Object’ 值是函数 ‘function’ // 未初始化的变量 var uninital; console.log('未初始化的变量:---' +typeof uninital); // undefined // 未定义的变量 console.log('未定义的变量:---' + typeof liy); // undefined // 值为null var obj = null; console.log('值是null的时候:---' + typeof obj); // object // 值未函数 function add (a, b) { return a + b; } console.log('值是funciton:---' + typeof add); // function
-
Boolean 类型
Boolean 类型是 ECMAScript 中使用最多的一种类型,只有两个字面量值: true、false;区分大小写,也就是说 True 不是布尔值。流程控制语句(例如 if 语句)自动执行 转型函数 Boolean()。
var message = "hello world"; if (message) { console.log('转换为 true'); // 执行这句 } else { console.log('转换为 false'); }
下表给出了几种数据类型及其对应的转换规则
数据类型 转换为true 值 转换为false值 Boolean true false String 任何非空字符串 ‘’(空字符串) Number 任何非0数字(包括无穷大) 0、NaN Object 任何对象 null Undefined 不适用 undefined -
Number 类型
- 浮点数值计算会产生舍入误差
- isFinite(值) 用于判断一个数值是不是位于最小值和最大值之间
var result = Number.MAX_VALUE + Number.MAX_VALUE; console.log('数值是否是有穷的:' + isFinite(result)); // false
- isNan(值) 用于判断一个数值是不是非数值。isNaN 也适用于对象。在基于对象调用 isNaN() 函数时,会首先调用对象的 valueof() 方,然后确定返回值是否可以转换为数值。如果不能,则基于这个返回值再调用 toString() 方法,再测试返回值。
console.log(isNaN(10)); // false console.log(isNaN('10')); // false console.log(NaN == NaN); // false
- 将数值转换为数值:Number(值), parseInt(值,几进制),parseFloat(值,几进制)
console.log(Number('hello')); // NaN console.log(Number('')); // 0
-
String 类型
-
转换为字符串有两种方式 toString([几进制]) 和 String(名)
var num = 10; console.log(num.toString()); // '10' console.log(num.toString(2)); // '1010' console.log(String(10)); // '10'
-
-
Object 类型
访问对象的属性有两种方式person['name'] person.name
3.3 操作符
-
布尔操作符
逻辑非和逻辑或都是 短路 操作符-
逻辑与
如果第一个操作数是对象,则返回 第二个 操作数;
如果第二个操作符是对象,则只有第一个操作数的求值结果是 true 的情况下才会返回该 对象;var obj = { name: 'lily', } console.log(obj && true); // true console.log(true && obj); // {name: 'lily'}
-
逻辑与
如果第一个操作数是对象,则返回 第一个 操作数;
如果两个操作数都是对象,则返回 第一个 操作数;
-
-
关系操作符
-
如果两个操作数都是字符串,则比较两个字符串对应的 字符编码,所以字符串比较的时候一般都转换为大写(或者小写);
-
任何操作数与 NaN 进行比较,结果都是 false;
console.log('Brink'.toLowerCase() < 'alpha'.toLowerCase()); // false console.log(NaN < 3); // false
-
3.4 语句
-
for 语句
在 ECMAScript 中没有块级作用域的概念,因此循环内部定义的变量也可以在外部访问到。for (var i = 0;i < 10 ; i++) {} console.log(i); //10
其实以上的代码就相当于
var j; for (j = 0; j < 10; j ++) {} console.log(j);
-
for-in 语句
for-in 语句用来遍历对象的属性,但此时的对象不能是null或者undefined。 -
switch 语句
switch 语句在比较值的时候使用的是全等
看一个我觉得很新颖的语句var num = 18; switch (true) { case num < 0: alert('less than 0'); break; case num > 0 && num < 10:$ alert('between 0 and 10'); breka; default: alert('more than 10'); }
-
函数
可以向 ECMAScript 函数传递任意数量的参数,并且可以通过arguments对象来访问这些参数。即便你定义的函数只接收两个参数,在调用这个函数也未必一定传递两个参数,可以传递一个、三个甚至不传递参数。如果没传递值的命名参数将自动被赋予undefined值。function add() { if (arguments.length == 1) { alert(arguments[0] + 10); } else { alert(arguments[0] + arguments[1]); } } add(10); // 20
四、变量、作用域和内存问题
4.1 变量
ECMAScript 变量包含两种不同类型的值:基本类型和引用类型值。
-
基本类型的值
-
基本类型的值在内存中占据固定大小的空间,因此保存在 栈 内存中
-
如果从一个变量向另一个变量复制基本类型的值,会在变量对象上创建一个新值,然后把值复制到新变量的位置上。
var num1 = 5; var num2 = num1;
-
传递参数的时候是按 值 传递的,和复制变量一样。
-
-
引用类型
-
引用类型的值是对象,保存在 堆 内存中;
-
从一个变量向另一个变量复制引用类型的值时,同样也将存储在变量对象中的值复制一份到新分配的空间中。不同的是,这个值的副本实际上时一个指针。
var obj1 = new Object(); var obj2 = obj1;
但是当为对象添加属性的时候,操作的是实际对象。
obj1.name = "lily"; console.log(obj2.name); // lily
-
传递参数的时候是按 值 传递的,和引用类型复制一样。
-
4.2 执行环境和作用域
- 执行环境
执行环境定义了变量或函数有权访问的其他数据,决定了他们的行为;
执行环境可以分为**全局执行环境** 和 **每个函数自己的执行环境**;
- 作用域链
代码在执行环境中执行,会创建对象的一个 **作用域链**,这个作用域链可以保证对执行环境有权访问的所有变量和函数的有序访问。
4.3 内存管理
优化内存占用的最佳方式,就是为代码只保存必要的数据。一旦数据不再用了,最好通过将其值设置为 null 来释放引用。
function cretePerson(name) {
var localPerson = new Object();
localPerson.name = name;
return localPerson;
}
var g_person = cretePerson('lily');
g_person = null;
五、引用类型
在 ECMAScript中,引用类型是一种数据结构,常用的有:Object、Date、RegExp、Function、基本包装类型、单体内置对象。
5.1 Array 类型
-
插入
- 在末尾插入数据:
arr[length] = 'add data from bottom';
- 在末尾插入数据:
arr.push('add data from bottom');
- 在头部插入数据:
arr.unshift('add data from head');
- 在任意位置插入数据,返回删除数据组成的数组:
splice(2,0,'red','green');
,三个参数,第一个是位置,第二个是要删除的数据个数,第三个数据为插入的数据;
- 在末尾插入数据:
-
删除
- 在末尾删除数据:
arr[length] = arr.length - 1;
- 在末尾删除数据,返回删除的数据:
arr.pop();
- 在头部删除数据,返回删除的数据:
arr.shift();
- 任意位置删除,返回删除数据的数组:
arr.splice(0,1);
,两个参数,第一个参数是位置,第二个参数是要删除的个数;
- 在末尾删除数据:
-
替换
- 替换任意位置数据,返回被替换的数据组成数组:
arr.splice(2,1,'red')
- 替换任意位置数据,返回被替换的数据组成数组:
-
检测数组
检测是否是数组if(Array.isArray(value)){}
-
转换为字符串的方法
- join
- toString
- tolocalString
- valueOf
-
排序方法
- reverse
- sort(可以传入排序规则)
-
合并
- concat
var color1 = ['color1', 'color2']; var color2 = color1.concat('yellow'); // color1,color2,yellow
-
位置方法
- indexof(‘要查找内容’,起始位置),不包含返回 -1
- lastIndexof
-
迭代
- every(): 对数组的每一项运行给定函数,每一项都 true 的时候,返回 true;
- some(): 对数组中的每一项运行给定函数,有任意一项返回 true 时候,才会返回 true;
- filter(): 对数组中的每一项都运行给定函数,返回该函数返回 true 项组成的数组
- forEach(): 对数组中的每一项运行给定的函数,无返回值
- map(): 对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组。
10.归并数组
- reduce
- reduceRight
5.1 Function 类型
函数分为函数声明和函数表达式,函数表达式可以随时访问,因为函数声明提升。
- 函数内部属性
-
arguments 对象
arguments 对象主要用途是保存函数参数,包含一个特殊属性callee属性,是个指针,指向拥有这个arguments对象的函数;function factorial (num) { if (num < 1) { return 1; } else { return num * arguments.callee(num - 1); } } console.log(factorial(4)); //24
-
this 对象
this 引用的是函数执行环境对象
window.color = 'red'; var obj = {color: 'obj bule'}; function showColor () { console.log(this.color); } showColor(); // red obj.sayColor = showColor; obj.sayColor(); // obj bule
-
- 非继承的函数方法
每个函数都包含两个方法:call() 和 apply()。这两个方法的用途都是在特定的作用域中调用函数,实际上就是设置函数体内this的值。-
apply(运行函数的作用域, 参数数组)
window.color = 'window color red'; var obj = {color: 'obj color blue'}; function showColor(data) { console.log(this.color); //window color red console.log(`传入数据${data}`); } showColor(); showColor.apply(obj, ['我用了apply']); // obj color blue 传入数据我用了apply
-
call(运行函数的作用域,其余参数)
window.color = 'window color red'; var obj = {color: 'obj color blue'}; function showColor(data) { console.log(this.color); //window color red console.log(`传入数据${data}`); } showColor.call(obj, '我用了call'); //obj color blue //传入数据我用了call
-
5.1 String 类型
1. length
2. charAt(位置)
3. charCodeAt(位置):返回字符编码
4. substring(start, [end])
5. substr(start, [end])
6. slice(start, [end])
7. indexof(要查找内容,位置)
8. trim: 去除空格
9. toUpperCase
10. toLocaleUpperCase
11. toLowerCase
12. toLocalLowerCase
13. split(切割符),返回数组
toString 和 toLocaleString() 的区别
- 数字是4位以上的时候
var num = 1234
num.toString(); // 1234
num.toLocalString(); // 1,234
- 时间格式
var date = new Date();
date.toString(); //Tue Jul 09 2019 21:28:06 GMT+0800 (中国标准时间)
date.toLoacalString(); //"2019/7/9 下午9:28:06"
总结:该字符串与执行环境的地区对应
ps: 欢迎大家添加讨论
一元运算符
- 前置++
- 前置–;
- 后置++;
- 后置–;
注意:前置操作符,变量的值会在求值语句之前就改变(这种情况被称为副作用)
var age = 27;
var afterAge = ++age + 1; // age 会执行为++,变为 28 再加1,结果为29
console.log(age, afterAge); // 27, 29
var age = 27;
var afterAge = age++ + 1; // 会先用age 进行 +1 操作,然后再进行++操作
console.log(age, afterAge); // 28, 28
位操作符-按位非
- 标识符:
~
- 本质:按位非的本质是- 操作数的负值 - 1;
var num = 25;
var nonNum = ~num; // -26
布尔操作符
-
逻辑非
- NaN, null, undefined 返回的都是
false
; - !! - 实际上会模拟
Boolean()
,其中第一个非,无论什么操作数返回一个布尔值,而第二个逻辑非操作会对该布尔值求反。
- NaN, null, undefined 返回的都是
-
逻辑与
- 短路操作:如果第一个值能知道结果就不看第二个值了;
- 应用于任何类型,返回值也不一定是布尔值;
- 如果第一个值是 null, undefined, NaN , 则直接返回这几个值;
{ name: 'lily'} && { age: 27 } // {age: 27}
-
逻辑或
-
短路操作:如果第一个的求值结果为
true
, 就不会计算对第二个操作数求值;
null, undefined 用Number() 转换
- Numer(null) // 0
- Number(undefined) // NaN
关系操作符 - 两个操作数都是字符串,则比较两个字符串字符编码值
'a' < 'B' // false
'a'.toLocaleLowerCase() < 'B'.toLocaleLowerCase() // true
- 操作数中一个是布尔值,会将其转换为数值,再执行比较
false < true // true
- 任何数与NaN比较,结果都是 false
for-in 语句
- 常用来枚举对象的属性
- 循环出的属性的顺序是不确定的【?这个用谷歌的浏览器验证了几次感觉没有问题啊】
- null, undefined 会有兼容性【?用谷歌浏览器验证了几次感觉没有问题】
break & continue 语句 - break 语句,立即跳出循环;
- continue 语句,跳出本次循环,进入下一次循环;
var num = 0;
for(let i = 1; i < 10; i++) {
if (i % 5 === 0) {
break;
}
num++;
console.log(i);
}
// 1 2 3 4
let num2 = 0;
for(let i = 1; i < 10; i ++) {
if (i % 5 === 0) {
continue;
}
num2++;
console.log(i);
}// 1 2 3 4 6 7 8 9
- 配合
label
语句,比如跳出双循环
let num3 = 0;
outermost:
for(let i = 0; i < 10; i++) {
for(let j = 0; j < 10; j++) {
if(i === 5 && j == 5) {
break outermost;
}
num3++;
}
}
// num3 55
switch 语句
break
关键字会导致代码执行流跳出switch 语句,如果省略了break关键字,就会导致执行完当前的case,继续执行下一个case, 通常这种情况需要添加注释;
switch(num) {
case 1:
case 2:
/**合并两种注释**/
console.log('条件1 和条件2');
break;
default:
break;
}
- switch(任何数据类型,字符串,对象),case[常量,变量,表达式]
- switch 比较的时候使用的是全等
函数参数
- 定义一个接收两个参数的函数
function test(param1, param2)
,调用的时候可以传入0个或者多个参数,参数在内部用一个数组来表示,函数内部接收的是这个数组,可以用arguments对象来访问这个参数数组 - 建议只读,不通过 arguments 改
function sum(num1, num2) {
if(arguments.length === 1) {
return 10 + num1;
} else if (arguments.length === 2) {
return num1 + num2;
} else {
return '参数有点多';
}
}
sum() // "参数有点多"
sum(1) // 11
sum(2, 3) // 5
检测类型
- typeof 检验基本类型,null 除外,
typeof null
是Object
,typeof 函数
返回function
- instanceof 检验引用类型
基本类型和引用类型的值
-
ECMAScript 包含两种不同数据类型的值: 基本类型值和引用类型值
-
“基本数据类型是按值访问的,因为可以操作保存在变量中实际的值”;“引用类型的值是保存在内存中的对象。与其他语言不同,javascript 不允许直接访问内存中的位置,也就是不能直接操作对象的内存空间。在操作对象时,实际上是在操作对象的引用而不是实际的对象。为此,引用类型的值是按引用访问的。”
以下是对于上面这句话理解的剖析: -
值类型和引用类型在内存上的存储区域:
-
程序员开发涉及到的内存区域: 栈,堆,静态存储区域;
-
堆和栈的概念:
- stack (栈)是有结构的(就像后进先出 从下到上) 每一个区域都按照一定次序存放,可以明确知道每个区块的大小
- heap(堆)是没有结构 数据可以任意存放。因此stack的寻址速度要快于heap。
- 所以 数据存放的规则是 只要是局部的 占用空间确定的数据 一般都存放在stack里面。否则就放在heap里面。局部变量一旦运行结束 就会GC回收 而heap的那个对象实例直到系统的GC将这里的内存回收。因此一般内存泄漏都方生在heap。
-
值和引用类型的区别:
-
值类型的值是存储在内存的栈当中;引用类型的值是存储在内存的堆中;
-
传递值类型和传递引用类型的时候,传递方式不一样。值类型我们称之为值传递,引用类型我们称之为引用传递。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gEMof9Gd-1592273613965)(https://user-images.githubusercontent.com/16410254/61796800-c0696b00-ae58-11e9-82e5-ae69385a72fc.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U35k45es-1592273613984)(https://user-images.githubusercontent.com/16410254/61796880-e0009380-ae58-11e9-9caa-417db777fb05.png)]
-
-
-
javascript中的变量
- 基本类型的值在内存中占据固定大小的空间,因此保存在栈内存中;
- 引用类型的值是对象,保存在堆中;
-
怎么理解 javascript不允许直接访问内存中的位置,不能直接操作对象的内存空间?
js语言本身定义确实不允许,其次,考虑js运行环境,是用在浏览器上,浏览器一般情况也不能访问内存,不然,你想像一下,浏览器可以访问读写电脑上的内存,那将是多么恐怖的事儿(随便写一个小程序就可以控制你电脑,拿到你电脑上所有的东西了。)。作为一门高级语言,JS并不像低级语言C/C++那样拥有对内存的完全掌控。JS中内存的分配和回收都是自动完成,内存在不使用的时候会被垃圾回收器自动回收。
值传递和引用传递
- 一般把函数中参数叫做“形式参数”,而把调用这个函数时传入的实际的参数叫做“实际参数”,那么调用函数时,实参传递给形参有两种方式:值传递 和 引用传递
- 值传递:形参只是得到实参的值,是两个不同的对象,互不影响;
- 引用传递:形参是实参的引用,也就是形参和实参是同一个对象,在函数中的对象修改会影响实参;
- ECMAScript 中所有函数的参数都是按值传递的,且形参是局部变量,会在函数执行完立即被销毁
- 引用类型
function setName(obj) { obj.name = 'lily'; obj = new Object(); obj.name = 'lucy'; } let person = new Object(); setName(person); person.name // lily
如果引用类型是按引用传递的话,那么obj
和 person
是同一个对象,当obj = new Object()
的时候person
指向堆中的对象也会变,输出的person.name 会是 lucy
执行环境和栈
原文网址:https://juejin.im/entry/5833f18fe696c9004d6da42e
这篇文章我将会深入地讨论JavaScript中最根本的一部分——Execution Context(执行上下文)。在文章结束的时候,你应该对解释器的工作原理有一个比较清晰的理解,对于为什么会存在‘变量提升’,它们的值又是如何被真正确定的这些问题有一个正确的答案。
什么是Executin Context(执行上下文)
当JavaScript代码执行的时候,执行环境是很重要的,它可能是下面三种情况中的一种:
全局 code(Global code)——代码第一次执行的默认环境
函数 code(Function code)——执行流进入函数体
Eval code(Eval code)——代码在eval函数内部执行
在网上你能够读到许多关于作用域的资料,这篇文章的目的是让事情变得简单些。让我们来思考下execution context这个词,它与当前代码的环境 / 作用域是等价的。好了,说的够多了,让我们来看一个包含global和function / local context的例子吧。
image 这里没有什么特别的地方,我们有一个global context被紫色的框框着,还有三个不同的function context,分别被绿色、蓝色、橘色的框框着。在你的程序中,有且仅能有一个global context,并且它能够被任何其他的context访问到。你能够拥有任意多个function context,并且每个函数被调用的时候都会生成一个新的context。它会生成一个私有作用域,并且在它内部声明的任何东西都不能直接在它的外部访问。就像上面的例子,一个函数可以直接访问它外面的变量,但是外部的context就不能直接访问在内部声明的变量或者函数。为什么会这样?如何准确的理解这段代码的执行?
以上是文章摘要 阅读更多请点击——>右下角的more 以下是余下全文
Execution Context Stack(执行上下文栈)
浏览器中的JavaScript解释器是单线程的。意思就是说在浏览器中,同一时间只能做一件事,其它的行为和事件都会在Execution Stack中排队。下面这个图表就是一个单线程栈的抽象描述:
image
我们已经知道,当浏览器第一次加载你的script的时候,默认进入global execution context。如果,你在全局代码中调用了函数,程序序列流就会进入被调用的函数中,生成一个新的execution context并且把它压入execution stack的顶部。
如果你在当前函数内调用其他函数,会发生同样的事情。代码的执行流会进入内部函数,生成一个新的execution context,并且将它压入existing stack。浏览器总是会执行stack顶部的executin context,当被执行的函数上下文执行完成后,它将会弹出栈顶,然后将控制权返回给当前栈中的下一个对象。下面是一个循环函数执行栈的例子:
(function foo(i) {
if (i === 3) {
return;
}
else {
foo(++i);
}
}(0));
image
函数foo递归调用了3次,每次 i 增长1。函数 foo 每次调用后,都出生成一个新的execution context。当一个context执行完成后,它就会出栈并且把控制权返回给下面的context,直到再次到达global context。
关于execution stack有5个关键点需要记住:
单线程
同步执行
1个Global context
无限制的函数context
每个函数调用都会创建新的execution context,即使是自己调用自己。现在我们知道了函数每次被调用的时候,一个新的execution context就会被创建。无论如何,在JavaScript解释器内部,每次调用执行execution context都有两个阶段:
创建阶段【在函数被调用的时候,但是内部代码执行之前】
创建Scope Chain
创建变量、函数和参数
确定 “this” 的值
激活 / 代码执行阶段
变量赋值、引用函数和解释 / 执行代码。
每个execution context在概念上可以用一个对象来表示,这个对象有三个属性:
executionContextObj = {
‘scopeChain’: { /* variableObject + all parent execution context’s variableObject / },
‘variableObject’: { / function arguments / parameters, inner variable and function declarations */ },
‘this’: {}
}
执行对象 / 变量对象【AO/VO】
这个executionContextObj在函数被调用的时候创建,但是是在真实函数代码被执行之前。这个就可以理解为第一阶段,创建阶段(Creation Stage)。在这里,解释器通过搜索函数的形参和传入的实参、本地函数的声明和本地变量的声明来创建executionContextObj。搜索的结果就是executionContextObj对象中的variableOject属性。
这里是解释器执行代码的一个伪综述:
找到调用函数的代码。
在执行函数代码之前,创建execution context。
进入创建阶段:
初始化Scope Chain
创建variable object
创建实参对象(arguments object),检查context的形参(parameters),初始化参数的名称和参数值并且创建一份引用的拷贝。
扫描context中的函数声明:
为每一个函数在varible object上创建一个属性,属性名就是函数名,含有一个指向内存中函数的引用指针。
如果函数名已经存在了,这个引用指针的值将会被重写。
扫描context中的变量申明:
为每一个变量在variable object上创建一个属性, 属性名就是变量名并且将变量的值初始化为undefined。
如果变量名在variable object中已经存在,那就什么都不会发生,并且继续扫描。
激活 / 代码执行阶段:
运行 / 解释context中的函数代码,并且根据代码一行一行的执行,为变量赋值。
让我们来看一个例子:
function foo(i) {
var a = ‘hello’;
var b = function privateB() {
};
function c() {
}
}
foo(22);
当调用foo(22)时,创建阶段(creation stage)时,context是下面这个样子:
fooExecutionContext = {
scopeChain: { … },
variableObject: {
arguments: {
0: 22,
length: 1
},
i: 22,
c: pointer to function c()
a: undefined,
b: undefined
},
this: { … }
}
因此,你可以看到,在创建阶段(creation stage)只负责对属性名称(变量名)的定义,但是并没有给它们赋值,当然这里有一个例外就是formal arguments / parameters(实参 / 形参)。当创建阶段完成以后,执行流进入函数内部,激活执行阶段(execution stage),然后代码完成执行,context是下面这个样子:
fooExecutionContext = {
scopeChain: { … },
variableObject: {
arguments: {
0: 22,
length: 1
},
i: 22,
c: pointer to function c()
a: ‘hello’,
b: pointer to function privateB()
},
this: { … }
}
关于Hoisting(变量提升)
在网上你可以找到很多定义JavaScript中hoisting这个词的文献,解释变量和函数的声明在它们的作用域中被提前。但是,没有从细节上解释为什么会发什么这种现象。通过了解解释器如何创建activation object,就很容易知道这种现象发生的原因了。看下面这个例子:
(function() {
console.log(typeof foo); // function pointer
console.log(typeof bar); // undefined
var foo = ‘hello’,
bar = function() {
return ‘world’;
};
function foo() {
return ‘hello’;
}
}());
现在我们可以回答下面这些问题了:
在foo声明之前,为什么我们可以访问它?
如果我们来跟踪creation stage, 我们知道在代码执行阶段之前,变量已经被创建了。因此在函数流开始执行之前,foo已经在activation object中被定义了。
foo 被声明了两次,为什么 foo 最后显示出来是一个function,并不是undefined或者是string?
尽管 foo 被声明了两次,我们知道,在创建阶段,函数的创建是在变量之前的,并且如果属性名在activation object中已经存在的话,我们是会简单的跳过这个声明的。
因此,对 function foo()的引用在activation object上先被创建了,当解释器到达 var foo 时,我们会发现属性名 foo 已经存在了,因此代码什么都不会做,继续向下执行。
为什么 bar 是undefined?
bar实际上是一个变量,并且被赋值了一个函数的引用。我们知道变量是在创建阶段被创建的,但是它们会被初始化为undefined,所以bar是undefined。希望现在你对JavaScript解释器如何执行你的代码能有一个好的理解了。理解execution context and stack会让你知道为什么你的代码有时候会输出和你最初期望不一样的值。
作用域链:
推荐两篇文章:
http://blog.xieliqun.com/2016/10/06/scope-chain-2/
http://blog.xieliqun.com/2016/10/06/scope-chain/
附一张自己整理的图片
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tkNg7qGL-1592273641906)(https://user-images.githubusercontent.com/16410254/64535352-b4befe80-d349-11e9-8e4c-e75e6550422f.jpg)]
- 闭包引用包含函数的整个活动对象
function test() {
var ele = document.getElementById('#test');
var id = ele;
ele.onclick = function() {
console.log(id);
}
ele = null;
}
-
模仿块级作用域
- 使用匿名函数模仿作用域
-
特权方法
-
定义:有权访问私有变量和私有函数的公有方法;
-
可以使用构造函数模式、原型模式、模块模式、增强的模块模式来实现
-构造函数模式
function Test() {
var name = 'lily';
// 特权方法
this.setName = function(val) {
name = val;
}
}
- 原型模式
(function() {
var name = 'lily';
Person = function() {};
// 特权方法
Person.prototype.setName = function(val) {
name = val;
}
})();
- 模块模式
- 概念:为单例(只有一个实例对象)创建私有变量和特权方法;
- 使用场景:需要一个对象,这个对象可以用某些数据对它进行初始化,同时还要公开一些可以访问呢这些私有数据的方法。
- 代码:
var singleton = function() { var name = 'lily'; function privateSay() { console.log('hello'); } // 返回对象 return { publicPropterty: true, publicMethod: function() { name ='luce'; return privateSay(); } } }();
- 增强的模块模式
- 使用:单例必须是某种类型的实例,同时必须添加某些属性或方法
- 代码
var singleton = function(){
// 私有变量
var components = new Array();
// 初始化
components.push(new BaseCom());
// 创建指定类型的是咧
var app = new BaseCom();
// 公共的接口和方法
app.getComponentCount = function () { return components.length; }
return app;
}();
javascript 内存管理
- 具备垃圾收集机制的语言,开发人员一般不用担心内存管理的问题。但是,javaScript一个主要问题是分配给Web浏览器的可用内存数量通常比分配给桌面应用程序的少,这样做的目的出于安全考虑,防止运行javascript的网页耗尽全部系统内存而导致系统崩溃。内存限制问题不仅会影响给变量分配内存,同时还会影响调用栈以及在一个线程中能够同时执行的语句数量, 因此,确保占用最少的内存可以让页面获得更好的性能。其中一个方法是解除引用,一般应用于全局变量和全局变量对象的属性,还有循环引用变量的引用。
function createPerson(name) {
var localPerson = new Ojbect();
localPerson.name = name;
return localPerson;
}
var glPerson = createPerson('lily');
glPerson = null // 手工解除引用
Array 类型
-
length
属性不是只读的,因为可以在数组的末尾移除元素或者添加元素- 移除
var colors = ['red', 'blue']; colors.length = 1; console.log(colors); // ['red']
- 添加
var colors = ['red', 'blue']; colors[colors.length] = 'green'; console.log(colors); // ['red', 'blue', 'green'];
- 移除
-
检测数据类型
Array.isArray(val)
判断value
是不是数组;
-
转换方法
toString()
: 以,
形式拼接每个值的字符串arr.valueof()
: 返回的是数组本身;arr.join(指定分隔符)
: 用指定分隔符拼接每个值的字符串;
注意:如果某一个项的值是null或undefined, 则 join(),valueof(), toString(), toLocaleString() 方法返回的结果中以空串表示
-
模拟栈
- 栈: last-in-first-out (后进先出)
- 方法:pop() : 弹出数组的最后一个元素,并返回该元素
- 方法:push() : 在数组的末尾添加一个元素,返回添加后的数组的length
- 模拟一个栈
let arr = [1]; arr.push(2); arr.pop();
- 栈: last-in-first-out (后进先出)
-
模拟队列
- 队列:first-in-first-out (先进先出)
- shift(): 移除数组第一项,并返回该项;
- unshift(): 在数组的前端添加任意项,并返回该数组的长度;
- 模拟一个队列:
let arr = [1, 2, 3]; arr.push(4); arr.shift(); // 1
- 队列:first-in-first-out (先进先出)
-
重排序方法
- reverse() : 反转数组项的顺序
var arr = [1, 2, 3]; arr.reverse(); // [3, 2, 1]
- sort(): 调用每个数组的toString() 方法,然后比较得到的字符串;
- sort() 可以接收一个比较函数作为参数,用来确定哪个值在哪个值的前面:
function compare(value1, value2) { if(value1 < vlaue2) { return -1; } else if (value1 > value2) { return 1; } else { return 0; } } var values = [0, 5, 1, 10, 15]; values.sort(compare); console.log(values); // [0, 1, 5, 10, 15]
- reverse() : 反转数组项的顺序
-
操作方法
concat()
: 基于当前的数组,返回一个新的数组,不改变原来的数组,接收到的参数追加到原来数组的末尾;
var arr1 = [1, 2]; var arr2 = arr1.concat([5]); // [1, 2, 5]
slice(start, end)
: 返回新数组,不影响原来的数组,截取 start~end-1 的元素,如果第二个参数没有的话,默认为到末尾;var sliceArr = [1, 2, 3] var transliceArr = sliceArr.slice(1, 2) // [2]
-```splice(index, delNum, item)``: 会改变原来的数组,f返回删除的元素,第一个参数为位置,第二个参数为删除几项,第三个参数为添加的数据。
- 删除:
var colors = ["red", "green", "black"]; var removed = colors.splice(0, 1); console.log(removed); // ['red'];
- 插入
removed = colors.splice(1, 0, 'white'); // ["green", "white", "black"] console.log(removed); // []
- 替换
colors.splice(1, 1, 'red', 'yellow'); // ["green", "red", "yellow", "black"]
-
位置方法:
- indexof(item, start): item: 要查找的项, start: 开始查找的位置;有:则返回查找元素的index,没没有返回-1;
- lastIndexof(item, start): 从后向前找,但是start也是从前向后定义的位置
-
迭代方法:
- every(item, index, array): 对每一项运行给定函数,如果该函数每一项都返回true, 则返回true
- some(item, index, array): 对每一项运行给定函数,如果有一项返回true,则返回true;
- filter(item, index, array): 对每一项运行给定函数,返回true的项组成数组;
- forEach(item, index, array): 对每一项运行给定函数,没有返回值;
- map(item, index, array): 对每一项运行给定函数,返回调用结果组成的数组;
var num = [1, 2, 3, 4, 5]; var everyRes = num.every((item) => return item >2); // false var someRes = num.some(item => return item > 2); //true; var filterRes = num.filter(item => return item > 2); // [3, 4, 5]; var mapRes = num.map(item => return item * 2); // [2, 4, 6, 8, 10]
-
归并方法
reduce(prev, cur, index, array)
和reduceRight(prev, cur, index, array)
这个函数返回任何值,都会作为第一个参数传入下一项。
var num = [1, 2, 3]; var sum = num.reduce((prev, cur, index ,arr) => return prev + cur ); // 6
-
Date 类型
- Date.now() : 返回调用这个方法时的日期和时间的毫秒数;
- 使用 “+” 操作符,可以获取 Date 对象的时间戳;
var start = +new Date(); // do some thing var end = +new Date(); var result = end - start;
- Date 类型的
valueOf()
方法,返回日期的毫秒数,所以可以使用**比较操作符来比较日期值。
RegExp 类型
-
字面量创建正则表达式
var expression = /pattern/ flags;
-
flags 包含3种形式
- g : 表示全,表示应用于所有字符串
- i : 表示忽略大小写
- m: 表示多行,就是到达文本末尾的时候,还会继续查找下一行中是否存在与模式匹配的项。
-元字符不要转义
( ) { } [ ] \ ^ $ | ? * + .
- 实例方法
- test()
pattern.test(text)
目标字符串是否和模式匹配,返回Boolean;
- test()
Function 类型
-
函数实际上是对象,每个函数是 Function 类型的实例;函数名实际上是指向某个函数的指针,不会与某个函数绑定;
-
定义:
- 函数声明语法定义:
function sum(num1, num2) { return num1 + num2 }
; - 函数表达式定义:
var sum = function(num1, num2) { return num1 + num2 };
;
- 函数声明语法定义:
-
函数声明和函数表达式
- 函数声明:在执行代码之前,解析器会通过一个函数声明提升,将函数声明添加到执行环境中
- 区别
- 函数表达式可以直接调用
var test = function () {} test()
; 函数声明不能直接调用function test() {} ()
;
- 函数表达式可以直接调用
// 函数声明 sum(4,5); // 9 function sum(a, b) { console.log(a + b)} // 函数表达式 add(4, 5); var add = function(a, b) { console.log(a + b) }
// 递归 var factorial = (function f()num { if( num < 1) { return 1} else { return num * f(num -1)} })
-
函数声明提升:
JS引擎会在正式执行代码之前进行一次”预编译“,预编译的函数,查找函数声明,作为GO属性,值赋予函数体(函数声明优先);找变量声明,作为GO属性,值赋予undefined;这样就是函数声明和函数表达是式的不同- 参考链接: https://juejin.im/post/5afcf1b96fb9a07abd0ddc43
-
函数的内部属性
- callee : 指向拥有
arguments
对象的函数;
function sum(a, b) { console.log(arguments.callee); // sum 这个函数 }
- caller: 调用当前函数的引用;
function outer() { inner() } function inner() { console.log(arguments.callee.caller) } // outer 这个函数
- this:
- this中值是函数的执行环境;
- this 对象是在运行时基于函数的执行环境绑定的;
function thisDemo() { console.log(this); } thisDemo(); // this 是window var obj = {}; obj.demo = thisDemo; obj.demo(); // this 就是obj
- callee : 指向拥有
-
函数属性
- length: 函数接收的命名参数的个数
function sum(a, b) { return a + b; } sum.length // 2
- prototype: ----以后会做扩展
- length: 函数接收的命名参数的个数
-
函数的方法
- toString(), valueOf() : 返回函数代码, 可用于调试过程中
- call(scope, [num1, num2]):
- 用途是在特定的作用域中调用函数;
- 两个参数,第一个是运行函数的作用域, 第二个参数是参数数组或arguments
var color = 'red'; function sayColor() { console.log(this.color) } var banana = { color: 'yellow' } sayColor(); // red sayColor(banana); // yellow;
- apply(scope, arg1, arg2);
- 用途:在特定的作用域中调用函数;
- 多个参数:第一个是运行函数的作用域,其余的参数是传入的参数值
- bind(scope):
- 用途: 返回一个函数实例, 这个函数绑定了特定的this的值
var color = 'red'; var banana = { color: 'yellow' } function sayColor () { console.log(this.color) } var bindSayColor = sayColor.bind(banana); bindSayColor(); // 'yellow', 虽然在全局执行,但是this是banana;
基本包装类型 - String
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dZsDG3qb-1592273710370)(https://user-images.githubusercontent.com/16410254/63694834-e87a2e80-c849-11e9-91c1-df0dad90df75.png)]
单体内置对象 - Global对象
-
URI 编码方法
encodeURI(uri)
对整个uri 进行编码,对本身属于URI的特殊字符不进行编码,例如/
;encodeURIComponent(uri)
对uri中的一段进行编码,对所有非标准字符不进行编码
例如:
var uri = 'http://www.baidu.com/lily index.html#start' encodeURI(uri); // "http://www.baidu.com/lily%20index.html#start" encodeURIComponent(uri); // "http%3A%2F%2Fwww.baidu.com%2Flily%20index.html%23start"
decodeURI(uri)
decodeURIComponent(uri)
-
在任何环境的情况下返回全局对象的方法
var global = function() { return this }();
单体内置对象- Math对象
- min 和 max 取出一组数据中的最小值/最大值
// 取数组中的最大值
var values = [1, 2, 3, 5, 8];
var max = Math.max.apply(Math, values);
-
舍入方法
- Math.ceil(num): 向上舍入,例如
25.1
向上舍入是26
- Math.floor(num): 向下舍入
- Math.round(num): 四舍五入
- Math.ceil(num): 向上舍入,例如
-
random():生成一个(0<= x < 1)之间的随机数
- 某个范围随机选择一个值的公式
// 值 =Math.floor(Math.random() * 总数 + 第一个值) function selectFrom(lowerVal, upperVal) { var choices = upperVal - lowerVal + 1; return Math.floor(Math.random() * choices + lowerVal); } // 例如2~9之间的随机数 var num = Math.floor(Math.random() * 8 + 2 );