JavaScript.xmind
JavaScript基础 - 图1

数据类型

数字Number

为什么0.1+0.2 解决方案
计算机原码 反码 补码
为什么计算机只认识0和1

变量命名 - var let const

var

var 声明可以在包含它的函数、模块、命名空间或全局作用域内部任何位置被访问,包含它的代码块对此没有什么影响。 有些人称此为 **var** 作用域或函数作用域

  1. for (var i = 0; i < 10; i++) {
  2. setTimeout(function() { console.log(i); }, 1000 * i);
  3. }
  4. function test() {
  5. for (var i = 0; i < 10; i++) {
  6. setTimeout(function() {
  7. console.log(new Date(), i);
  8. }, 1000 * i);
  9. }
  10. console.log("end", new Date(), i); //为方便后边演示,这里加了打印end标志
  11. }
  12. test();

看一下上面的输出是什么? 10个10,每隔1s,打印一个10
image.png

我们传给 setTimeout 的每一个函数表达式实际上都引用了相同作用域里的同一个 i

让我们花点时间思考一下这是为什么。 setTimeout 在若干毫秒后执行一个函数,事件循环机制中,i=0的时候,i++, 先把红任务setTimeout放到事件队列中,当主线程执行完后(end被打印时),timeout这些回调依次执行(队列:FIFO),此时for 循环结束后,i 的值为 10。 所以当函数被调用的时候,它会打印出 10,放到队列的时候 i=1,所以会间隔1s打印10!

解法1:IIFE

  1. for (var i = 0; i < 10; i++) {
  2. (function(i) {
  3. setTimeout(function() { console.log(i); }, 100 * i);
  4. })(i);
  5. }

IIFE当函数变成立即执行的函数表达式时,表达式中的变量不能从外部访问,也就是说,传入内部的i覆盖了for中i

解法2:函数调用按值传递

  1. const output = function (i) {
  2. setTimeout(function() {
  3. console.log(new Date, i);
  4. }, 1000);
  5. };
  6. function test(){
  7. for (var i = 0; i < 10; i++) {
  8. output(i); // 这里传过去的 i 值被复制,而不是引用
  9. }
  10. console.log(new Date, i);
  11. }

解法3:利用setTimeout第三个参数

  1. for (var i = 0; i < 10; i++) {
  2. setTimeout(function(i) {
  3. console.log(new Date, i);
  4. }, 1000, i);
  5. }

解法4:let
针对每次迭代都会创建这样一个新作用域

  1. for (let i = 0; i < 10 ; i++) {
  2. setTimeout(function() {console.log(i); }, 100 * i);
  3. }

let

和var区别:

  • let声明的变量具有块级作用域。块作用域变量在包含它们的块或 for 循环之外是不能访问的。
  • 暂时性死区:不能在被声明之前访问
  • let不能重复对相同变量进行声明

    const

    作用域规则和let一样,不可以重新赋值

call/apply/bind

一般情况下对象的方法被调用时,this指向该对象

  1. const person = {
  2. name: 'Guanqingchao',
  3. age: 22,
  4. introduce() {
  5. console.log(`Hello everyone! My name is ${this.name}. I'm ${this.age} years old.`);
  6. }
  7. };
  8. Hello everyone! My name is Guanqingchao. I'm 22 years old.

通常,我们想复用对象里的方法的时候,通过call、bind、apply可以改变方法中的this指向

  1. const person = {
  2. name: 'Guanqingchao',
  3. age: 22,
  4. introduce() {
  5. console.log(`Hello everyone! My name is ${this.name}. I'm ${this.age} years old.`);
  6. }
  7. };
  8. const myFriend = {
  9. name: 'Renjianqing',
  10. age: 22,
  11. };
  12. person.introduce.call(myFriend)

call

