文章目录
01JavaScript发展历程及语言特点
背景知识
ECMAScript是JavaScript的语法标准。
前端JavaScript由ECMAScript和DOM(文档对象模型)、BOM(浏览器对象模型)组成。
JavaScript核心和DOM并不是不可分割的,它们的标准相互独立。
DOM对JavaScript来说是宿主对象,是语言中可更换的部分。
ECMAScript对JavaScript来说是,是核心语言,是不可被替代的功能。
浏览器是客户端JavaScript运行的宿主环境。
JavaScript语言特点
JavaScript是一种直译式脚本语言,是一种弱类型、动态类型语言。
JavaScript只能够在浏览器中执行吗?
–不是的,JavaScript 除了在浏览器中运行,还可以在其他的运行环境中运行,如 node.js 环境。目前 JavaScript 的运行环境有浏览器和 node.js 环境两种。
JavaScript 在浏览器中是如何运行的?
––浏览器下载 JavaScript 脚本文件后,由浏览器 JavaScript 引擎解释执行。
02数据类型以及存储和转换
数据类型分类
基本(原始)类型:Number、String、Boolean、Null、Undefined
引用(对象)类型:Object(Array、Function、Date)等
数据存储
基本数据类型的临时变量分配在栈区。
引用数据类型的变量的引用(地址)存储在栈区,被引用(指向)的对象存储在堆区。
var a = {x:10}
//变量a存在于栈中,{x:10}作为对象存在于堆中
参数传递:ECMAScript中所有函数的参数都是按值来传递的
–基本类型值:把变量里的数据值传递给参数,之后参数和变量互不影响。
–引用类型值:吧对象的引用(地址)值传递给参数,参数和对象都指向同一个对象,相互影响
function foo(num, obj) {
num = 20;
obj.value = "hello world";
}
var num1 = 10;
var obj1 = {
value: "hello"
};
foo(num1, obj1);
console.log(num1); //10
console.log(obj1); //hello world
数据类型转换
分为显示类型转换和隐式类型转换。
undefined和null 、NaN转换为False其他为true
03包装对象和数据类型转换
包装对象
对象是一个单独拥有属性和方法的实体。
包装对象:
–存取字符串、数字或布尔值的属性时临时创建的临时对象称为包装对象
–用来处理属性的引用,一旦属性引用结束,包装对象就会销毁。
包装类型和引用类型的区别:
–主要区别是对象的生存期
–字符值和字符串对象
–数字值和数值对象
–布尔值布尔对象。
临时对象在使用之后会立即释放
var str=”test”;
str.p = 4;//设置临时对象属性
var t = str.p; // 临时对象已释放,再输出t时为undefined
数据类型转换
undefined和null会转换为空对象。
Object转换为Boolean时
任何对象转换为布尔值为true,包括空对象
真值(truthy)与假值(falsy)
在 JavaScript 中,真值指的是在强制转换布尔值时,转换后的值为真的值。所有值都是真值,除非它们被定义为假值(即除 false、0、""、null、undefined 和 NaN 以外皆为真值)。
进行类型比较的时候:
undefined == null,结果是true。且它俩与所有其他值比较的结果都是false。
String == Boolean,需要两个操作数同时转为Number。
String/Boolean == Number,需要String/Boolean转为Number。
Object == Primitive,需要Object转为Primitive。
console.log([] == ![]) //true
– 1. 等号右边有 ! ,优先级比 == 更高,优先计算右边的结果。 [] 为非假值,
所以右边的运算结果为 false,即:![] ==> false
– 2. == 的两边分别是 object 和 boolean 类型的值
把 object 转换成 number 类型,需要对 object 进行 ToNumber 操作,即
Number([].valueOf()) ==> 0
boolean 类型的值时先把这个值转换成 number 类型,右边转换成了 0,
即Number(false) ==> 0
04标识符与表达式
标识符
–标识符是代码中用来标识变量、函数、或属性的字符序列
– 命名规则:
• 由字母、数字、下划线和$符号组成
• 不能以数字开头
• 大小写敏感(区分大小写)
注意:标识符不能和 JavaScript 其它关键字同名
保留字在某种意思上是为将来的关键字而保留的单词。因此保留字不能被用作变量名或函数名。
function add(a,b){} //函数声明
var add = function(a,b){}; //函数表达式
JavaScript 解析器识别函数声明的条件是以 function 关键字开始,只要在 function关键字的前面有任何其他的元素,就会从函数声明转变为函数表达式。
–在 function 前面加!、+、 - 甚至是逗号等到都可以起到识别为函数表达式的效果
–在这些运算符中加括号是最安全的做法,因为它不会改变函数的返回值。
当逻辑运算符两边的操作数都是布尔类型:
&& :真真为真,其余为假
|| : 假假为假,其余为真
当逻辑运算符两边的操作数不是布尔类型:
–首先将左操作数转换成布尔类型
– 对转换后的左操作数进行逻辑判断(true or false)
– 根据短路原则返回原始左操作数或原始右操作数
短路原则:
–对于 &&,转换后的左操作数若为 true,则直接返回原始右操作数,若为 false 则直接返回原始左操作数
– 对于 | |,转换后的左操作数若为 true,则直接返回原始左操作数,若为 false 则直接返回原始右操作数
用途:通过遵循短路特性,使用 || 来设置函数参数的默认值
– 函数定义时可以给参数指定默认值,调用时若未传参数则该参数的值取它定义时的默认值
//定义一个计算圆面积的函数area_of_circle(),它有两个参数:
//r: 表示圆的半径;
//pi: 表示π的值,如果不传,则默认3.14
function area_of_circle(){
return r * r * (pi || 3.14);
}
比较运算符:
JavaScript 有两种比较方式:严格比较运算符和宽松比较运算符。
严格相等运算符(===)
– 仅当两个操作数的类型相同且值相等为 true
宽松相等运算符(==)
– 在进行比较之前,将两个操作数转换成相同的类型
赋值表达式的返回值为右操作数
逗号操作符:– 对它的每个操作数求值(从左到右),并返回最后一个操作数的值
练习:
var a = { n: 1 };
var b = a;
a.x = a = { n: 2 };
console.log(a.x); //undefined
console.log(b); //n: 1 x: {n: 2}
a.x = a = { n: 2 };是连续赋值,同时点运算符优先级高,所以先执行a.x = {n:2},
然后执行a = {n : 2};
switch 语句中的 case
–case在比较时使用的是全等操作符比较 ,因此不会发生隐式类型转换。
–case后可以使一个表达式(如i<60)
05函数定义和调用形式
函数定义方式
三种:
–通过函数声明的形式来定义
–通过函数表达式的形式来定义
–通过Function构造函数实例化的形式来定义
Function构造函数:
– 可以传入任意数量的实参
– 最后一个实参为函数体
– 函数体中 javascript 语句之间分号隔开
– Function 构造函数创建一个匿名函数
new Function( [arg1[, arg2[, …argN]], ], functionBody)
函数定义三要素:函数名、函数的参数、函数的返回值。
匿名函数、具名函数优势、代理函数名
函数的name属性会返回函数实例的名称
使用new Function()语法的函数其名称为"anonymous"
函数的length属性指明函数定义的形参个数
arguments
JavaScript 函数在定义时有固定数目的命名参数,但当调用这个函数时,传递给它的参数数目却可以是任意的。
arguments 对象
– 代表传入函数的实参
– 是函数中的局部变量
– 不能显式创建,只有函数调用时才可用
– 它是一个类数组对象
类数组对象:
– 与数组一样具有 length 与 index 属性
– 本质确实个 Object
arguments与形参的 “双向绑定” 特性
– 在调用时 arguments 对象与实际传递了值的形参变量发生双向绑定
– arguments 对象中的对应单元会和命名参数建立关联
arguments中的length属性:
– 表示函数调用时传入的实参数量
– 在调用时,实参个数确定, arguments.length 确定, 不会再发生改变。
函数对象call/apply/bind方法
toString方法:返回一个当前函数源代码的字符串
valueOf方法:返回函数本身
this关键字
– 在 function 内部被创建
– 指向调用时所在函数所绑定的对象
– this 不能被赋值,this 的值取决于函数被调用的方式
apply/call/bind方法总结:
– apply,call,bind 三个方法第一个参数都是改变函数在调用时 this 指向的对象
– apply,call,bind 第一个参数为空,null,undefined,this 指向的是 window
– apply,call 两个方法只是参数形式有所不同,apply 参数是一个数组,call 参数则是列表序列
– apply,call 都会立即调用函数执行,bind 不会立即调用函数
函数调用形式
– 作为函数直接调用:定时器函数,立即执行函数
– 作为对象方法调用:事件函数
– 作为构造函数调用
– 通过 call/apply 间接调用
06函数定义和调用过程
执行上下文
JavaScript 是一种具有函数优先的轻量级,解释型或即时编译型的编程语言
– 编译型语言写的程序执行之前,需要一个专门的编译过程,把所有程序编译成为机器语言的文件
– 解释型语言在运行程序的时候才解析,每执行一段代码就要翻译一段代码
– 两种方式编译的时机不同
JavaScript 引擎是一段一段地运行代码的
JavaScript 代码执行时,会为当前代码创建相应的运行环境
执行上下文(execution context) 简称EC
– 可以理解为当前代码的运行环境
– 作用是用来保存当前代码运行时所需要的数据
– 在全局环境、函数环境中会创建执行上下文
执行上下文栈(Execution context stack,ECS)
– 执行上下文栈按照函数的调用顺序来管理执行上下文
– 栈底永远是全局上下文,栈顶是当前正在执行的函数
– 特点:先进后出
注意:函数执行时才会创建执行上下文。
执行上下文的生命周期
– 创建阶段 :生成变量对象、建立作用域链、确定this指向
– 执行阶段 :变量赋值、函数引用、执行其他代码
– 等待回收阶段
每个执行上下文都有一个与之关联的变量对象(variable object)和一个作用域链(scope chain)
testEC = {
VO :{}, //变量对象
scopeChain : [], //作用域链
this : {} //this指向
}
return语句作用:
–返回值
–终止函数的执行:销毁当前执行上下文,弹出执行上下文栈。
变量对象
变量对象(Variable Object)
– 变量对象是与执行上下文相关的数据作用域(scope of data) 。
– 变量对象是与上下文关联的特殊对象,用于存储被定义在上下文中的变量(variables) 和函数声明(function declarations)
变量对象创建过程
– 建立 arguments 对象
– 检查当前上下文的函数声明
– 检查当前上下文中的变量声明
JavaScript代码运行机制
分为两个阶段:
–代码解析阶段:将代码翻译成可执行代码 (预解析:声明提升)
–代码执行阶段:执行可执行代码
JavaScript编译和执行过程
–全局解析阶段(全局预解析)
– 全局顺序执行阶段(变量赋值、函数调用等操作)
– 当遇到函数调用时,进行函数范围内的预解析,再执行函数内代码,
– 当存在函数嵌套时,以此类推,会进行多次函数解析与执行
注:解析和执行是一个不断交替的过程。
预解析工作之一:声明提升
– 所有的变量声明和函数声明提升到当前作用域的最前面
声明提升规则:
– 规则1:函数声明整体提前
– 规则2:变量声明提前,赋值留在原地
– 规则3:函数会优先被提升,然后才是变量
– 规则4:函数声明有冲突,会覆盖;变量声明有冲突,会忽略
foo(); // 3
function foo() {
console.log(1);
}
var foo = function() {
console.log(2);
};
function foo() {
console.log(3);
}
foo();
等价于:
/*
function foo() { //被覆盖
console.log(1);
}
*/
function foo() {
console.log(3);
}
// var foo; 被忽略
foo(); //3
foo = function() {
console.log(2);
};
foo(); //2
全局上下文的变量对象:
浏览器中的全局上下文的变量对象:
–变量对象就是window对象
–在页面关闭前一直存在
windowEC.VO = window;
作用域链
作用域
– 作用域是一套关于如何存储变量当中的值,并且能在之后对这个值进行访问和修改的规则
作用域类型
– 全局作用域(global scope)
– 函数作用域(function scope)
– 块作用域(block scope)
全局作用域(global scope)
– 在全局作用域下声明的变量叫做全局变量
– 全局变量在全局(代码的任何位置)下都可以使用
– 全局作用域中无法访问到局部作用域中的变量
全局变量的创建方式
– 在全局作用域下 var 声明的变量
– 在函数内部,没有使用 var关键字声明直接赋值的变量
– 使用 window 全局对象创建的属性和方法
函数作用域(function scope)
– 在函数作用域下声明的变量叫做局部变量
– 局部变量只在当前函数内部中使用
注意:a = 20;这种没有用var的,也是全局的。但要注意假如这句被包含在函数中,函数必须运行,这个全局变量才能被创建。
var a = 10;
b = 20;
function fun(){
var a = 100;
c=200;
function bar (){
var a =500;
d = 600;
}
bar();
console.log(d);
}
fun(); //输出了600
– 局部作用域中可以访问到全局作用域中的变量
局部变量的创建方式
– 在函数内部通过 var 声明的变量
– 函数的形参
块作用域(block scope)
– 任何一对花括号 { } 中的语句集都属于一个块,在这之中定义的所有变量在代码块外都是不可见的,我们称之为块级作用域
– ES5 没有块作用域,在 ES6 中添加了块作用域
作用域模型
分为两种:
–词法作用域(静态性)是由函数定义的书写位置决定的,与调用位置无关
–动态作用域(动态性)是由调用位置决定,不关心变量和函数的定义的书写位置
function foo() {
console.log(a);
}
function bar(){
var a = 3;
foo();
}
var a =2;
bar(); //输出2
/*
步骤:foo的上一层是全局。遵从的是静态性;与书写位置有关
与调用位置无关
*/
词法作用域补充部分
– 通过 new Function 创建的函数对象不遵从静态词法作用域
– 通过 new Function 创建的函数对象总是在全局作用域中执行
[[scope]] 属性
– 虚拟属性,无法访问和修改
– 函数创建定义时生成的属性,保存着这个函数所有父级执行上下文环境中的变量对象的集合
var x = 10;
function foo() {
console.log(x);
}
foo(); // 10
function fun() {
var x = 20;
var foo1 = foo;
foo1(); // 10还是20? 是10
}
fun();
在复制函数的时候, 复制后的函数与复制前的函数引用的是同一个[[scope]] 属性
作用域链:
–由当前执行环境与所有父级执行环境的一系列变量对象组成
–ScopeChain = VO + [[scope]]
– 提供对变量和函数访问的权限和顺序的规则
内存空管理
垃圾回收机制:
– 当一个值失去引用之后就会回收
– 一般的,当一个函数的执行上下文运行完毕之后,内部的所有内容就会失去引用,被垃圾回收机制回收
– 但是,一个函数的执行上下文运行完毕之后,内部的内容仍然被引用着,就不会被回收
函数定义
– [[scope]] 作用域确定
函数执行
– 解析阶段 作用域链确定 创建变量对象(声明提升规则)
(执行上下文创建阶段)
– 执行阶段 修改变量对象
(执行上下文执行阶段)
函数执行结束
– 执行环境不被引用,执行上下文被销毁
– 执行环境被引用,执行上下文不销毁
07IIFE、闭包和this
IIFE
IIFE英文全称:Immediately-Invoked Function Expression即立即执行的函数表达式。
lIFE的作用(建立函数作用域,解决ES5没有块作用域所带来的问题,如:变量污染、变量共享等问题)
IIFE写法:
–使用小括号
–与运算符结合
–JS(ES5)中没有块作用域,js文件内和文件间的同名变量容易互相污染
–我们往往会通过IIFE引入一个新的作用域来限制变量的作用域
闭包
– 函数与对其词法环境的引用共同构成闭包。
– 闭包是由函数以及创建该函数的词法环境组合而成。这个环境包含了这个闭包创建时所需访问的所有局部变量。
闭包的作用
– 可通过闭包访问函数作用域内的局部变量
– 使函数中的变量被保存在内存中不被释放
闭包的缺点
– 由于闭包会使得函数中的变量都被保存在内存中,内存消耗大
– 闭包会在父函数外部,改变父函数内部变量的值
闭包的两种常见形式:
以普通函数形式返回
作为对象的方法返回
this绑定
– this 是被自动定义在所有函数的作用域中一个关键字
– this 是在函数运行期间绑定,与函数定义无关
this 作用
– 复用代码,为函数自动引用合适的上下文对象
– 提供了一个更加优雅而简便的方式来隐式传递一个对象引用
绑定规则:
作为函数直接调用(定时器函数、立即执行函数) => 默认绑定
作为对象方法调用(事件函数) => 隐式绑定
作为构造函数调用 => new绑定
通过call / apply间接调用 => 显示绑定
new 绑定 > 显式绑定 > 隐式绑定 > 默认绑定
严格模式
开启严格模式
– 整个脚本文件开启严格模式,需要在所有语句之前放一个特定语句 “use strict”;
– 某个函数开启严格模式,得把 “use strict”; 声明放在函数体所有语句之前
严格模式对 JavaScript 的语法和行为,都做了一些改变
– 普通模式中,如果一个变量没有声明就赋值,默认是全局变量
严格模式下,禁止这种用法,变量必须先声明再使用
– 普通模式中,在全局作用域函数中的 this 指向 window 对象
严格模式下,全局作用域中函数中的 this 是 undefined
– 普通模式中,构造函数不加 new 也可以调用,this 指向全局对象
严格模式下,构造函数不加 new 调用,this 报错
– 普通模式中,函数允许参数重名
严格模式下,函数参数不允许重名
函数形式
纯函数:
– 相同的输入得到相同的输出
– 不会产生副作用
高阶函数:例如定时器
– 函数的参数为函数
– 函数的返回值为函数
回调函数:
– 一个函数被作为参数传递给另一个函数(在这里我们把另一个函数叫做“otherFunction”),回调函数在 otherFunction 中被调用。
– 回调函数并不会马上被执行。它会在包含它的函数内的某个特定时间点被“回调”(比如定时器)
最近很忙,烂七八糟,没有正常更新。这是为了4.20的小测整理的JavaScript基本内容。冲冲冲!