JavaScript基础速记
徐徐 抱歉选手

变量

变量就是一个用来存储数据类型的容器。

声明var&let

var会被变量提升,但是let没有。因此使用var时,可以根据需要多次声明相同名称的变量,但是let不能。

声明与初始化的区别

声明但不初始化变量就相当于让变量容器存在于执行上下文中,但里面是“空”的,什么也不放,因而在控制台会返回undefined。

没有声明也没有初始化的变量相当于执行环境中没有该变量,不存在容器,因而控制台直接会返回报错信息。

变量类型

JavaScript有9中数据类型:

  • 原始类型Primitive(boolean, undefined, number, string, symbol, bigInt)
  • 引用类型(Object)
  • null也是一个Object类型
  • function 非数据结构,每个Function构造器都由Object构造器派生。

Number

数值的数据分类有哪些,基本运算符有哪些,变量自增自减在控制台中的表现。

String

需要单引号或者双引号包围

  • 字符串中转义字符的使用:\'

  • 字符串拼接:myString1+myString2

  • 利用字符串长度索引:myString1[0]&string1[string1.length-1]

  • 查找子串: myString1.indexOf('mySubstring1')

  • 切割子串: myString1.slice(i,j)第二个参数可选,如果没有传入那么一直切割到末尾,不会改变myString1

  • 替换子串:myString1.replace('mySubstring1', 'mySubstring2'),返回更新后的字符串。

如果要用新的字符串替换原来的字符串,一定不可以只有newStory.replace('130', pound);,这对原字符串并不会有任何影响,一定要newStory = newStory.replace('130', pound);才会修改原字符串。

  • 大小写:myString1.toLowerCase()/toUpperCase()

Number & String类型互相转换,Number(myString)可以将String类型转换为Number类型,myNum.toString()可以讲Number类型转换为String类型。

Boolean

null & undefined

null表示为空,就像有个能放东西的容器存在,但是这里什么都没放。一个对象是null,代表一个空对象;null本身也是个对象。

undefined表示不存在,是否存在只有在运行的时候才知道。

Object

JavaScript时一种动态类型语言,不需要人为指定变量包含什么类型的数据。你赋什么类型的值,变量就会是什么类型。可以利用typeof操作符检查传递给改操作符的变量的数据类型。

Array是单个对象,用方括号包含很多元素,元素之间用逗号分隔,元素索引从0开始。

  • 获取长度:sequence.length

  • 添加末尾元素:let length = sequence.push(element1)返回新数组的长度

  • 删除末尾元素:let removedItem = sequence.pop()返回已删除的项目

  • 添加头部元素:let length = sequence.unshift(element1)

  • 删除头部元素:let removedItem = sequence.shift()返回已删除的项目

Array & String类型互相转换

当String中存在重复出现的分隔符时,可以将String分成许多元素放入Array中,如let myArray = myString.split(',');

当Array中的元素想通过某种方式连接起来变成String时,可以使用let myString = myArray.join(',');,也可以使用let myString = myArray.toString();。二者区别是join可以指定分隔符。

循环

for-loop

1
2
3
for (initializer; exit-condition; final-expression) {
// code to run
}

while loop

1
2
3
4
5
6
initializer
while (exit-condition) {
// code to run

final-expression
}

do-while loop

1
2
3
4
5
6
initializer
do {
// code to run

final-expression
} while (exit-condition)

函数

函数是对象方法的一部分。

内置浏览器函数基于浏览器API,并不是核心JavaScript语言的一部分,他们是方法。方法是在对象哪定义的函数,浏览器内置函数(方法)和变量(属性)储存在结构化对象里,使得代码管理更高效,易于处理。

声明与调用

1
2
3
4
function myFunction(){

}//声明
myFunction()//调用

怎么样才算调用一个函数呢?函数名后面的括号叫做函数调用运算符(function invocation operator)。

注意以下两种写法。

btn.onclick = displayMessage;的意思是当按钮被点击就运行该名称的函数。

btn.onclick = displayMessage();的意思是不需要点击按钮该函数就会被调用。

为了改变第二种结果,最适合的写法应该是将要调用的函数包裹在匿名函数中

1
2
3
btn.onclick = function() {
displayMessage();
};

匿名函数

一个没有名称的函数就是匿名函数,它本身不会做任何事情,一般将匿名函数和事件处理程序一起使用。

1
2
3
myButton.onclick = function(){

}

还可以把匿名函数分配为多个变量值。

1
2
3
4
var myGreeting = function(){

}
myGreeting()//调用

匿名函数也叫匿名表达式。函数声明会declaration hoisting,而函数表达式不会。

参数

参数值放在函数括号内,可以叫做参数arguments,也被叫做属性properties/attributes。

函数作用域scope和冲突

所有函数的最外层被称为全局作用域。 在全局作用域内定义的值可以在任意地方访问。

当你创建一个函数时,函数内定义的变量和其他东西都在它们自己的单独的范围内, 意味着它们被锁在自己独立的隔间中, 不能被函数外的代码访问。

参数的存在就相当于把属于一个作用域的变量传递另一个作用域。

返回值

一些没有返回值的函数调用后在控制台输出就是空值void或未定义值undefined。

