备注:在摸鱼的时间里面,个人总结JavaScript一些自己比较容易遗忘的知识点,所以得记在本子上,方便自己复习。因为个人觉得基础很重要,基础扎实了,再去看一些框架的源码的话就游刃有余了。根据阅读JavaScript高级程序设计(第3版)来记录,如有不正确的地方,请留言提醒一下(请轻喷),如果jio得不错,点个赞鼓励一下,感谢各位掘友萌!
1、一个完整的JavaScript实现由三个不同的部分组成
- 核心(ECMAScript),这个核心规定了这门语言的组成部分(
语法、类型、语句、关键字、保留字、操作符、对象),提供核心语言功能。- 文档对象模型(DOM,Document Object Model),通过DOM创建的这个文档树形图,获得控制页面内容和结构的主动权,借助DOM提供的API,可以删除、添加、或修改任何节点,提供访问和操作网页内容的方法和接口。
- 浏览器对象模型(BOM,Browser Object Model),可以控制浏览器显示的页面以外的部分,弹出、移动、缩放、关闭浏览器窗口的功能;navigator对象、location对象、screen对象、对cookies、XMLHttpRequest的支持,提供与浏览器的方法和接口。
2、<script>标签的属性
async:可选。表示应该立即下载脚本,但不应妨碍页面中的其他操作,比如下载其他资源或等待加载其他脚本。只对外部脚本文件有效。charset:可选。表示通过src属性指定的代码的字符集。由于大多数浏览器会忽略它的值,因此这个属性很少有人用。defer:可选。表示脚本可以延迟到文档完全被解析和显示之后再执行。只对外部脚本文件有效。language:已废弃。src:可选。表示包含要执行代码的外部文件。type:可选。表示编写代码时用的脚本语言的内容类型。
3、<script>标签有两种使用方式
- 直接在页面中嵌入
JavaScript代码- 包含外部
JavaScript文件 因为HTML文件是从上到下的文档流加载的,也就意味着必须把所有JavaScript代码都下载、解析和解释完成后,才能开始渲染页面(页面在浏览器解析到<body>的起始标签时开始渲染)。对于需要很多JavaScript的页面,这会导致页面渲染的明显延迟,在此期间浏览器窗口完全空白,所以<script>标签最好是放<body>标签之后(除了特殊原因之外)。
4、标识符
- 第一个字符必须是一个
字母、下划线( _ )或者一个美元符号($); - 其他字符可以是
字母、下划线、美元符或数字。
按照惯例,EMCAScript标识符采用驼峰大小写格式,也就是第一个字母小写,剩下的每个有意义的单词的首字母大写。例如:firstSecond、myCar、doSomething。
5、数据类型
- 基本数据类型:
Undefined、String、Boolean、Null、Number- 复杂数据类型:
Object
6、typeof null 为什么返回Object?
Null类型是第二个只有一个值的数据类型,这个特殊的值是null。从逻辑角度来看,null值表示一个空对象指针,而这正是使用typeof操作符检测null值时是返回Object的原因。
7、引用类型复制
当从一个变量向另外一个变量复制引用类型的值时,同样也会将存储在变量对象中的值复制一份放到新变量分配的空间中。 不同的是,这个值的副本实际上是一个指针,而这个指针指向存储在堆中的一个对象。复制操作结束后,两个变量实际上将引用同一个对象。 因此,改变其中一个变量,就会影响另一个变量。这个就是区别浅拷贝和深拷贝的重要依据,请参考juejin.cn/post/684490…
8、关于执行环境的几点总结
- 执行环境有全局执行环境(也称为全局环境)和函数执行环境之分;
- 每次进入一个新执行环境,都会创建一个用于搜索变量和函数的作用域链(作用域用途:保证对执行环境有权访问的所有变量和函数的有序访问);
- 函数的局部环境不仅有权访问函数作用域中的变量,而且有权访问其包含(父)环境,乃至全局变量;
- 全局环境只能访问在全局环境中定义的变量和函数,而不能直接访问局部环境中的任何数据;
- 变量的执行环境有助于确定应该何时释放内存。
9、数组array经常用到的方法
slice(),能够基于当前数组中的一个或多个项创建一个新数组。slice()方法可以接受一个或者两个参数,即要返回项的起始和结束位置。只有一个参数的情况下,slice()方法返回从该参数指定位置开始到当前数组末尾的所有项。如果有两个参数,该方法返回起始和结束位置之前的项。如果参数中有一个负数,则用数组长度加上该数来确定相应的位置,例如,在一个包含5项的述责上调用slice(-2, -1)与调用slice(3, 4)得到的结果相同。如果结束位置小于起始位置,则返回空数组。注意,slice()方法不会影响原始数组。splice(),主要用途是向数组中部插入项,但是用这种方的方式则有如下3种方式:
- 删除:可以删除任意数量的项,只需指定2个参数:要删除的第一项的位置和要删除的项数。例如,
splice(0, 2)会删除数组中的前两项。- 插入:可以指定位置插入任意数量的项,只需提供3个参数:起始位置、0(要删除的项数)和要插入的项。如果要插入多个项插入多个项,可以再传入第四、第五,移至任意多个项。例如,
splice(2, 2 , "red" , "green")会从当前数组的位置2开始插入字符"red"和"green"。- 替换: 可以向指定位置插入任意数量的项,且同时删除任意数量的项,只需指定3个参数;起始位置、要删除的项数要插入的任意数量的项。插入的项不必与删除的项数相等。例如,
splice(2, 1, "red", "green")会删除当前数组位置2的项,然后再从2开始插入字符串"red"和"green"。splice(),方法始终都会返回一个数组,该数组中包含从原始数组中删除的项(如果没有删除任何项,则返回一个空数组)。indexOf()和lastIndexOf()。这两个方法都接收两个参数:要查找的项(可选的)表示查找起点位置的索引,在没有找到的情况下返回-1。其中indexOf()方法从数组的开头(位置0)开始向后查找,lastIndexOf()方法则从数组的末尾开始向前查找。every()和some()很相似,它们都用于查询数组中的项是否满足某个条件。对every()来说,传入的函数必须对每一项都返回true,这个方法才返回true;否则,它就会返回false。而some()方法则是只要传入的函数对数组中的某一项返回true,就会返回true。
var numbers = [1,2,3,4,5,4,3,2,1];
var everyResult = numbers.every(function(item, index, array){
return (item > 2);
});
console.log(everyResult); // false
var someResult = numbers.some(function(item, index, array){
return (item > 2);
});
console.log(someResult); // true
filter(),它利用指定的函数确定是否在返回的数组中包含的某一项。例如要返回一个所有数值都大于2的数组,可以使用以下代码。
var numbers = [1,2,3,4,5,4,3,2,1];
var filterResult = numbers.filter(function(item, index, array){
return (item > 2);
});
console.log(filterResult); // [3,4,5,4,3]
map(),也是返回一个数组,而这个数组的每一项都是在原始数组中的对应上运行传入函数的结果。例如,可以给数组中的每一项乘以2,然后返回这些乘积组成的数组,如下所示。
var numbers = [1,2,3,4,5,4,3,2,1];
var mapResult = numbers.map(function(item, index, array){
return item * 2;
});
console.log(mapResult); // [2,4,6,8,10,8,6,4,2]
forEach(),它只是对数组中的每一项运行传入的函数。这个方法没有返回值,本质上与使用for循环迭代数组一样。来看一个例子。
var numbers = [1,2,3,4,5,4,3,2,1];
numbers.forEach(function(item, index, array){
//执行某些操作
});
10.引用类型有Object、Array、Date、RegExp、Function。
11.函数声明与函数表达式
解析器在向执行环境中加载数据时,对函数声明和函数表达式并非一视同仁。解析器会率先读取函数声明,并使其在执行任何代码之前可用(可以访问);至于函数表达式,则必须等到解析器执行到它所在的代码行,才会真正被解释执行。
console.log(sum(10, 10));
function sum(num1, num2){
retrun num1 + num2;
}
以上代码完全可以正常运行。因为在代码开始执行之前,解析器就已经通过一个名为 函数声明提升(function declaration hoisting) 的过程,读取并将函数声明添加到执行环境中。对代码求值时,JavaScript引擎在第一遍会声明函数并将它们放在源代码树的顶部。所以,及时声明函数的代码在调用它的代码后面,JavaScript引擎也能把函数声明提升到顶部。
12. 函数表达式
定义函数方式有两种:一种是函数声明,另一种是函数表达式。 关于函数声明,它的一个重要特征就是函数声明提升(function declaration hoisting),意思是在执行代码之前会先读取函数声明。这个意味着可以把函数声明放在调用它的语句后面。例如下面这个例子:
sayHi(); function sayHi(){ console.log("Hi!"); }
13. 函数属性和方法
函数是对象,因此函数也有属性和方法。 每个函数都包含两个属性:
length和prototype。
length属性表示函数希望接受的命名参数的个数。prototype属性是保存它们所有实例方法。在创建自定义引用类型以及实现继承时,prototype属性的作用极为重要。
14. 介绍一下ECMAScript语言中最强大的一个方法:eval()
eval()就像是一个完整的ECMAScript解析器,它只接收一个参数,及执行的ECMAScript或字符串。
- 当解析器发现代码调用
eval()方法时,它会将传入的参数当作实际ECMAScript语句来解析,然后就把执行结果插入到原位置。通过eval()执行的代码被认为是被包含该次调用的执行环境的一部分,因此被执行的代码具有与该执行环境相同的作用的作用域链。这意味着通过eval()执行的代码可以引用在包含环境中定义的变量。- 在
eval()中创建的任何变量或函数都不会被提升,因为在解析代码的时候,它们被包含在一个字符中;它们只在eval()执行的时候创建。- 注意:能够解释代码字符串的能力非常强大,但也非常危险。因此在使用
eval()时必须极为谨慎,特别是在用它执行用户输入数据的情况下。否则,可能会有恶意用户输入威胁你的站点或应用程序安全的代码(即所谓的代码注入)。
15. 对象的几个属性:
- [[Configurable]]:表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性。默认值为
true。- [[Enumerable]]:表示能否通过for-in循环返回属性。默认值为
true。- [[Writable]]:表示能否修改属性的值。默认值为
true。- [[Value]]:包含这个属性的数据值。读取属性值的时候,从这个位置读;写入属性值的时候把新值保存在这个位置。默认值为
true。- [[Get]]: 在读取属性时调用的函数。默认值为
undefined。- [[Set]]: 在写入属性时调用的函数。默认值为
undefined。 要修改属性默认的特性,必须使用Object.defineProperty()方法。这个方法接收三个参数:属性所在的对象、属性的名字和一个描述符对象。其中,描述符对象的属性必须是:configurable、enumerable、writable和value。设置其中的一个或者多个值,可以修改对应的特性值。
16.创建对象的几种常用的模式
(1)工厂模式,用函数来封装以特定接口创建对象的细节,如下面的例子所示。
function createPerson(name, age, job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function() {
console.log(this.name);
}
return o;
}
var person1 = createPerson("Jondan", 57, "Basketball Player");
var person2 = createPerson("Messi", 33, "Football Player");
(2)构造函数模式,例如,可以使用前面的例子重写,如下。
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function() {
console.log(this.name);
}
}
var person1 = new Person("Jondan", 57, "Basketball Player");
var person2 = new Person("Messi", 33, "Football Player");
在这个例子中,
Person()函数取代了createPerson()函数。Person()中的代码除了与createPerson()中相同的部分外,还存在以下不同之处:
- 没有显式地创建对象;
- 直接将属性和方法赋给this对象;
- 没有return语句。 使用
new创建实例会经历以下4个步骤:
- 创建一个现对象;
- 将构造函数的作用域赋给新对象(因此this就指向了这个新对象);
- 执行构造函数中的代码(为这个新对象添加属性);
- 返回新对象。
此外,还应该注意到函数名Person使用的是大写字母P。 按照惯例,构造函数始终都应该以一个大写字母开头,而非构造函数则应该以一个小写字母开头。这个做法借鉴其它面向对象语言,主要是为了区别于ECMAScript中的其他函数;因为构造函数本身也是函数,只不过可以用来创建对象而已。
(3)原型模式,我们创建的每一个函数都有一个prototype(原型) 属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以有特定类型的所有实例共享的属性和方法。如果按照字面意思来理解,那么prototype就是通过调用构造函数而创建的那个对象实例的原型对象。使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法。
(4)组合式(构造函数模式和原型模式),用构造函数定义实例属性,用原型定义方法和共享属性。
17. 谈一谈原型链
每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。那么,假如我们让原型对象等于另一个类型的实例,此时的原型对象将包含一个指向另一个原型的指针,相应地,另一个原型也包含着一个指向另一个构造函数的指针。 假如另一个原型又是另一个类型的实例,那么上述关系依然成立,如此层层递进,就构成了实例与原型的链条。这就是所谓原型链的基本概念。【这个是JavaScript高级程序设计(第3版)】原版的阐述。
__proto__属性的作用就是当访问一个对象的属性时,如果该对象内部不存在这个属性,那么就会去它的__proto__属性所指向的那个对象(父对象)里找,一直找,直到__proto__属性的终点null,再往上找就相当于在null上取值,会报错。通过__proto__属性将对象连接起来的这条链路即我们所谓的原型链。
18. 几种继承方法
- 原型链(原型链是实现继承的主要方式)
- 借用构造函数
- 组合继承(使用原型链继承原型属性和方法,使用借用构造继承实例属性)
- 原型式继承
- 寄生组合式继承(通过借用构造函数继承属性,通过原型链混成的方式继承方法) ---最理想的继承范式
19. 递归
递归函数是在一个函数通过名字调用自身的情况构成的,如下所示:
function factorial(num){
if(num <= 1){
return 1;
}
else {
return num * factorial(num - 1); // return num * arguments.callee(num - 1);
}
}
这是一个经典的递归阶乘函数。注意,在编写递归函数时,使用arguments.callee总比使用函数名更保险。
20. 闭包
当在函数内部定义了其他函数时,就创建了闭包。闭包有权访问包含函数内部的所有变量,原理如下:
- 闭包是指有权访问另一个函数作用域的变量的函数。 创建闭包的常见方式,就是在一个函数内部创建另一个函数。
- 但需要注意,由于闭包携带包含它的函数的作用域,因此会比其他函数占用更多的内存。过度使用闭包可能会导致内存占用过多,所以建议大家要慎重使用闭包。 请参照一下阮一峰老师的文章
- 在后台执行环境中,闭包的作用域链包含着它自己的作用域、包含函数的作用域和全局作用域。通常,函数的作用域及其所有变量都会在函数执行结束后被销毁。但是,当函数返回了一个闭包时,这个函数的作用域将会一直存在内存中保存到闭包不存在为止。
- 使用闭包可以在JavaScript中模仿块级作用域(JavaScript本身没有块级作用域),要点如下。
- 创建并立即调用一个函数,这样既可以执行其中的代码,又不会在内存中留下对该函数的引用。
- 结果就是函数内部的所有变量都会被立即销毁——除非将某些变量赋值给了包含作用域(及外部作用域)中的变量。
闭包还可以用于在对象中创建私有变量,相关概念和要点如下:
- 即使JavaScript中没有正式的私有对象属性的概念,但可以使用闭包来实现公有方法,而通过公有方法可以访问在包含作用域中定义的变量。
- 有权访问私有变量的公有方法叫做特权方法。
- 可以使用构造函数模式、原型模式来实现自定义类型的特权方法,也可以使用模式模块、增强的模块模块来实现单例的特权方法。
21. 关于this对象
我们知道,
this对象是在运行是基于函数的执行环境绑定的:在全局函数中,this等于window,而当函数被作为某个对象的方法调用时,this等于那个对象。不过,匿名函数的执行环境具有全局性,因此其this对象通常指window。更详细的讲解请参照阮一峰老师的文章
22. 关于DOM里面的NodeList
每个节点都有一个
childNodes属性,其中保存着一个NodeList对象。NodeList是一种类数组对象,用于保存一组有序的节点,可以通过位置来访问这些节点。请注意,虽然可以通过方括号来访问NodeList的值,而且这个对象也有length属性,但它并不是Array的实例。
23. Dom将HTML和XML文档形象地看作一个层次化的节点树,可以使用JavaScript来操作这个节点树,进而改变底层文档的外观和结构。
DOM由各种节点构成,简要总结如下:
- 最基本的节点类型是
Node,用于抽象的表示文档中一个独立的部分;所有其它类型都继承自Node。Document类型表示整个文档,是一组分层节点的根节点。在JavaScript中,document对象是Document的一个实例。使用document对象,有很多种方式可以查询和取得节点。Element节点表示文档中的所有HTML或XML元素,可以用来操作这些元素的内容和特性。- 另外还有一些节点类型,分别表示文本内容、注释、文档类型、CDATA区域和文档片段。
- 理解DOM的关键,就是理解DOM对性能的影响。DOM操作往往是JavaScript程序中开销最大的部分,而因访问NodeList导致的问题为最多。NodeList对象都是“动态的”,这就意味着每次访问NodeList对象,都会运行一次查询。有鉴于此,最好的办法就是尽量减少DOM操作。
24. DOM 扩展的选择器API
querySelector()方法接收一个CSS选择符,返回与该模式匹配的第一个元素,如果没有找到匹配的元素,返回null。请看下面的例子:
// 取得body元素
var body = document.querySelector("body");
// 取得ID为"myDiv"的元素
var myDiv = document.querySelector("#myDiv");
// 取得类名为"selected"的第一个元素
var selected = document.querySelector(".selected");
// 取得类名为"button"的第一个图像元素
var img = document.body.querySelector("img.button");
querySelectorAll()方法接收的参数与querySelector()方法一样,都是一个CSS选择符,但返回的是所有匹配的元素而不仅仅是一个元素。这个方法返回的是一个NodeList的实例。如果没有找到匹配的元素,NodeList就是空的。与querySelector()类似,能够用querySelectorAll()方法的类型包括Document、DocumentFragment和Element。下面是几个例子。
// 取得某<div>中的所有<em>元素(类似于getElementsByTagName("em"))
var ems = document.getElementById("myDiv").querySelectorAll("em");
//取得累为"selected"的所有元素
var selecteds = document.querySelectorAll(".selected");
//取得所有<p>元素中所有<strong>元素
var strong = document.querySelectorAll("p strong");
getElementByClassName()方法接受一个参数,即一个包含一或多个类名的字符串,返回带有指定类的所有元素的NodeList。传入多各类名时,类名的先后顺序不重要。来看下面的例子。
//取得所有类中包含"username"和"current"的元素,类名的先后顺序无所谓
var allCurrentUsernames = document.getElementByClassName("username current");
//取得ID为"myDiv"的元素中带有类名"selected"的所有元素
var selected = document.getElementById("myDiv").getElementByClassName("selected");
25. 事件的概念
- JavaScript与HTML之间的交流是通过事件实现的。事件,就是文档或浏览器窗口中发生的一些特定的交互瞬间。
- 事件流描述的是从页面中接收事件的顺序。
26. 事件冒泡
事件冒泡,IE的事件流叫事件冒泡(event bubbling),即事件开始时由最具体的元素(文档中嵌套层次最深的那个节点)接收,然后逐级向上传播到较为不具体的节点(文档)。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>this</title>
</head>
<body>
<div class="myDiv">Click me</div>
</body>
</html>
/*
如果你单击了页面中的<div>元素,那么这个click事件会按照如下顺序传播:
<div> ----> <body> ----> <html> ----> document
也就是说,click事件首先在<div>元素上发生,而这个元素就是我们单击的元素。然后,click事件沿DOM树向上传播,在每一级节点上都会发 生,直至传播到document对象。
*/
如下图所示:
27. 事件捕获
事件捕获,Netscape Communicator团队提出的另一种事件流叫做事件捕获(event capturing)。事件捕获的思想是不太具体的节点应该更早接收到事件,而最具体的节点应该最后接收到事件。事件捕获的用意在于在事件到达目标之前捕获它。如果仍以前面的HTML页面作为演示事件捕获的例子,那么单击<div>元素就会以下列顺序触发click事件。
/*
document ----> <html> ----> <body> ----> <div>
在事件捕获过程中,document对象首先接收到click事件,然后事件沿DOM树依次向下,一直传播到事件的实际目标,即<div>元素。
*/
如下图所示:
28. DOM事件流包括三个阶段
DOM事件流包括三个阶段,
事件捕获阶段,处于目标阶段和事件冒泡阶段。首先发生的是事件捕获,为截获事件提供了机会。然后是实际的目标接收到事件。最后一个阶段是冒泡阶段,可以在这个阶段对事件做出响应。以前面简单的HTML页面为例,单击<div>元素会按照如下图所示的顺序触发事件。
29. 事件类型
- UI(User Interface,用户界面)事件,当用户与页面上的元素交互时触发;
- 焦点事件,当元素获得或失去焦点时触发;
- 鼠标事件,当用户通过鼠标在页面上执行操作时触发;
- 滚轮事件,当使用鼠标滚轮(或类似设备)时触发;
- 文本事件,当在文档中输入文本时触发;
- 键盘事件,当用户通过键盘在页面上执行操作时触发;
- 合成事件,当为IME(Input Method Editor,输入法编辑器)输入字符时触发;
- 变动(mutation)事件,当底层DOM结构发生变化时触发。
30. 事件委托
事件委托利用了事件冒泡,指定一个事件处理程序,就可以管理某一类型的所有事件。例如,click事件会一直冒泡到document层次。也就是说,我们可以为整个页面指定一个onclick事件处理程序,而不必给每个可单击的元素分别添加事件处理程序。
31. JSON
JSON是一个轻量级的数据格式(不是一种编程语言),可以简化表示复杂数据结构的工作量。 ECMAScript 5 定义了一个原生JSON对象,可以用来将对象序列化为JSON字符串或者JSON数据解析为JavaScript对象。
JSON.stringify()和JSON.parse()方法分别用来实现上述两项功能。
32. Ajax
- Ajax是无需刷新页面就能够从服务器取得数据的一种方法。 负责Ajax运作的核心对象是 XMLHttpRequest(XHR) 对象。
- 同源策略是对XHR的一个主要约束,它为通信设置了“
相同的域、相同的端口、相同的协议”这个限制。试图访问上述限制之外的资源,都会引发安全错误,除非采用被认可的跨域解决方案。这个解决方案叫做CORS(Cross-Origin Resource Sharing,跨源资源共享)。- Comet是对Ajax的进一步扩展,让服务器几乎能够实时地向客户端推送数据。
- SSE(Server-Sent Events,服务器发送事件) 是一种实现Comet交互Comet交互的浏览器API。
- Web Sockets是一种与服务器进行全双工、双向通信的信道。