JavaScript
一门用来与网页交互的脚本语言
JavaScript的实现
JavaScript包含以下实现部分:
核心ECMAScript,可以理解为对这一规范的实现。
文档对象模型DOM,对HTML文本操作的抽象,呈现树结构。提供与网页进行交互的方法和接口。
浏览器对象模型BOM,对浏览器窗口操作的抽象。提供与浏览器进行交互的方法和接口。
JavaScript的使用
使用<script>元素。
标签中常用的属性:
async 表示应该立即开始下载脚本
defer 脚本会被延迟到整个页面都解析完毕后再运行
charset 使用 src 属性指定的代码字符集
src 表示包含要执行的代码的外部文件
type 表示代码块中脚本语言的内容类型
使用方式一:<script>元素内使用
<script>
const fun = function(a){
console.log("Hello World");
}
</srcipt>
使用方式二:通过script元素引入外部js文件
<script src="example.js"></srcipt>
<script>元素的位置
不管包含的是什么代码,浏览器都会按照<script>在页面中出现的顺序加载它们。也因此,现代Web应用程序通常将所有JavaScript引用放在body元素中的页面内容最后面,减少页面的空白时间,让用户感觉加载更快。
JavaScript的语言基础
语法
①区分大小写
②语句分号结尾也可以不分号结尾,选择你喜欢的风格,遵守它
③ 标识符使用驼峰大小写形式
④单行注释 // 多行注释 /* */
⑤严格模式:脚本开头加上 use strict
变量
变量的声明:var、let、const
var(×)
var声明作用域:
function中通过var声明的是局部变量,如果不带var,则声明的是全局变量,但是不建议这么做,不易维护。
var声明提升:
var 关键字声明的变量会自动提升到函数作用域顶部 。 所谓的“提升”(hoist),也就是把所有变量声明都拉到函数作用域的顶部 (注意,这是只是声明的提升,初始化没有提升)。
function foo() {
console.log(age);
var age = 26;
}
foo(); // undefined
==
function foo() {
var age;
console.log(age);
age = 26;
}
foo(); // undefined
var重复声明:
function foo() {
var age = 16;
var age = 26;
var age = 36;
console.log(age);
}
foo(); // 36
let(√)
声明作用域:
作用域是块作用域 ,而var是函数作用域
重复声明:
不允许重复声明
声明提升:
let的声明不会被提升,在let声明之前执行的瞬间被称为暂时性死区。
全局声明:
在全局作用域里使用let声明的变量,不会成为windows对象的属性;相应的var则会
let对循环的优化:
在没出现let之前,在循环中使用var定义的迭代变量,会渗透到循环体的外部,而let的出现则解决了这个问题。
const(√)
行为基本和let相同,但是它必须声明和初始化一起,并且不支持修改,简而言之,就是用来声明常量。
声明风格及最佳实践
①不使用var,let和const带来了明确的作用域、声明位置和值,垃圾回收的性能也更好。
②优先const,其次才是let。
数据类型
简单数据类型(原始类型):Undefined、NULL、String、Number、Boolean、Symbol
复杂数据类型Object(对象): Object 是一种无序名值对的集合。
typeof操作符的使用
let number = 0;
let string = "hello";
let boolean = true;
let und ;
let val = null;
let s = Symbol(10);
let obj={}
let fun = function () {
}
console.log(typeof number)
console.log(typeof string)
console.log(typeof boolean)
console.log(typeof und)
console.log(typeof val)
console.log(typeof s)
console.log(typeof obj)
console.log(typeof fun)
undefined与null
Undefined 类型只有一个值,就是特殊值 undefined。当使用 var 或 let 声明了变量但没有初始 化时,就相当于给变量赋予了 undefined 值 。
Null 类型同样只有一个值,即特殊值 null。逻辑上讲,null 值表示一个空对象指针, 在定义将来要保存对象值的变量时,建议使用 null 来初始化,不要使用其他值。
undefined 值是由 null 值派生而来的,表面上它们是相等的,但是它们的用途完全不一样,如前面说的,我们永远没必要显示的设置undefined,但是,null不一样,任何时候,只要变量需要保存对象,而当前又没有合适的对象保存,这时我们就要用null填充。
Number
整型、浮点型。
tips:浮点型存在精度的问题,在进行相等判断时,要考虑精度。
NaN
有一个特殊的数值叫 NaN,意思是“不是数值”(Not a Number),用于表示本来要返回数值的操作失败了(而不是抛出错误)。
console.log(0/0); // NaN
console.log(-0/+0); // NaN
如果分子是非 0 值,分母是有符号 0 或无符号 0,则会返回 Infinity 或-Infinity:
console.log(5/0); // Infinity
console.log(5/-0); // -Infinity
①任何涉及到NaN的操作,返回的都只会是NaN
②NaN不等于任何值,包括自身,我们可以使用isNaN()对其他值是否为NaN进行判断
数值转换
将非数值转换为数值的函数:
Number()、parseInt()、parseFloat()
String
String(字符串)数据类型表示零或多个 16 位 Unicode 字符序列。字符串可以使用双引号(")、 单引号(')或反引号(`)标示 。
String的特点
不可变的(immutable),意思是一旦创建,它们的值就不能变了。 要修改 某个变量中的字符串值,必须先销毁原始的字符串,然后将包含新值的另一个字符串保存到该变量:
let lang = "Java";
lang = lang + "Script";
转换为String
toString()函数
模板字符串
let myMultiLineString = 'first line\nsecond line'
console.log(myMultiLineString);
// first line
// second line
字符串插值
字符串插值通过在${}中使用一个 JavaScript 表达式实现
let interpolatedTemplateLiteral =
`${ value } to the ${ exponent } power is ${ value * value }`;
Symbol
符号是原始值,且符号实例是唯一、不可变的。 符号的用途是确保对象属性使用唯一标识符,不会发生属性冲突的危险。
Object☆
通过创建 Object 类型的实例来创建对象 :
let obj = new Object()
类似 Java 中的 java.lang. Object,ECMAScript 中的 Object 也是派生其他对象的基类。Object 类型的所有属性和方法在派生 的对象上同样存在。
每个 Object 实例都有如下属性和方法:
constructor 用于创建当前对象的函数。
hasOwnProperty(propertyName)
isPrototypeOf(object)
propertyIsEnumerable(propertyName)
toLocaleString()
toString()
valueOf()
操作符
一元操作符
递增/递减操作符
一元加和减
位操作符
按位非(~)
按位与(&)
按位或(|)
按位异或(^)
有符号左移(<<)
有符号右移(>>)
无符号右移(>>>)
布尔操作符
逻辑非(!)
逻辑与(&&)
逻辑或(||)
乘性操作符
乘法(*)、除法(/)和取模(%)
指数运算符
**
加性操作符
加+、减-
关系操作符
小于(<)、大于(>)、小于等于(<=)和大于等于(>=)
相等操作符★
== !=,这两个会强制类型转换
===,这个不会强制类型转换
条件操作符(三元)
?:
赋值操作符
= += /= *= -= 等等
语句(流程控制)
if语句
do-while语句
while语句
for语句
for-in语句
for-of语句
break和continue语句
switch语句
函数
函数的声明、调用
function a(){}
let a = function(){}
a()
JavaScript的变量、作用域和内存
原始值与引用值
变量可以包含两种不同类型的数据:原始值和引用值。原始值(primitive value)就是 最简单的数据,引用值(reference value)则是由多个值构成的对象。
保存原始值的变量是按值(by value)访问的,因为我们操作的就是存储在变量中的实际值。保存引用值的变量是按引用(by reference)访问的。
复制值
跟Java一个样,原始值就是直接copy一份,引用类型就是浅拷贝
传递参数
按值传递 。
执行上下文与作用域
全局上下文、函数上下文、块级上下文、作用域链
作用域链增强
try/catch 语句的 catch 块
with 语句
垃圾回收
标记清理 (常用)
引用计数 (不常用)
JavaScript基本引用类型
Date
RegExp
原始值包装类型 : Boolean、Number 和 String
单例内置对象 :
Global( window )、 Math
JavaScript集合引用类型
Object 是一个基础类型,所有引用类型都从它继承了基本的行为
Array 表示一组有序的值,并提供了操作和转换值的能力
Map
Set
Date
RegExp
JavaScript迭代器与生成器
迭代器是一个可以由任意对象实现的接口,支持连续获取对象产出的每一个值。任何实现 Iterable 接口的对象都有一个 Symbol.iterator 属性,这个属性引用默认迭代器。默认迭代器就像一个迭代器 工厂,也就是一个函数,调用之后会产生一个实现 Iterator 接口的对象。 迭代器必须通过连续调用 next()方法才能连续取得值,这个方法返回一个 IteratorObject。这 个对象包含一个 done 属性和一个 value 属性。前者是一个布尔值,表示是否还有更多值可以访问;后 者包含迭代器返回的当前值。这个接口可以通过手动反复调用 next()方法来消费,也可以通过原生消 费者,比如 for-of 循环来自动消费。 生成器是一种特殊的函数,调用之后会返回一个生成器对象。生成器对象实现了 Iterable 接口, 因此可用在任何消费可迭代对象的地方。生成器的独特之处在于支持 yield 关键字,这个关键字能够 暂停执行生成器函数。使用 yield 关键字还可以通过 next()方法接收输入和产生输出。在加上星号之 后,yield 关键字可以将跟在它后面的可迭代对象序列化为一连串值
JavaScript面向对象
理解对象
创建自定义对象:
//创建 Object 的一个新实例,然后再给它添加属性和方法
let person = new Object();
person.name = "Nicholas";
person.age = 29;
person.job = "Software Engineer";
person.sayName = function() {
console.log(this.name);
};
//对象字面量创建
let person = {
name: "Nicholas",
age: 29,
job: "Software Engineer",
sayName() {
console.log(this.name);
}
};
属性
ECMA-262 使用一些内部特性来描述属性的特征。这些特性是由为 JavaScript 实现引擎的规范定义 的。因此,开发者不能在 JavaScript 中直接访问这些特性。为了将某个特性标识为内部特性,规范会用 两个中括号把特性的名称括起来,比如[[Enumerable]]
属性的特性:
数据属性:数据属性包含一个保存数据值的位置。值会从这个位置读取,也会写入到这个位置。
[[Configurable]]
[[Enumerable]]
[[Writable]]
[[Value]]
访问器属性: 访问器属性不包含数据值。相反,它们包含一个获取(getter)函数和一个设置(setter)函数 。 在读取访问器属性时,会调用获取函数,这个函数的责任就是返回一个有效 的值。在写入访问器属性时,会调用设置函数并传入新值,这个函数必须决定对数据做出什么修改。
[[Configurable]]
[[Enumerable]]
[[Get]]
[[Set]]
访问器属性是不能直接定义的,必须使用 Object.defineProperty()。
对象解构
对象解构就是使用与对象匹配的结构来实现对象属性赋值。
let person = {
name: 'Matt',
age: 27
};
let { name, age } = person;
创建对象
工厂模式创建
function createPerson(name, age, job) {
let o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function() {
console.log(this.name);
};
return o;
}
let person1 = createPerson("Nicholas", 29, "Software Engineer");
构造函数模式创建
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function() {
console.log(this.name);
};
}
let person1 = new Person("Nicholas", 29, "Software Engineer");
原型模式创建
每个函数都会创建一个 prototype 属性,这个属性是一个对象,包含应该由特定引用类型的实例 共享的属性和方法。实际上,这个对象就是通过调用构造函数创建的对象的原型。使用原型对象的好处 是,在它上面定义的属性和方法可以被对象实例共享。原来在构造函数中直接赋给对象实例的值,可以 直接赋值给它们的原型。
function Person() {}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function() {
console.log(this.name);
};
let person1 = new Person();
person1.sayName(); // "Nicholas"
理解原型
无论何时,只要创建一个函数,就会按照特定的规则为这个函数创建一个 prototype 属性(指向 原型对象)。默认情况下,所有原型对象自动获得一个名为 constructor 的属性,指回与之关联的构造函数。
继承
实现继承是 ECMAScript 唯一支持的继承方式,而这主要是通过原型链实现的。
原型链
原型链涉及把构造函数的原型赋值为另一个类型的实例。 这样一来,子类就可以访问父类的所有属性和方法,就像基于类的继承那样。原型链的问题是所有继承 的属性和方法都会在对象实例间共享,无法做到实例私有。盗用构造函数模式通过在子类构造函数中调 用父类构造函数,可以避免这个问题。这样可以让每个实例继承的属性都是私有的,但要求类型只能通 过构造函数模式来定义(因为子类不能访问父类原型上的方法)。目前最流行的继承模式是组合继承, 即通过原型链继承共享的属性和方法,通过盗用构造函数继承实例属性。
默认情况下,所有引用类型都继承自 Object,这也是通过原型链实 现的。任何函数的默认原型都是一个 Object 的实例,这意味着这个实例有一个内部指针指向 Object.prototype。这也是为什么自定义类型能够继承包括 toString()、valueOf()在内的所有默 认方法的原因。
类
ECMAScript 6 新引入的 class 关键字具有正式定义类的能力。类(class)是 ECMAScript 中新的基础性语法糖结构,因此刚开始接触时可能会不太习惯。虽然 ECMAScript 6 类表面 上看起来可以支持正式的面向对象编程,但实际上它背后使用的仍然是原型和构造函数的概念。
类的语法让开发者可以优雅地定 义向后兼容的类,既可以继承内置类型,也可以继承自定义类型。类有效地跨越了对象实例、对象原型 和对象类之间的鸿沟。
JavaScript反射与代理
代理和反射为开发者提供了拦截并向基本操作嵌入额外行为的能力。具体地 说,可以给目标对象定义一个关联的代理对象,而这个代理对象可以作为抽象的目标对象来使用。在对 目标对象的各种操作影响目标对象之前,可以在代理对象中对这些操作加以控制。
代理是使用 Proxy 构造函数创建的。这个构造函数接收两个参数:目标对象和处理程序对象。缺 少其中任何一个参数都会抛出 TypeError。
使用代理的主要目的是可以定义捕获器(trap)。捕获器就是在处理程序对象中定义的“基本操作的 拦截器”。每个处理程序对象可以包含零个或多个捕获器,每个捕获器都对应一种基本操作,可以直接 或间接在代理对象上调用。每次在代理对象上调用这些基本操作时,代理可以在这些操作传播到目标对 象之前先调用捕获器函数,从而拦截并修改相应的行为。