通常函数返回值是用作中间步骤的计算结果,该结果可以用于计算的下一阶段。

事件

每个可用的事件都会有一个事件处理器,也就是事件触发时会运行的代码块。当我们定义了一个用来回应事件被激发的代码块的时候,我们说我们注册了一个事件处理器

监听器留意事件是否发生,然后处理器就是对事件发生做出的回应。

响应网页事件的几种机制

事件处理器属性

比如对于几乎所有HTML元素,都有onclick属性,和其他.style,.textContent一样。

行内事件处理器-不要使用

也叫事件处理程序HTML属性,会混用HTML和JavaScript。

addEventListener()

定义在DOM Level2 Events中的函数addEventListener(),接受两个参数。第一个参数是我们想要将处理器函数应用上去的时间名称,第二个是用来回应事件的函数,可以是一个匿名函数。

相较于事件处理器属性被覆盖,addEventListener()可以实现一个监听器注册多个处理器。

事件对象

有时候在处理函数内部,可一个固定指定名称的参数,如event,evt,e,这是事件对象,它被自动传递给事件处理函数。

事件对象 etarget属性始终是事件刚刚发生的元素的引用。

阻止默认行为

自定义注册表单。,当你填写详细信息并按提交按钮时,自然行为by default是将数据提交到服务器上的指定页面进行处理,并将浏览器重定向到某种“成功消息”页面(或 相同的页面,如果另一个没有指定。)

但是当用户提交的数据并不正确时,开发人员希望停止提交信息到服务器,并给用户错误提示,需要在事件对象上调用preventDefault()函数,这样就停止了表单提交。

事件冒泡及捕获

事件冒泡和捕捉是两种机制,主要描述当在一个元素上有两个相同类型的事件处理器被激活会发生什么。

当一个事件发生在具有父元素的元素上时,现代浏览器运行两个不同的阶段:捕获阶段和冒泡阶段。

在现代浏览器中,默认情况下,所有事件处理程序都在冒泡阶段进行注册。

捕获阶段:

  • 浏览器检查元素的最外层祖先<html>,是否在捕获阶段中注册了一个onclick事件处理程序,如果是,则运行它。
  • 然后,它移动到<html>中单击元素的下一个祖先元素,并执行相同的操作,然后是单击元素再下一个祖先元素,依此类推,直到到达实际点击的元素。

冒泡阶段:

  • 浏览器检查实际点击的元素是否在冒泡阶段中注册了一个onclick事件处理程序,如果是,则运行它
  • 然后它移动到下一个直接的祖先元素,并做同样的事情,然后是下一个,等等,直到它到达<html>元素。

修复

标准事件对象具有可用的名为 stopPropagation()的函数,当在事件对象上调用该函数时,它只会让当前事件处理程序运行,但事件不会在冒泡链上进一步扩大,因此将不会有更多事件处理器被运行(不会向上冒泡)。

事件委托

Event delegation allows you to avoid adding event listeners to specific nodes; instead, the event listener is added to one parent. That event listener analyzes bubbled events to find a match on child elements.

check the event object’s target property to gain a reference to the actual clicked node.

虽然事件处理函数添加在父元素上,但通过if语句对事件对象进行条件判断,可以访问父元素及其内部子元素,从而实现特定的相应操作。

1
if(e.target && e.target.nodeName == "LI")

Using the Element.matches API, we can see if the element matches our desired target.

1
if (e.target && e.target.matches("a.classA")) 

参考

一些实作的易错点

关于单引号的双引号的区分。规定自己对于html元素名称或者属性的引用都使用单引号,但对字符串统一使用双引号。如if(btn.getAttribute('class') === "dark")

对属性赋值都是赋值字符串,如overlay.style.backgroundColor = "rgba(0, 0, 0, 0.5)";,如果rgba没有加双引号,那最后javascript编译器会把它当作一个函数,从而报错。

对象

对象是一个包含相关数据和方法的集合(通常由一些变量和函数组成,我们称之为对象里面的属性和方法)。

面向对象

对象包(object package,或者叫命名空间 namespace)存储(官方用语:封装)着对象的数据(常常还包括函数),使数据的组织和访问变得更容易了;对象也常用作数据存储体(data stores),用于在网络上运输数据,十分便捷 —— 也就是JSON(JavaScript对象表示法)。

对象字面量

一个对象由许多的成员组成,每一个成员都拥有一个名字(像上面的name、age),和一个值(如[‘Bob’, ‘Smith’]、32)。每一个名字/值(name/value)对被逗号分隔开,并且名字和值之间由冒号(:)分隔。

对象成员的值可以是任意的,可以是字符串(string),数字(number),数组(array),函数(function)。前三种属于资料项目,被称为对象的属性(property);最后一个函数,允许对象对资料做一些操作,被称为对象的方法(method)。

1
2
3
4
5
var objectName = {
member1Name : member1Value,
member2Name : member2Value,
member3Name : member3Value
}

一个如上所示的对象被称之为对象的字面量(literal)——手动的写出对象的内容来创建一个对象。

访问对象属性

点表示法

使用点表示法(dot notation)来访问对象的属性和方法。