myFriend复用了introduce方法,通过call来改变this指向
通过上面代码我们可以看出 introduce 这个函数中的 this 指向被改成了 myFriend。Function.prototype.call 的函数签名是 fun.call(thisArg, arg1, arg2, ...)。第一个参数为调用函数时 this 的指向,随后的参数则作为函数的参数并调用,也就是 fn(arg1, arg2, …)。

apply

apply 和 call 的区别只有一个,就是它只有两个参数,而且第二个参数为调用函数时的参数构成的数组。函数签名:func.apply(thisArg, [argsArray])。如果不用给函数传参数,那么他俩就其实是完全一样的,需要传参数的时候注意它的应该将参数转换成数组形式
通常参数个数不确定的时候用apply

  1. function displayHobbies(...hobbies) {
  2. console.log(`${this.name} likes ${hobbies.join(', ')}.`);
  3. }
  4. // 下面两个等价
  5. displayHobbies.call({ name: 'Bob' }, 'swimming', 'basketball', 'anime'); // => // => Bob likes swimming, basketball, anime. 
  6. displayHobbies.apply({ name: 'Bob' }, ['swimming', 'basketball', 'anime']); // => Bob likes swimming, basketball, anime.

bind

bind 的函数签名是 func.bind(thisArg, arg1, arg2, ...)

返回一个原函数的拷贝,并拥有指定的 **this** 值和初始参数。

thisArg调用绑定函数时作为 this 参数传递给目标函数的值。 如果使用new运算符构造绑定函数,则忽略该值。当使用 bindsetTimeout 中创建一个函数(作为回调提供)时,作为 thisArg 传递的任何原始值都将转换为 object。如果 bind 函数的参数列表为空,或者thisArgnullundefined,执行作用域的 this 将被视为新函数的 thisArg

arg1, arg2, ...当目标函数被调用时,被预置入绑定函数的参数列表中的参数。

一个简单的例子:

  1. const person = {
  2. name: 'YuTengjing',
  3. age: 22,
  4. };
  5. function introduce() {
  6. console.log(`Hello everyone! My name is ${this.name}. I'm ${this.age} years old.`);
  7. }
  8. const myFriend = { name: 'dongdong', age: 21 };
  9. person.introduce = introduce.bind(myFriend);
  10. // person.introduce 的 this 已经被绑定到 myFriend 上了
  11. console.log(person.introduce()); // => Hello everyone! My name is dongdong. I'm 21 years old.
  12. console.log(person.introduce.call(person)); // => Hello everyone! My name is dongdong. I'm 21 years old.

应用

多参函数 转换为单个数组参数 调用
javascript 中有很多 API 是接受多个参数的比如之前提过的 Math.max,还有很多例如 Math.min,Array.prototype.push 等它们都是接受多个参数的 API,但是有时候我们只有多个参数构成的数组,而且可能还特别大,这个时候就可以利用 apply 巧妙的来转换。

  1. let arr1 = [1, 2, 3];
  2. let arr2 = [4, 5, 6];
  3. Array.prototype.push.apply(arr1, arr2);
  4. console.log(arr1); // [1, 2, 3, 4, 5, 6]
  5. arr1.push(...arr2);
  1. // Math.max 参数为多参数
  2. console.log(Math.max(1, 2, 3)); // => 3
  3. // 现在已知一个很大的元素为随机大小的整数数组
  4. const bigRandomArray = [...Array(10000).keys()].map(num => Math.trunc(num * Math.random()));
  5. // 怎样使用 Math.max 获取 bigRandomArray 中的最大值呢?Math.max 接受的是多参数而不是数组参数啊!
  6. // 思考下面的写法
  7. console.log(Math.max.apply(null, bigRandomArray)); // => 9936
  8. 可以上 ES6 的话就简单了,使用扩展运算符即可,优雅简洁。
  9. console.log(Math.max(...bigRandomArray));

