【JS】阮一峰js教程总结

时间:2024-10-04 22:10:03

阮一峰js教程总结

  • 1、
      • js三种方法确定值得类型
      • null和undefined的区别
      • 0.1与0.3问题
      • NaN
      • 数值相关的全局方法
      • 字符串
      • Base64 转码
      • 对象
      • 函数
      • 闭包
      • 数组
      • 数据类型转换
      • 错误处理机制
      • Object 对象
      • 对象的拷贝
      • 对象的
      • ()
      • 随机数生成
      • JSON 对象
  • 面向对象编程!
      • 对象是什么
      • 对象生成
      • 原型链
      • constructor属性
      • 构造函数的继承
      • 获取原型对象的三种方法
      • 对象的拷贝
      • 异步操作
      • 任务队列和事件循环
      • 异步操作的模式
      • 异步操作的流程控制
      • Promise对象
      • 微任务
  • DOM
      • Document 节点
      • Element 节点
      • EventTarget 接口
      • 事件模型
      • this 的指向
      • 事件的传播
      • 事件的代理
  • Event 对象
  • 网页状态事件
      • script
      • 多个script
      • defer
      • async
      • 脚本的动态加载

阮一峰js教程总结

1、

js三种方法确定值得类型

  • typeof
  • instanceof

null和undefined的区别

区别是这样的:

  • null 是一个表示“空”的对象,转为数值时 为 0 ;

  • -undefined 是一个表示”此处无定义”的原始值,转为数值时为 NaN 。

  • 注意,空数组( [] )和空对象( {} )对应的布尔值,都是 true 。

0.1与0.3问题

0.1 + 0.2 === 0.3 2. // false 
0.3 / 0.1 5. // 2.9999999999999996 
(0.3 - 0.2) === (0.2 - 0.1) 8. // false
  • 1
  • 2
  • 3

NaN

  • “非数字”( NaN ),typeof NaN // ‘number’
  • NaN 不等于任何值,包括它本身。
  • NaN 在布尔运算时被当作 false 。
  • NaN 与任何数(包括它自己)的运算,得到的都是 NaN 。
  • isNaN()方法可以用来判断一个值是否为 NaN 。isNaN(NaN) // true 2. isNaN(123) // false

isNaN 只对数值有效,如果传入其他值,会被先转成数值。比如,传入字符串的时候,字符串 会被先转成 NaN ,所以最后返回 true ,这一点要特别引起注意。也就是 说, isNaN 为 true 的值,有可能不是 NaN ,而是一个字符串

对于空数组和只有一个数值成员的数组, isNaN 返回 false 。

isNaN([]) // false 
isNaN([123]) // false 
isNaN(['123']) // false
  • 1
  • 2
  • 3
'
运行

数值相关的全局方法

  • parseInt()
  • parseInt(‘1000’, 2) // 8

字符串

Base64 转码

Base64 就是一种编码方法,可以将任意值转成 0~9、A~Z、a-z、 + 和 / 这64个字符组 成的可打印字符

对象

  • 对象的所有键名都是字符串(ES6 又引入了 Symbol 值也可以作为键名)
  • 如果键名是数值,会被自动转为字符串。
  • 查看一个对象本身的所有属性,可以使用 方法, (obj);// [‘key1’, ‘key2’]
  • delete 命令用于删除对象的属性,删除成功后返回 true 。能删除对象本身的属性,无法删除继承的属性
  • 属性是否存在:in 运算符,自身和原型链上的都算
  • 对象的 hasOwnProperty 方法判断一下,是否为对象自身的属性。
  • for…in 循环用来遍历一个对象的全部属性。,对象所有可遍历(enumerable)的属性,会跳过不可遍历的属性。它不仅遍历对象自身的属性,还遍历继承的属性。

函数

  • 函数参数如果是原始类型的值(数值、字符串、布尔值),传递方式是传值传递(passes by value)。这意味着,在函数体内修改参数值,不会影响到函数外部。
  • 如果函数参数是复合类型的值(数组、对象、其他函数),传递方式是传址传递(pass by reference)。也就是说,传入函数的原始值的地址,因此在函数内部修改参数,将会影响到原始值。
  • arguments 对象;