对象的名字表现为一个命名空间(namespace),它必须写在第一位。

子命名空间

可以用一个对象来做另一个对象成员的值。访问对象内的对象成员就相当于要用两次点表示法。

紧接着是你想要访问的项目,标识可以是简单属性的名字,或者是数组属性的一个子元素,又或者是对象的方法调用。

括号表示法

另外一种访问属性的方式是使用括号表示法(bracket notation),例如person['age']

对象有时被称之为关联数组(associative array)了——对象做了字符串到值的映射,而数组做的是数字到值的映射。

括号表示法与点表示法的区别

括号表示法能接受一个变量作为成员名字;点表示法只能接受字面量的成员的名字,不接受变量作为名字。

设置成员对象

既可以对已有的成员的值进行改变,也可以创建新的成员并赋值。

this

关键字”this”指向了当前代码运行时的对象( 原文:the current object the code is being written inside ),它保证了当代码的上下文(context)改变时变量的值的正确性。

在字面量的对象里this看起来不是很有用,但是当你动态创建一个对象(例如使用构造器)时它是非常有用的。

对象与类的关系

用类(class)的概念去描述一个对象。

类并不完全是一个对象,它更像是一个定义对象特质的模板。 我们能够基于类创建出一些拥有class中属性及方法的对象。

当一个对象需要从类中创建出来时,类的构造函数就会运行来创建这个实例。

创建对象的方式总结

声明一个对象去创建对象

使用构造函数创建对象

使用Object()构造函数创建对象

首先使用var person1 = new Object();创建一个空的对象。

其次根据需要使用点或括号表示法向此对象添加属性和方法。


也可以将对象文本传递给Object()构造函数作为参数。var person1 = new Object({name: 'Chris', age: 38, greeting: function(){} });

基于现有对象创建新对象

使用Object2.create(Object1),基于现有对象创建新的对象, 它们具有相同的属性和方法。

结合原谅来看,这个函数做的就是从指定原型对象Object1创建一个新的对象Object2,在控制台输入Object2.__prooto__返回的就是对象Object1

原型

JavaScript 常被描述为一种基于原型的语言 (prototype-based language)

每个对象拥有一个原型对象,对象以其原型为模板、从原型继承方法和属性。原型对象也可能拥有原型,并从中继承方法和属性,一层一层、以此类推。这种关系常被称为原型链 (prototype chain),它解释了为何一个对象会拥有定义在其他对象中的属性和方法。

prototype属性

如果某一对象把属性定义在prototype属性之中,那就意味着这个对象允许其他对象继承这些方法。也就是说该对象可继承的内容都放在了该对象的prototype这个子命名空间中,其实这个子命名空间也是一个对象。

其他不位于prototype 对象内的成员,不会被“对象实例”或“继承自 Object() 的对象类型”所继承。这些方法/属性仅能被 Object() 构造器自身使用。

constructor属性

每个实例对象都从原型中继承了一个constructor属性,该属性指向了用于构造此实例对象的构造函数。

某个对象的constructor属性可以告诉我们该对象从什么对象继承而来。可以获取作为构造器的对象的属性。

在对象的constructor属性末尾添加一对圆括号(括号中包含所需的参数),并在前面添加 new 关键字,从而用这个构造器创建另一个对象实例。

原型存在的意义

如果某个对象的原型更新了方法,该对象可以通过上溯圆形链调用原型的方法,而不必把上游对象的方法复制到该对象中。

利用原型定义对象

一种极其常见的对象定义模式是,在构造器(函数体)中定义属性、在 prototype 属性上定义方法(也就是将方法都放在构造器的prototype 属性中)。如此,构造器只包含属性定义,而方法则分装在不同的代码块,代码更具可读性。

实际操作中的一些问题

实现继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Shape(x, y, velX, velY, exist){
// 将属性值当作构造函数的参数传入,使用this指向对象
// 注意对象要大写
this.x = x;
this.y = y;
this.velX = velX;
this.velY = velY;
this.exist = exist;
}
function Ball(x, y, velX, velY, exist, size, color){
Shape.call(this, x, y, velX, velY, exist);
// 继承属性
// 需要注意的是传入call函数的参数一定也要出现在构造函数的参数中
this.size = size;
this.color = color;
this.exist = true;
}

构造函数的参数与确定属性值的关系

1
2
3
4
5
6
7
8
function EvilCircle( x, y,exist, color, size){
Shape.call(this, x, y, exist);
this.color = color;
this.size = size;
this.velX = 20;
this.velY = 20;
// 这里确定值的velX和velY就没有作为参数传入
}

构造函数和原型之间的关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 这是构造函数,需要大写
function Ball(x, y, velX, velY, exist, size, color){
Shape.call(this, x, y, velX, velY, exist);
// 继承属性
// 需要注意的是传入call函数的参数一定也要出现在构造函数的参数中
this.size = size;
this.color = color;
this.exist = true;
}

// 这是原型对象,用于定义方法,可以通过this访问构造函数的属性
Ball.prototype.draw = function(){
ctx.beginPath();
ctx.fillStyle = this.color;
ctx.arc(this.x, this.y, this.size, 0, 2*Math.PI);
ctx.fill();
}