类数组转换为数组
JavaScript类型化数组是一种类似数组的对象,它们有数组的一些属性,但是如果你用 Array.isArray() 去测试会返回 false,常见的像 arguments,NodeList 等。

  1. function testArrayLike() {
  2. // 有 length 属性没有 slice 属性
  3. console.log(arguments.length); // => 3
  4. console.log(arguments.slice); // => undefined
  5. // 类数组不是数组
  6. console.log(Array.isArray(arguments)); // => false
  7. console.log(arguments); // => { [Iterator] 0: 'a', 1: 'b', 2: 'c', [Symbol(Symbol.iterator)]: [λ: values] } 
  8. const array = Array.prototype.slice.call(arguments);
  9. console.log(Array.isArray(array)); // => true
  10. console.log(array); // => [ 'a', 'b', 'c' ]
  11. }
  12. testArrayLike('a', 'b', 'c');
  13. 其实,这个用法也可以忘了,用 ES6 来转换不造多简单,ES6 大法好
  14. 可以使用 Array.from(arrayLike):
  15. const array = Array.from(arguments);
  16. 还可以使用扩展运算符:
  17. const array = [...arguments];

组合继承

  1. function Animal(type) {
  2. this.type = type;
  3. }
  4. function Bird(type, color) {
  5. Animal.call(this, type);
  6. this.color = color;
  7. }
  8. const bird = new Bird('bird', 'green');
  9. console.log(bird); // => Bird { type: 'bird', color: 'green' }
  1. var scope = 123;
  2. var obj = {
  3. scope: 456,
  4. getScope: function () {
  5. var scope = 789;
  6. console.log(scope);
  7. console.log(this.scope);
  8. var f = function() {
  9. console.log(scope);
  10. console.log(this.scope);
  11. }
  12. f();
  13. },
  14. };
  15. obj.getScope(); // example: 输出 xxx/xxx,因为 xxx
  16. var getScope = obj.getScope;
  17. getScope();
  18. var a = { scope: 1 };
  19. var b = { scope: 2 };
  20. getScope.apply(a);
  21. console.log(getScope.bind(a));
  22. getScope.bind(b).call(a);


装饰器

装饰器是一个对类进行处理的函数。装饰器函数的第一个参数,就是所要装饰的目标类。
可以装饰类,类的属性和方法。不可以装饰函数,因为存在函数提升

注意,装饰器对类的行为的改变,是代码编译时发生的,而不是在运行时。这意味着,装饰器能在编译阶段运行代码。也就是说,装饰器本质就是编译时执行的函数。

场景:日志、注释、类型检查、Mixin

  1. @testable
  2. class MyTestableClass {
  3. // ...
  4. }
  5. function testable(target) {
  6. target.isTestable = true;
  7. }
  8. MyTestableClass.isTestable // true
  1. @decorator
  2. class A {}
  3. // 等同于
  4. class A {}
  5. A = decorator(A) || A;
  1. class MyReactComponent extends React.Component {}
  2. export default connect(mapStateToProps, mapDispatchToProps)(MyReactComponent);
  3. @connect(mapStateToProps, mapDispatchToProps)
  4. export default class MyReactComponent extends React.Component {}

DOM

文档对象模型(Document Object Model)简称 DOM

12种节点类型,常用:元素节点、文本节点

遍历

DOM 的所有操作都是以document对象开始
document.documentElement
document.body
document.head

返回的dom元素集合是可迭代的类数组 Array.from()

包含注释.文本等节点
父节点: parentNode
子节点:childNodes firstChild lastChild elem.hasChildNodes()
兄弟节点:previousSibling nextSibling
JavaScript基础 - 图3
仅仅包含元素节点
父节点:parentElement
子节点:children firstElementChild lastElementChild elem.hasChildNodes()
兄弟节点:previousElementSibling nextElementSibling

JavaScript基础 - 图4

搜索

document.getElementById('id') get** 实时动态
elem.querySelectorAll('css') elem.querySelector('css') 静态
elem.matches(css) 用于检查elem与给定的 CSS 选择器是否匹配。
elem.closest(css) 用于查找与给定 CSS 选择器相匹配的最近的祖先。elem本身也会被检查。
如果elemB在elemA内(elemA的后代)或者elemA==elemB,elemA.contains(elemB)将返回 true。