arguments 对象包含了函数运行时的所有参数, arguments[0] 就是第一个参 数, arguments[1] 就是第二个参数,以此类推。这个对象只有在函数体内部,才可以使用。

需要注意的是,虽然 arguments 很像数组,但它是一个对象。数组专有的方法(比 如 slice 和 forEach ),不能在 arguments 对象上直接使用。

arguments 对象使用数组方法,真正的解决方法是将 arguments 转为真正的数组。下面 是两种常用的转换方法: slice 方法和逐一填入新数组

arguments 对象带有一个 callee 属性,返回它所对应的原函数

闭包

function f1(){
var n = 999;
function f2() {
	console.log(n);//999
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
'
运行

函数 f2 就在函数 f1 内部,这时 f1 内部的所有局部变量,对 f2 都是可见的。 但是反过来就不行, f2 内部的局部变量,对 f1 就是不可见的。这就是 JavaScript 语言特有 的”链式作用域”结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。所以,父 对象的所有变量,对子对象都是可见的,反之则不成立。 既然 f2 可以读取 f1 的局部变量,那么只要把 f2 作为返回值,我们不就可以在 f1 外部读取 它的内部变量了吗!

 function f1() { 
  var n = 999; 
   function f2() { 
   console.log(n); 
   } 
    return f2; 
    } 
   var result = f1();
   result(); // 999
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
'
运行

闭包就是函数 f2 ,即能够读取其他函数内部变量的函数。

只有函数 内部的子函数才能读取内部变量,因此可以把闭包简单理解成“定义在一个函数内部的函数”。闭包最大 的特点,就是它可以“记住”诞生的环境,比如 f2 记住了它诞生的环境 f1 ,所以从 f2 可以得 到 f1 的内部变量。在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁

闭包的最大用处有两个,

  • 一个是可以读取函数内部的变量,
  • 另一个就是让这些变量始终保持在内存中, 即闭包可以使得它诞生环境一直存在。

是封装对象的私有属性和私有方法。

注意,外层函数每次运行,都会生成一个新的闭包,而这个闭包又会保留外层函数的内部变量,所以内 存消耗很大。因此不能滥用闭包,否则会造成网页的性能问题。

eval
eval 命令接受一个字符串作为参数,并将这个字符串当作语句执行。
eval(‘var a = 1;’); a // 1
eval 没有自己的作用域,都在当前作用域内执行,因此可能会修改当前作用域的变量的值,造成安 全问题。
JavaScript 规定,如果使用严格模式, eval 内部声明的变量,不会影响到 外部作用域

数组

  • 本质上,数组属于一种特殊的对象。 typeof 运算符会返回数组的类型是 object 。
  • length 属性是可写的。如果人为设置一个小于当前成员个数的值,该数组的成员数量会自动减少 到 length 设置的值。
  • 检查某个键名是否存在的运算符 in ,适用于对象,也适用于数组。
  • for…in 循环不仅可以遍历对象,也可以遍历数组,毕竟数组只是一种特殊对象。
  • 数组的 forEach 方法,也可以用来遍历数组
  • 使用 delete 命令删除一个数组成员,会形成空位,并且不会影响 length 属性。

类似数组的对象

“类似数组的对象”的根本特征,就是具有 length 属性。只要有 length 属性,就可以认为这个对 象类似于数组。但是有一个问题,这种 length 属性不是动态值,不会随着成员的变化而变化。

典型的“类似数组的对象”是函数的 arguments 对象,以及大多数 DOM 元素集,还有字符串

典型的“类似数组的对象”是函数的 arguments 对象,以及大多数 DOM 元素集,还有字符串
var arr = (arrayLike);
“类似数组的对象”还有一个办法可以使用数组的方法,就是通过 call() 把数 组的方法放到对象上面。

function print(value, index) { 
 console.log(index + ' : ' + value);  }
  Array.prototype.forEach.call(arrayLike, print);
  • 1
  • 2
  • 3

注意,这种方法比直接使用数组原生的 forEach 要慢,所以最好还是先将“类似数组的对象”转为真 正的数组,然后再直接调用数组的 forEach 方法。

数据类型转换

错误处理机制

JavaScript 解析或运行时,一旦发生错误,引擎就会抛出一个错误对象。

JavaScript 原生提 供 Error 构造函数,所有抛出的错误都是这个构造函数的实例
var err = new Error(‘出错了’);
// “出错了”

Object 对象

JavaScript 的所有其他对象都继承自 Object 对象,即那些对象都是 Object 的实例。
Object 对象的原生方法分成两类: Object 本身的方法与 Object 的实例方法。

  • Object 的静态方法,是指部署在 Object 对象自身的方法。

方法和 方法都用来遍历对象的属性。

  • Object 的实例方法,
  • () :判断某个属性是否为当前对象自身的属性,还是继承 自原型对象的属性
  • () :判断当前对象是否为另一个对象的原型。
  • () :判断某个属性是否可枚举。

对象的拷贝

对象的

  • () 禁止新增删除或修改 强
  • () 只是禁止新增或删除属性 中
  • ()一个对象无法再添加新的属性 弱

的可写性有一个漏洞:可以通过改变原型对象,来为对象增加属性

上面代码中,对象 obj 本身不能新增属性,但是可以在它的原型对象上新增属性,就依然能够 在 obj 上读到。

把 obj 的原型也冻结住。

var obj = new Object();
Object.preventExtensions(obj); 
var proto = Object.getPrototypeOf(obj); 
Object.preventExtensions(proto); 
proto.t = 'hello'; 
obj.t // undefined
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
'
运行

,如果属性值是对象,上面这些方法只能冻结属性指向的对象,而不能冻结对象本身的 内容。

()

随机数生成

function getRandomArbitrary(min, max) { 
return Math.random() * (max - min) + min; 3. 
}
getRandomArbitrary(1.5, 6.5)
  • 1
  • 2
  • 3
  • 4
'
运行

JSON 对象

每个 JSON 对象就是一个值,可能是一个数组或对象,也可能是一个原始类型的值。总之,只能是一 个值,不能是两个或更多的值。

JSON对象是 JavaScript 的原生对象,用来处理 JSON 格式数据。它有两个静态方 法: () 和 () 。

面向对象编程!

对象是什么

对象是单个实物的抽象。
对象是一个容器,封装了属性(property)和方法(method)

对象生成

前面说过,对象是单个实物的抽象。通常需要一个模板,表 示某一类实物的共同特征,然后对象根据这个模板生成。

  • 单个对象生成 对象字面量、new Object()
  • 多个对象 工厂模式、构造函数、原型模式、组合模式、寄生构造模式、稳妥模式

new 命令的作用,就是执行构造函数,返回一个实例对象

new一个对象时候,有以下四步

  • 生成一个空对象
  • 将作用域绑定到新对象上(this指向新对象)
  • 执行构造函数(给属性上绑定方法)
  • 返回对象

构造函数作为模板,可以生成实例对象。但是,有时拿不到构造函数,只能拿到一个现有的对象。我们 希望以这个现有的对象作为模板,生成新的实例对象,这时就可以使用 () 方法。

原型链

  • “原型链”(prototype chain):对象到原型,再到原型的原型…… 如果一层层地上溯,所有对象的原型最终都可以上溯到 ,即 Object 构造函数 的 prototype 属性。也就是说,所有对象都继承了 的属性。这就是所有对象 都有 valueOf 和 toString 方法的原因,因为这是从 继承的。

  • 那么, 对象有没有它的原型呢?回答是 的原型 是 null 。 null 没有任何属性和方法,也没有自己的原型。因此,原型链的尽头就是 null 。

constructor属性

prototype 对象有一个 constructor 属性,默认指向 prototype 对象所在的构造函数。

constructor 属性的作用是,可以得知某个实例对象,到底是哪一个构造函数产生的。

构造函数的继承

JavaScript 不提供多重继承功能,即不允许一个对象同时继承多个对象。但是,可以通过变通方 法,实现这个功能。

获取原型对象的三种方法

  • obj.proto

对象的拷贝

确保拷贝后的对象,与原对象具有同样的原型。

确保拷贝后的对象,与原对象具有同样的实例属性

异步操作

单线程模型指的是,JavaScript 只在一个线程上运行。。也就是说,JavaScript 同时只能执行一个 任务,其他任务都必须在后面排队等待

如果 JavaScript 同时有两个线程,一个线程在网页 DOM 节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?

“事件循环”机制

如 果用得好,JavaScript 程序是不会出现堵塞的,这就是为什么 Node 可以用很少的资源,应付大流 量访问的原因

  • 同步任务

同步任务是那些没有被引擎挂起、在主线程上排队执行的任务。只有前一个任务执行完毕,才能执行后 一个任务。

  • 异步任务
    异步任务是那些被引擎放在一边,不进入主线程、而进入任务队列的任务。只有引擎认为某个异步任务 可以执行了(比如 Ajax 操作从服务器得到了结果),该任务(采用回调函数的形式)才会进入主线 程执行。排在异步任务后面的代码,不用等待异步任务结束会马上运行,也就是说,异步任务不具 有“堵塞”效应

任务队列和事件循环

,除了一个正在运行的主线程,引擎还提供一个任务队列(task queue),里 面是各种需要当前程序处理的异步任务

异步任务的写法通常是回调函数。一旦异步任务重新进入主线程,就会执行对应的回调函数。如果一个 异步任务没有回调函数,就不会进入任务队列,也就是说,不会重新进入主线程,因为没有用回调函数 指定下一步的操作

JavaScript 引擎怎么知道异步任务有没有结果,能不能进入主线程呢?
答案就是引擎在不停地检 查,一遍又一遍,只要同步任务执行完了,引擎就会去检查那些挂起来的异步任务,是不是可以进入主 线程了。这种循环检查的机制,就叫做事件循环(Event Loop)。

异步操作的模式

  • 回调函数
    回调函数的优点是简单、容易理解和实现,缺点是不利于代码的阅读和维护,各个部分之间高度耦合) (coupling),使得程序结构混乱、流程难以追踪(尤其是多个回调函数嵌套的情况),而且每个任 务只能指定一个回调函数。

  • 事件监听
    -发布/订阅 事件完全可以理解成“信号”,如果存在一个“信号中心”,某个任务执行完成,就向信号中心“发 布”(publish)一个信号,其他任务可以向信号中心“订阅”(subscribe)这个信号,从而知道什么 时候自己可以开始执行。这就叫做”发布/订阅模式”(publish-subscribe pattern),又称“观察 者模式”(observer pattern)。

异步操作的流程控制

如果有多个异步操作,就存在一个流程控制的问题:如何确定异步操作执行的顺序,以及如何保证遵守 这种顺序。

Promise对象

首先,Promise 是一个对象,也是一个构造函数。

实例,使用Promise完成图片加载

var preloadImage = function(path) {
return new Promise(function (resolve,reject){
	var image = new Image();
	image.onload = resolve;
	image.onerror = reject;
	image.src = path;});
	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
'
运行

上面代码中, image 是一个图片对象的实例。它有两个事件监听属性, onload 属性在图片加载成 功后调用, onerror 属性在加载失败调用。

而且,Promise 还有一个传统写法没有的好处:它的状态一旦改变,无论何时查询,都能得到这个状 态。这意味着,无论何时为 Promise 实例添加回调函数,该函数都能正确执行。所以,你不用担心是 否错过了某个事件或信号。如果是传统写法,通过监听事件来执行回调函数,一旦错过了事件,再添加 回调函数是不会执行的。

微任务

DOM

DOM 是 JavaScript 操作网页的接口,全称为“文档对象模型”(Document Object Model)。它 的作用是将网页转为一个 JavaScript 对象,从而可以用脚本进行各种操作(比如增删内容)。

浏览器会根据 DOM 模型,将结构化文档(比如 HTML 和 XML)解析成一系列的节点,再由这些节点 组成一个树状结构(DOM Tree)。所有的节点和最终的树状结构,都有规范的对外接口。

第一个是文档类型节点( <!doctype html> ),

第二个是 HTML 网页的 顶层容器标签

三种层级关系

  • 父节点关系(parentNode):直接的那个上级节点
  • 子节点关系(childNodes):直接的下级节点
  • 同级节点关系(sibling):拥有同一个父节点的节点

DOM 提供操作接口,用来获取这三种关系的节点。比如,子节点接口包括 firstChild (第一个子节 点)和 lastChild (最后一个子节点)等属性,同级节点接口包括 nextSibling (紧邻在后的那 个同级节点)和 previousSibling (紧邻在前的那个同级节点)属性。

Document 节点

Location 对象是浏览器提供的原生对象,提供 URL 相关的信息和操作方法。通 过 和 属性,可以拿到这个对象。

Element 节点

Element 节点对象对应网页的 HTML 元素。每一个 HTML 元素,在 DOM 树上都会转化成一 个 Element 节点对象(以下简称元素节点)。

EventTarget 接口

addEventListener :绑定事件的监听函数
removeEventListener :移除事件的监听函数
dispatchEvent :触发事件

function hello() { 
 console.log('Hello world'); 
  } 
 var button = document.getElementById('btn'); 
  button.addEventListener('click', hello, false);
  • 1
  • 2
  • 3
  • 4
  • 5

事件模型

监听函数

浏览器的事件模型,就是通过监听函数(listener)对事件做出反应。事件发生后,浏览器监听到了 这个事件,就会执行对应的监听函数。这是事件驱动编程模式(event-driven)的主要编程方式。

有三种方法绑定事件监听函数
HTML 的 on- 属性

元素节点的事件属性

window.onload = doSomething; 
div.onclick = function (event) {
console.log('触发事件'); 
   };
  • 1
  • 2
  • 3
  • 4

()
所有 DOM 节点实例都有 addEventListener 方法,用来为该节点定义事件的监听函数。

this 的指向

监听函数内部的 this 指向触发事件的那个元素节点。

事件的传播

一个事件发生后,会在子元素和父元素之间传播(propagation)。这种传播分成三个阶段。

  • 第一阶段:从 window 对象传导到目标节点(上层传到底层),称为“捕获阶段”(capture phase)。
  • 第二阶段:在目标节点上触发,称为“目标阶段”(target phase)。
  • 第三阶段:从目标节点传导回 window 对象(从底层传回上层),称为“冒泡阶段”(bubbling phase)。

事件的代理

由于事件会在冒泡阶段向上传播到父节点,因此可以把子节点的监听函数定义在父节点上,由父节点的 监听函数统一处理多个子元素的事件。这种方法叫做事件的代理(delegation)

var ul = document.querySelector('ul'); 
 ul.addEventListener('click', function (event) { 
  if (event.target.tagName.toLowerCase() === 'li') { 
   // some code 
   } 
    });
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

Event 对象

事件发生以后,会产生一个事件对象,作为参数传给监听函数。浏览器原生提供一个 Event 对象,所 有的事件都是这个对象的实例,或者说继承了 对象。

Event 对象本身就是一个构造函数,可以用来生成新的实例。

()

() ,

因此,任意时点都有两个与事 件相关的节点,一个是事件的原始触发节点( ),另一个是事件当前正在通过的节点 ( )。前者通常是后者的后代节点

** 属性返回原始触发事件的那个节点,即事件最初发生的节点。**这个属性不会随着事件 的传播而改变。

网页状态事件

DOMContentLoaded 事件

网页下载并解析完成以后,浏览器就会在 document 对象上触发 DOMContentLoaded 事件。这 时,仅仅完成了网页的解析(整张页面的 DOM 生成了),所有外部资源(样式表、脚本、iframe 等 hashchange 事件 网页状态事件 DOMContentLoaded 事件 其他常见事件 - 580 - 本文档使用 书栈网 · 构建
等)可能还没有下载结束。也就是说,这个事件比 load 事件,发生时间早得多。

注意,网页的 JavaScript 脚本是同步执行的,脚本一旦发生堵塞,将推迟触 发 DOMContentLoaded 事件。

script

正常的网页加载流程是这样的。

  1. 浏览器一边下载 HTML 网页,一边开始解析。也就是说,不等到下载完,就开始解析。

  2. 解析过程中,浏览器发现

加载外部脚本时,浏览器会暂停页面渲染,等待脚本下载并执行完成后,再继续渲染。原因是 JavaScript 代码可以修改 DOM,所以必须把控制权让给它,否则会导致复杂的线程竞赛的问题

如果外部脚本加载时间很长(一直无法完成下载),那么浏览器就会一直等待脚本下载完成,造成网页 长时间失去响应,浏览器就会呈现“假死”状态,这被称为“阻塞效应”。

这样即使遇到脚 本失去响应,网页主体的渲染也已经完成了,用户至少可以看到内容,而不是面对一张空白的页面。如 果某些脚本代码非常重要,一定要放在页面头部的话,最好直接将代码写入页面,而不是连接外部脚本 文件,这样能缩短加载时间。

脚本文件都放在网页尾部加载,还有一个好处,在 DOM 结构生成之前就调用 DOM 节点, JavaScript 会报错,如果脚本都在网页尾部加载,就不存在这个问题,因为这时 DOM 肯定已经生 成了

一种解决方法是设定 DOMContentLoaded 事件的回调函数。

另一种解决方法是,使用

多个script

浏览器会同时并行下载 和 ,但是,执行时会保证先执行 ,然后再执 行 ,即使后者先下载完成,
也就是说,脚本的执行顺序由它们在页面中的出现顺序 决定,这是为了保证脚本之间的依赖关系不受到破坏。当然,加载这两个脚本都会产生“阻塞效应”,必 须等到它们都加载完成,浏览器才会继续页面渲染。

解析和执行 CSS,也会产生阻塞。Firefox 浏览器会等到脚本前面的所有样式表,都下载并解析完, 浏览器模型概述 - 596 - 本文档使用 书栈网 · 构建
再执行脚本;Webkit则是一旦发现脚本引用了样式,就会暂停执行脚本,等到样式表下载并解析完, 再恢复执行

defer

为了解决脚本文件下载阻塞网页渲染的问题,一个方法是对

只有等到 DOM 加载完成后,才会执行 和 。

defer 属性的运行流程如下

  1. 浏览器开始解析 HTML 网页
  2. 解析过程中,发现带有 defer 属性的

有了 defer 属性,浏览器下载脚本文件的时候,不会阻塞页面渲染。下载的脚本文件 在 DOMContentLoaded 事件触发前执行(即刚刚读取完 标签),而且可以保证执行顺序 就是它们在页面上出现的顺序。

对于内置而不是加载外部脚本的 script 标签,以及动态生成的 script 标签, defer 属性不起 作用。另外,使用 defer 加载的外部脚本不应该使用 方法。

async

解决“阻塞效应”的另一个方法是对

async 属性的作用是,使用另一个进程下载脚本,下载时不会阻塞渲染。

  1. 浏览器开始解析 HTML 网页。
  2. 解析过程中,发现带有 async 属性的 script 标签。 defer 属性 async 属性
  3. 浏览器继续往下解析 HTML 网页,同时并行下载

async 属性可以保证脚本下载的同时,浏览器继续渲染。需要注意的是,一旦采用这个属性,就无法 保证脚本的执行顺序。哪个脚本先下载结束,就先执行那个脚本

defer 属性和 async 属性到底应该使用哪一个?

一般来说,如果脚本之间没有依赖关系,就使用 async 属性,如果脚本之间有依赖关系,就使 用 defer 属性。如果同时使用 async 和 defer 属性,后者不起作用,浏览器行为 由 async 属性决定。

脚本的动态加载