异步

一个线程是一个基本的处理过程,程序用它来完成任务。每个线程一次只能执行一个任务。

JavaScript 传统上是单线程的。即使有多个内核,也只能在单一线程上运行多个任务,此线程称为主线程(main thread)。

Web workers

借助web workers把一些任务交给一个名为worker的单独的线程。一般来说,用一个worker来运行一个耗时的任务,主线程就可以处理用户的交互(避免了阻塞)。

异步callbacks

异步callbacks函数只不过是作为参数传递给那些在后台执行的其他函数

原理

涉及两个函数,他们的关系是函数和参数,回调函数和调用回调函数的函数(包含函数)。

当我们把回调函数作为一个参数传递给另一个函数时,仅仅是把回调函数定义作为参数传递过去——回调函数并没有立刻执行,回调函数会在包含它的函数的某个地方异步执行,包含函数负责在合适的时候(例如触发了某个事件)执行回调函数

addEventListener()的第二个参数就是异步callback函数。第一个参数是侦听的事件类型,第二个就是事件发生时调用的回调函数。

回调函数和异步的关系

不是所有的回调函数都是异步的。

当遍历数组时使用的Array.prototype.forEach()函数需要的参数就是一个回调函数,它无需等待立刻运行。

问题

回调地狱与每层嵌套都需要调用一个失败回调。

Promises

基本流程

创建最初的promise

首先,为fectch()传入网络资源的URL作为参数。该函数返回一个promise对象,代表了异步操作完成或失败的对象。


这个最初既不成功也不失败的中间状态下的promise的官方术语叫作pending


返回promise

其次,使用then()块,传入回调函数作为参数。如果前一个操作成功,该函数将运行,并且每个回调都接收前一个成功操作的结果作为输入,因此您可以继续对它执行其他操作。

每个.then()块返回另一个promise,这意味着可以将多个.then()块链接到另一个块上,这样就可以依次执行多个异步操作。

如果任何一个.then()块返回失败,就运行末尾的catch()块,也是传入一个回调函数作为参数,提供了一个错误对象,可用来报告发生的错误类型。


当promise返回时,称为 resolved(已解决).

  1. 一个成功resolved的promise称为fullfilled实现)。它返回一个值,可以通过将.then()块链接到promise链的末尾来访问该值。.then()块中的执行程序函数将包含promise的返回值。
  2. 一个不成功resolved的promise被称为rejected拒绝)了。它返回一个原因(reason),一条错误消息,说明为什么拒绝promise。可以通过将.catch()块链接到promise链的末尾来访问此原因。

promise完成后运行一段代码

在promise完成后,你可能希望运行最后一段代码,无论它是否已实现(fullfilled)或被拒绝(rejected)。

.then()块与AddEventListener()

.then()块的工作方式类似于使用AddEventListener()向对象添加事件侦听器时的方式。

最显着的区别是.then()每次使用时只运行一次,而事件监听器可以多次调用。

流程合并

请记住,履行的promise所返回的值将成为传递给下一个 .then() 块的executor函数的参数。

1
2
3
4
5
6
7
8
9
10
11
fetch('coffee.jpg')
.then(response => response.blob())
.then(myBlob => {
let objectURL = URL.createObjectURL(myBlob);
let image = document.createElement('img');
image.src = objectURL;
document.body.appendChild(image);
})
.catch(e => {
console.log('There has been a problem with your fetch operation: ' + e.message);
});

多个promise协同工作

Promise.all()函数将一个promises数组,也就是多个pending,作为输入参数,并返回一个新的Promise对象,只有当数组中的所有promise都满足时才会满足。

1
2
3
Promise.all([a, b, c]).then(values => {
...
});

只要一个被拒绝了,该函数整体就不会返回promise。需要逐个检查promise返回了什么。

自定义Promise

使用Promise()构造函数构建自己的promise。

resolve()reject()是用来实现拒绝新创建的promise的函数。