节点属性

节点类型 nodeType

  • 对于元素节点elem.nodeType == 1
  • 对于文本节点elem.nodeType == 3
  • 对于 document 对象elem.nodeType == 9

节点名称 nodeName tagName(仅元素节点) 大写

读取并修改元素内容 innerHTML
获取元素完整内容,不会修改元素内容 outerHTML = innerHTML + 自己

文本节点内容(text) nodeValue data
纯文本内容 去掉tag textContent
隐藏 hidden

特性和属性

DOM属性 DOM节点有很多属性,我们也可以在节点上添加属性 大小写敏感
HTML特性 标签可能拥有特性。当浏览器解析 HTML 文本,并根据标签创建 DOM 对象时,浏览器会辨别标准的特性并以此创建 DOM 属性。但是非标准的特性则不会。大小写不敏感

  1. <body id="test" something="non-standard">
  2. <script>
  3. console.log(document.body.id); // test
  4. // 非标准的特性没有获得对应的属性
  5. console.log(document.body.something); // undefined
  6. </script>
  7. </body>

如果一个特性不是标准的,那么就没有相对应的 DOM 属性。
所有特性都可以通过使用以下方法进行访问:

  • elem.hasAttribute(name)— 检查特性是否存在。
  • elem.getAttribute(name)— 获取这个特性值。
  • elem.setAttribute(name, value)— 设置这个特性值。
  • elem.removeAttribute(name)— 移除这个特性。

自定义特性 所有以 “data-” 开头的特性均被保留供程序员使用。它们可在dataset属性中使用

修改文档

创建元素 document.createElement(tag)
创建文本 document.createTextContent(text)
节点移除 node.remove()
克隆节点 elem.cloneNode() 参数是true或者false 深拷贝

插入元素或者文本

  • node.append(...nodes or strings)—— 在node末尾插入节点或字符串,
  • node.prepend(...nodes or strings)—— 在node开头插入节点或字符串,
  • node.before(...nodes or strings)—— 在node前面插入节点或字符串,
  • node.after(...nodes or strings)—— 在node后面插入节点或字符串,
  • node.replaceWith(...nodes or strings)—— 将node替换为给定的节点或字符串。

更通用
insertAdjacentHTML/Text/Element(where,html)
该方法的第一个参数是代码字(code word),指定相对于elem的插入位置。必须为以下之一:

  • beforebegin“— 将html插入到elem前插入,
  • afterbegin“— 将html插入到elem开头,
  • beforeend“— 将html插入到elem末尾,
  • afterend“— 将html插入到elem后。

这里还有“旧式”的方法:

  • parent.appendChild(node)
  • parent.insertBefore(node, nextSibling)
  • parent.removeChild(node)
  • parent.replaceChild(newElem, node)

JavaScript基础 - 图5

JavaScript基础 - 图6

样式和类

getComputedStyle(elem, [pseudo])返回与style对象类似的,且包含了所有类的对象。只读。
elem.className
elem.classList

  • elem.classList.add/remove(class)— 添加/移除类。
  • elem.classList.toggle(class)— 如果类不存在就添加类,存在就移除它。
  • elem.classList.contains(class)— 检查给定类,返回true/false。

elem.style.*

  1. div.style.cssText=`color: red !important;
  2. background-color: yellow;
  3. width: 100px;
  4. text-align: center;
  5. `;
  6. 完全重写

元素大小和滚动