1
2
3
4
5
6
let timeoutPromise = new Promise((resolve, reject) => {
resolve();//这个函数表明了调用该promise成功后,作为参数传递给后面的then块的值
reject();//这个函数表明了调用该promise失败后,作为参数传递给最后的catch块的值,一般是要和条件判断一起写的。

};
// 上面的写法也可以写成一个函数,函数返回的return是new Promise

async/await

这两个关键字async/await是基于promises的语法糖,使异步代码更易于编写和阅读。

async

在函数声明前使用async关键字,使该函数称为异步函数。函数声明,函数表达式,箭头函数都可以。

例如,

1
2
3
async function hello() { return "Hello" }; //异步函数
let hello = async function() { return "Hello" }; // 异步函数表达式
let hello = async () => { return "Hello" }; //箭头函数

异步函数是一个知道怎么使用await关键字调用异步代码的函数。调用异步函数会返回一个promise

await

await关键字与异步函数一起使用,且 await只在异步函数里面才起作用

使用

可以在调用任何返回Promise的函数前使用 await,包括Web API函数,然后将这个返回的量赋值到一个变量去。

WHY

await作为 .then() 代码块的替代存在,可以让我们用更少的.then()。这让代码的形式看起来更像同步代码,能够将结果返回到中间变量去,而不是像链条一样把一个输出连到另一个的输入。

await关键字使JavaScript运行时暂停于此行,允许其他代码在此期间执行,直到异步函数调用返回其结果。一旦完成,您的代码将继续从下一行开始执行。

超时和间隔

以下介绍的三个异步函数都是在主线程上运行的。在给定的时间间隔之前和重复调用之间可以在主线程上运行其他代码。

setTimeout()

在指定的时间后执行一段代码。

传入参数

  • 回调函数/函数引用
  • 以毫秒为单位的时间间隔:如果指定为0,函数将尽快运行,而不是立即运行。因为需要主线程的堆栈为空才能运行回调函数
  • 希望传递给回调函数的参数值,在参数列表后面添加即可。

取消任务

setTimeout() 返回一个标志符变量用来引用这个间隔。取消该超时任务使用函数clearTimeout(setTimeout(params))

setInterval()

以固定的时间间隔重复运行一段代码,例如动画。这与setTimeout()的工作方式非常相似。

传入参数

作为第一个参数传递给它的函数,重复执行的时间不少于第二个参数给出的毫秒数,而不是一次执行

可以将正在执行的函数所需的任何参数作为 setInterval() 调用的后续参数传递。

取消任务

setInterval() 返回一个确定的值,稍后你可以用它来取消间隔任务。

通过将setInterval()调用返回的标识符传递给clearInterval()函数来取消间隔任务。

递归调用setTimeout()和调用setInterval()效果类似。

二者微妙区别在于:

setInterval()的执行时间包括了我们想要运行的代码所花费的时间。

setTimeout()每次都等代码运行完了之后,开始计算等待的时间,时间间隔总是相同。

requestAnimationFrame()

setInterval()的现代版本,在浏览器下一次重新绘制显示之前执行指定的代码块,因此该方法将重新加载页面之前要调用的回调函数作为参数。

WHY

动画的平滑度直接取决于动画的帧速率,并以每秒帧数(fps)为单位进行测量。由于大多数屏幕的刷新率为60Hz,因此在使用web浏览器时,可以达到的最快帧速率是每秒60帧(FPS)。

requestAnimationFrame() 总是试图尽可能接近60帧/秒的值

当然有时这是不可能的如果你有一个非常复杂的动画,你是在一个缓慢的计算机上运行它,你的帧速率将更少。requestAnimationFrame()尽其所能利用现有资源提升帧速率。

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function draw(timestamp) {
// Drawing code goes here
if(!startTime) {
startTime = timestamp;
}

currentTime = timestamp - startTime;

// Do something based on current time

requestAnimationFrame(draw);
}

draw();

传递给 requestAnimationFrame() 函数的实际回调也可以被赋予一个参数(一个时间戳值),表示自 requestAnimationFrame() 开始运行以来的时间。

模版字符串

${var}是变量的占位符,常用语字符串拼接。在实际开发中一般在ajax请求中用不变的html${变量}html标签代替以前传统复杂的单引号双引号与+的拼接。

1
2
3
spinner.style.transform = 'rotate(' + rotateCount +'deg)';

spinner.style.transform = `rotate(${rotateCount}deg)`;

问题

requestAnimationFrame() 的限制之一是无法选择帧率。他总是尽可能地逼近60fps。

有的时候反而需要限制帧率,比如老式动画。如果需要以较慢的帧速率运行动画,则需要使用setInterval()或递归的setTimeout()

API

共性

基于对象

API使用一个或多个对象来与JavaScript的代码交互,这些对象的功能是:API使用的数据容器(包含在对象属性中),API提供的功能(包含在对象方法中)。

可识别的入口点

文档对象模型 (DOM) API的入口点就是Document对象。

Canvas API需要为要编写的API代码创建特定的上下文。例如

1
2
var canvas = document.querySelector('canvas');
var ctx = canvas.getContext('2d');

使用事件来处理状态的变化

一些Web API不包含事件,但有些包含一些事件。当事件触发时,允许我们运行函数的处理程序属性通常在单独的 “Event handlers”(事件处理程序) 部分的参考资料中列出。

文档操作的API

web页面构成

window是载入浏览器的标签,用window对象来表示。通过调用该对象的方法,可以返回窗口的大小,操作载入窗口的文档等等。

navigator是浏览器存在于web上的状态和标识(即用户代理),用navigator对象来表示,通过调用该对象的方法,可以获取用户摄像头的地理信息、偏好语言、多媒体流等。

document是载入窗口的实际页面,用document对象表示,可以返回、操作文档中HTML和CSS上的信息。

DOM树

元素节点、根节点、子节点、后代节点、父节点、兄弟节点、文本节点等。

主要需要弄清楚子节点和后代节点的关系。子节点是直接位于另一个节点内的节点,后代节点是位于另一个节点内任意位置的节点。

依据DOM元素存在的各种事件,考虑的角度应该是在创建这个对象的时候就为它添加对应的事件,而不是说在这个事件要发生的时候去考虑该怎么做。

input输入框的内容是通过value属性获取的,let content = input.value;。对input输入框的操作还涉及了自动对焦,input.focus();

span这种行内元素的内容是通过innerHTML确定的,span.innerHTML = content;

操作DOM元素

获取

Document.querySelector()允许使用CSS选择器选择元素,它调用会匹配它在文档中遇到的第一个匹配的元素。

如果需要选择多个元素,可以使用Document.querySelectorAll()。会返回一个存放了这些元素的array。

一般来说对DOM元素的引用全部创建为常量,因为这些引用在应用程序的生命周期中不需要更改。

也有其他方法比如,document.getElementById()/getElementByTagName(),分别传入id属性值和html元素标签名。

新建/放置

新建元素使用Document.vreateElement(),放置使用Node.appendChild()

新建文本节点要用Document.createTextNode(),放置使用Node.appendChild()

移动/删除

基于现有元素做一个副本,然后把副本移动到其他节点,需要使用Node.cloneNode()

删除节点需要知道父亲节点的引用和要删除节点的引用,Node.removeChild();但是不知道父亲节点的引用,需要问询父亲节点linkPara.parentNode.removeChild(linkPara);

操作样式

有两种方式。

第一种是直接想要动态设置样式的内部元素添加内联样式。通过HTMLElement.style属性来实现,这个属性包含了文档中每一个元素的内联样式的信息。载入页面后会直接看到样式应用到HTML文件对应的元素。

第二种是使用Element.setAttribute(attribute, value)

从服务器获取数据的API

主要涉及两个常用API,一个是XMLHttpRequest,另一个是Fetch。这些API允许网页直接对服务器上可用的特定资源进行HTTP请求,并在显示之前根据需要对数据进行格式化。

在早期,这种通用技术被称为Asynchronous JavaScript and XML(Ajax)

第三方API根植于第三方浏览器,要通过JavaScript获取,首先要连接到它的功能接口上并使其在页面上生效。

使用第三方API

首先就是要阅读第三方API的官方文档了解应该如何使用。

其次需要向第三方API的提供者申请使用密钥。

浏览器API通常有一个安全性提示,在第三方API中,使用API KEY来允许开发人员访问API功能,但是如果开发者恶意使用API来侵犯用户隐私,API的提供者可以根据密钥撤销该开发者的API使用权。

引入第三方库

需要引入一个<script></script>元素连接到第三方服务器所开放的JavaScript库。

谷歌地图的API如下。

1
<script type="text/javascript" src="https://maps.google.com/maps/api/js?key=AIzaSyDDuGt0E5IEGkcE6ZfrKfUtE9Ko_de66pA"></script>

基于URL请求

一般此类API都遵循RESTful API的设计指南,遵循域名、API版本、Endpoint、Filtering过滤信息参数、状态码、错误处理等相关规定。

  • 利用字符串操作等方式创建相应的URL

    • 首先应当有一个baseURL

    • 建立API和本地应用的链接?api-key=${key}

      每次向服务器对应的 URL 发起 get 请求,都需要把 API key 作为 get 请求的参数。

    • 个性化请求参数&page=${pageNumber}

  • 利用URL从API请求数据,结合Fetch方法/XMLHTTPRequest方法。

  • 对接收到的JSON数据进行解析,拆分显示到当前HTML的各个元素。

用于绘制和操作图形的API

主要基于<canvas>讲解。

基本准备

  • body中添加带class的canvas元素并在JS中选择该canvas元素
  • 获取该画布上下文canvas context并完成设置var ctx = canvas.getContext('2d');,还可以选择wbgl1和webgl2。这里的ctx是一个CanvasRenderingContext2D对象。
  • 画布原点默认是在(0,0),如需改变,使用ctx.translate()函数,将画布原点移动到指定坐标。

绘制基本形状

  • 内容填充与描绘边

    ctx.fillStyle = ''; ctx.fillRect()

    ctx.storkeStyle = ''; ctx.lineWidth = ; ctx.strokeRect();

  • 路径绘制与画线自定义形状

    前期准备确定一下ctx.fillStyle = '',即要画什么颜色。

    • 第一步:产生钢笔,并移动钢笔,这个步骤不会绘制任何内容ctx.beginPath(); ctx.moveTo();

    • 第二步:移动钢笔到多个计算好坐标的位置,形成闭合路径 ctx.lineTo();

    • 第三步:选择填充还是描边这两种行为 ctx.fill(); ctx.stroke();

  • 画圆/画弧

1
2
3
4
5
ctx.fillStyle = 'yellow';
ctx.beginPath();
ctx.arc(200, 106, 50, degToRad(-45), degToRad(45), true);
ctx.lineTo(200, 106);
ctx.fill();
  • 填充文字
1
2
3
4
5
6
7
ctx.fillStyle = 'red'; 
// ctx.strokeStyle = 'white';
// ctx.lineWidth = 1;

ctx.font = '48px georgia';
ctx.fillText('Canvas text', 50, 150);

  • 图片嵌入

首先,使用Image()构造器创建一个新的HTMLImageElement对象,返回对象的类型与非空<img>元素的引用一致,设置改图片的src属性

其次使用ctx.drawImage()函数来嵌入图片。由于需要事先确保图片已经载入完毕,要在图片的onload事件处理器中调用嵌入图片的函数。

动画

window.requestAnimationFrame()只接受一个参数,这个参数是一个每帧都要运行的函数名,因为请注意每一帧我们都整体清除画布并重新渲染所有内容。

这个函数传入该方法后就会在一秒内被重复运行多次。下一次浏览器准备好更新屏幕时,将会调用你的函数。如果你的函数向动画中绘制了更新内容,则在函数结束前再次调用 requestAnimationFrame(),动画循环得以保留。

只有在停止调用 requestAnimationFrame() 时,或 requestAnimationFrame() 调用后、帧调用前调用了 window.cancelAnimationFrame()时,循环才会停止。

实现步骤

  1. 清除画布内容(可用 fillRect()clearRect())。
  2. (在需要时)用 save() 保存状态。(在进行下一步前保存所更新的设置,一般在复杂环境中用到)
  3. 绘制动画图形。
  4. 使用 restore() 恢复第 2 步中保存的状态。
  5. 调用 requestAnimationFrame() 准备下一帧动画

3D画布内容通过WebGL API实现,WebGL基于Open GL图形编程语言实现,可以直接与GPU通信,更类似C++的底层语言。

常用的第三方API有three.js/PlayCanvas等。

使用three.js的基本步骤如下:

  • 构造一个新的3D场景
  • 为该3D场景添加摄影机,代表绘图语境中观察着视角
  • 指定渲染器,并将3D画布与DOM对象关联
  • 创建基于纹理的画布内容

首先要创建一个TextureLoader对象,把作为纹理的图片在图片载入成功后(通过事件监听实现)传入一个回调函数。

其次该纹理图可能会经过一系列复制旋转等操作。

  • 为场景打光,分柔光和硬光,分别创建对象。

视频和音频API

HTML5中的播放器

HTML5中<video><audio>元素允许我们把视频和音频嵌入到网页当中。

构成HTML视频播放器的主要由两个部分,这两个部分都被包裹在名为<div class="player">的div块中。

HTML部分


首先是video元素层,包含多个视频source,可以根据浏览器来加载其所支持的不同视频格式。

1
2
3
4
5
6
<video controls>
<!-- 注意上面的controls是浏览器默认控件 -->
<source src="video/sintel-short.mp4" type="video/mp4">
<source src="video/sintel-short.mp4" type="video/webm">
<!-- fallback content here -->
</video>

其次是自定义控件层,涉及多个button以及div。

四个 <button>涉及play/pause, stop, rewind, and fast forward,分别带有属性一个class名 ,一个data-icon 属性来决定在每个按钮上显示什么图标 (在下一节讲述它是如何工作的),和一个aria-label 属性为每一个按钮提供容易理解的描述, 即使我们没有在tags内提供可读的标签。

有一个设定的计时器 <div>用来报告已经播放的时长。由两部分组成, 一个<span> 包含了流逝时间的分钟和秒;一个额外的<div> 用来创建一个水平的随着时间增加而增长的进度条。

1
2
3
4
5
6
7
8
9
10
<div class="controls">
<button class="play" data-icon="P" aria-label="play pause toggle"></button>
<button class="stop" data-icon="S" aria-label="stop"></button>
<div class="timer">
<div></div>
<span aria-label="timer">00:00</span>
</div>
<button class="rwd" data-icon="B" aria-label="rewind"></button>
<button class="fwd" data-icon="F" aria-label="fast forward"></button>
</div>

CSS部分

关于播放器的CSS设置有如下几个要点。

  • 默认情况下,我们将控件的opacity设置为0.5 opacity,这样当您尝试观看视频时,它们就不会分散注意力。 只有当您将鼠标悬停/聚焦在播放器上时,控件才会完全不透明。
  • 使用flexbox控制各个按钮
  • CSS中设置我们的自定义控件的visible属性为hidden;video元素中默认使用浏览器自带的控件。这样的好处是,一旦JavaScript由于某种原因没有加载, 用户依然可以使用原生的控件播放视频。
  • 我们使用 ::before状态选择器显示在每个button被点击之前显示内容。

使用API

HTMLMediaElement API允许开发者用编程的方式控制视频和音频播放的相关功能,如例如 HTMLMediaElement.play(), HTMLMediaElement.pause()

客户端储存

现代web浏览器提供了很多在用户电脑的web客户端存放数据的方法。

它是由 JavaScript APIs 组成的因此允许你在客户端存储数据 (比如在用户的机器上),而且可以在需要的时候重新取得需要的数据。例如个性化网站偏好,站点行为如是否记住登录状态,本地化数据的静态资源可以加速访问等。

传统上使用cookies,现代浏览器使用Web Storage和Indexed DB这两个API。

Web Storage API

只需存储简单的键名/键值对数据 (限制为字符串、数字等类型) 并在需要的时候检索其值。

存储对象sessionStorage和localStorage

一般所有web storage的数据都包含在浏览器内的两个对象中,他们都属于Storage类的对象。对这两个对象调用方法就能实现客户端数据存储。

第一个是sessionStorage,只要浏览器开着,数据就会一直保存 (关闭浏览器时数据会丢失)。

第二个是localStorage,会一直保存数据,甚至到浏览器关闭又开启后也是这样。

web storage 的一个关键特性是,数据在不同页面加载时都存在(甚至是当浏览器关闭后,对localStorage的而言)。

方法调用

Storage.setItem()允许在客户端储存中保存一个数据项,接受两个参数:数据项的名字和其值。

例如localStorage.setItem('name','Chris');

Storage.getItem()接受一个参数:想要检索的数据项的名称,返回数据项的值。

例如localStorage.getItem('name');

Storage.removeItem()接受一个参数:想要删除的数据项的名称,并从web stroage中删除该数据项。

例如localStorage.removeItem('name');

数据库的使用

首先,需要打开数据库并存放在常量中,第一个参数是名字,第二个参数是版本号。如果不存在该数据库,会自动创建。

我们需要在页面载入成功,也就是window.onload完成之后打开一下数据库并把它放在,而不是在需要用数据库的时候再去调用他,如果数据库很大的话,这个加载需要很久的时间,会导致阻塞。

1
let request = window.indexedDB.open('notes', 1);

其次,由于数据库操作是异步的,需要给打开后的数据库添加各种状态监听函数

监听函数需要包括是否成功打开数据库的后台消息onerror/onsuccess;设置初始数据库onupgradeneeded。

如何从监听函数内部获取对现有数据库的引用let db = e.target.result;

如何在数据库中建立一个新的table/对象库let objectStore = db.createObjectStore('notes', {keyPath:'id', autoIncrement: true});

如何在数据库的某个对象库中创建数据项objectStore.createIndex('title', 'title', {unique: false});

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
request.onerror = function(){};
request.onsuccess = function(){};
request.onupgradeneeded = function(e){
// 从事件目标e.target中获取对现有数据库的引用,即request对象
let db = e.target.result;

// 这个objectStore就像一个table一样存放记录
// 在我们打开的数据库中新建一个对象库,类似于传统数据库系统中的单个表
// id用于唯一标志一条记录
let objectStore = db.createObjectStore('notes', {keyPath:'id', autoIncrement: true});
// 定义这个table中会存放什么样的数据项,创建了两个索引
objectStore.createIndex('title', 'title', {unique: false});
objectStore.createIndex('body', 'body', {unique: false});

console.log('Database setup complete');
}

对数据库进行增加数据项的操作

首先,明确要增加进去的数据项newItem

需要引入一个读写数据库的方法的引用let transaction = db.transaction(['notes'], 'readwrite');

然后,锁定目标到数据库内的某一个table/对象库。

最后,基于该对象库调用obJectStore.add(newItem)对象。

1
2
3
4
let newItem = {title: titleInput.value, body: bodyInput.value};
let transaction = db.transaction(['notes'], 'readwrite');
let objectStore = transaction.objectStore('notes');
var request = objectStore.add(newItem);

该引用成功完成后应当增加transaction.oncomplete监听函数。该引用失败后应当增加transaction.onerror监听函数。


数据库更新同步到客户端网页DOM中

首先,需要明确原网页DOM中所有数据库的原先内容都要被移除,否则会出现叠加。

1
2
3
while(list.firstChild){
list.removeChild(list.firstChild);
}

其次,获得对数据库中的特定对象库table的引用。let objectStore = db.transaction('notes').objectStore('notes');

最后,最重要的是获得对对象库中的所有条目的引用。条目的各项index都可以通过cursor.value的属性获得,但是需要注意cursor.value.id是连接DOM中的条目和数据库中条目的通道。删除了的DOM中条目如何对应到数据库中去删除?这就需要id来分辨。

1
2
3
4
5
6
7
8
objectStore.openCursor().onsuccess = function(e){
let cursor = e.target.result;
// cursor.value就相当于一个table中的一个条目
// cursor.value.title/bod就相当于一个条目中的一个index

// 当用户操作删除这个的时候,便于我们在数据库中寻找对应的条目删除
listItem.setAttribute('data-note-id', cursor.value.id);
}

DOM树的数据项删除同步到客户端数据库

首先,需要获得删除数据项在数据库中的唯一id。

1
let noteId = Number(e.target.parentNode.getAttribute('data-note-id'));

其次,打开数据库的特定对象库。

1
2
let transaction = db.transaction(['notes'], 'readwrite');
let objectStore = transaction.objectStore('notes');

最后,在对象库上传入id参数执行delte操作。

1
let request = objectStore.delete(noteId);

对该请求设置一个事件监听操作,一旦在数据库中删除成功,这种变化需要反映到DOM树中去。

1
2
3
4
5
6
7
8
9
10
11
request.oncomplete = function(){
e.target.parentNode.parentNode.removeChild(e.target.parentNode);
console.log('Note ${noteId} deleted.');
// 如是删除到最后都是空列表该怎么做
// 这是没有listitem的默认显示
if(!list.firstChild){
let listItem = document.createElement('li');
listItem.textContent = 'No notes stored.';
list.appendChild(listItem);
}
};
  • 本文标题:JavaScript基础速记
  • 本文作者:徐徐
  • 创建时间:2020-11-21 13:34:05
  • 本文链接:https://machacroissant.github.io/2020/11/21/learn-js/
  • 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
 评论