image.png

  • offsetParent— 是最接近的 CSS 定位的祖先,或者是td,th,table,body。
  • offsetLeft/offsetTop— 是相对于offsetParent的左上角边缘的坐标。
  • offsetWidth/offsetHeight— 元素的“外部” width/height,边框(border)尺寸计算在内。
  • clientLeft/clientTop— 从元素左上角外角到左上角内角的距离。对于从左到右显示内容的操作系统来说,它们始终是左侧/顶部 border 的宽度。而对于从右到左显示内容的操作系统来说,垂直滚动条在左边,所以clientLeft也包括滚动条的宽度。
  • clientWidth/clientHeight— 内容的 width/height,包括 padding,但不包括滚动条(scrollbar)。
  • scrollWidth/scrollHeight— 内容的 width/height,就像clientWidth/clientHeight一样,但还包括元素的滚动出的不可见的部分。
  • scrollLeft/scrollTop— 从元素的左上角开始,滚动出元素的上半部分的 width/height。

除了scrollLeft/scrollTop外,所有属性都是只读的。如果我们修改scrollLeft/scrollTop,浏览器会滚动对应的元素。

Window 大小和滚动

几何:

  • 文档可见部分的 width/height(内容区域的 width/height):document.documentElement.clientWidth/clientHeight
  • 整个文档的 width/height,其中包括滚动出去的部分:

    1. let scrollHeight = Math.max(
    2. document.body.scrollHeight, document.documentElement.scrollHeight,
    3. document.body.offsetHeight, document.documentElement.offsetHeight,
    4. document.body.clientHeight, document.documentElement.clientHeight
    5. );

    滚动:

  • 读取当前的滚动:window.pageYOffset/pageXOffset。

  • 更改当前的滚动:
    • window.scrollTo(pageX,pageY)— 绝对坐标,
    • window.scrollBy(x,y)— 相对当前位置进行滚动,
    • elem.scrollIntoView(top)— 滚动以使elem可见(elem与窗口的顶部/底部对齐)。

      元素坐标

窗口相对坐标:clientX/clientY
文档相对坐标:pageX/pageY
elem.getBoundingClientRect()返回最小矩形的窗口坐标
image.png

事件

绑定事件

  1. HTML 特性(attribute):onclick="..."
  2. DOM 属性(property):elem.onclick = function
  3. 方法(method):elem.addEventListener(event, handler[, phase])用于添加,removeEventListener用于移除。

捕获冒泡/默认行为

冒泡 当一个事件发生在一个元素上,它会首先运行在该元素上的处理程序,然后运行其父元素上的处理程序,然后一直向上到其他祖先上的处理程序。

event.stopPropagation() 停止向上移动,但是当前元素上的其他处理程序都会继续运行。
event.stopImmediatePropagation() 阻止当前元素上的处理程序运行,使用该方法之后,其他处理程序就不会被执行。
event.preventDefault()或者 return false

事件委托

如果我们有许多以类似方式处理的元素,那么就不必为每个元素分配一个处理程序 —— 而是将单个处理程序放在它们的共同祖先上。
在处理程序中,我们获取event.target以查看事件实际发生的位置并进行处理

事件对象

event有很多属性 typetargetcurrentTargeteventPhase

引发事件的那个嵌套层级最深的元素被称为目标元素,可以通过**event.target**访问。
注意与this(=event.currentTarget)之间的区别:

  • event.target—— 是引发事件的“目标”元素,它在冒泡过程中不会发生变化。事件绑定的元素
  • this—— 是“当前”元素,其中有一个当前正在运行的处理程序。

_defaultPrevented_ 检查处理程序是否阻止了浏览器的默认行为
_event.isTrusted_ 真实用户操作的事件属性为true,对于脚本生成的事件属性为false。

自定义事件

鼠标事件

点击鼠标:mousedown mouseup click
移动鼠标:mouseover/out mouseenter/leave

拖拽事件

  1. H5原生 drag drop
  2. 利用鼠标事件实现

基础的拖放算法如下所示:

  • 事件流:ball.mousedown→document.mousemove→ball.mouseup(不要忘记取消原生ondragstart)。
  • 在拖动开始时 —— 记住鼠标指针相对于元素的初始偏移(shift):shiftX/shiftY,并在拖动过程中保持它不变。
  • 使用document.elementFromPoint检测鼠标指针下的 “droppable” 的元素。

    滚动事件

    scroll

    表单控件

    资源加载

    HTML 页面的生命周期包含三个重要事件:

  • DOMContentLoaded—— 浏览器已完全加载 HTML,并构建了 DOM 树,但像JavaScript基础 - 图9和样式表之类的外部资源可能尚未加载完成。

  • load—— 浏览器不仅加载完成了 HTML,还加载完成了所有外部资源:图片,样式等。
  • beforeunload/unload—— 当用户正在离开页面时。

每个事件都是有用的:

  • DOMContentLoaded事件 —— DOM 已经就绪,因此处理程序可以查找 DOM 节点,并初始化接口。
  • load事件 —— 外部资源已加载完成,样式已被应用,图片大小也已知了。
  • beforeunload事件 —— 用户正在离开:我们可以检查用户是否保存了更改,并询问他是否真的要离开。
  • unload事件 —— 用户几乎已经离开了,但是我们仍然可以启动一些操作,例如发送统计数据。
顺序 DOMContentLoaded
async 加载优先顺序。脚本在文档中的顺序不重要 —— 先加载完成的先执行 不相关。可能在文档加载完成前加载并执行完毕。如果脚本很小或者来自于缓存,同时文档足够长,就会发生这种情况。
defer 文档顺序(它们在文档中的顺序) 在文档加载和解析完成之后(如果需要,则会等待),即在DOMContentLoaded之前执行。

在实际开发中,defer用于需要整个 DOM 的脚本,和/或脚本的相对执行顺序很重要的时候。
async用于独立脚本,例如计数器或广告,这些脚本的相对执行顺序无关紧要。

Excersize

图片懒加载

思路

  1. 图片加data-src特性,指向图片真实的src
  2. 判断图片是否在当前可视区域
  3. 在可视区域内,将data-src赋值给src

    1. function isVisible(elem) {
    2. let coords = elem.getBoundingClientRect();
    3. let windowHeight = window.innerHeight||document.documentElement.clientHeight;
    4. // 顶部元素边缘可见吗?
    5. let topVisible = coords.top > 0 && coords.top < windowHeight;
    6. // 底部元素边缘可见吗?
    7. let bottomVisible = coords.bottom < windowHeight && coords.bottom > 0;
    8. return topVisible || bottomVisible;
    9. }
    10. function showVisible() {
    11. for (let img of document.querySelectorAll('img')) {
    12. let realSrc = img.dataset.src; //img.getAttribute('data-src')
    13. if (!realSrc) continue;
    14. if (isVisible(img)) {
    15. // disable caching
    16. // this line should be removed in production code
    17. realSrc += '?nocache=' + Math.random();
    18. img.src = realSrc;
    19. img.dataset.src = '';
    20. }
    21. }
    22. }
    23. window.addEventListener('scroll', showVisible);
    24. showVisible();

BOM

浏览器对象模型(Browser Object Model)简称 BOM

location
navigation
history
screen

网络请求和文件

大文件上传及断点续传

https://juejin.cn/post/6844904046436843527#heading-1

fetch和ajax区别

1)fetch只对网络请求报错,对400,500都当做成功的请求,服务器返回 400,500 错误码时并不会 reject,只有网络错误这些导致请求不能完成时,fetch 才会被 reject。
2)fetch默认不会带cookie,需要添加配置项: fetch(url, {credentials: ‘include’})
3)fetch不支持abort,不支持超时控制,使用setTimeout及Promise.reject的实现的超时控制并不能阻止请求过程继续在后台运行,造成了流量的浪费
4)fetch没有办法原生监测请求的进度,而XHR可以

  1. 语法简洁,更加语义化
    2. 基于标准 Promise 实现,支持 async/await
    3. 同构方便,使用 isomorphic-fetch
    4.更加底层,提供的API丰富(request, response)
    5.脱离了XHR,是ES规范里新的实现方式