数据类型
数字Number
为什么0.1+0.2 解决方案
计算机原码 反码 补码
为什么计算机只认识0和1
变量命名 - var let const
var
var
声明可以在包含它的函数、模块、命名空间或全局作用域内部任何位置被访问,包含它的代码块对此没有什么影响。 有些人称此为 **var**
作用域或函数作用域 。
for (var i = 0; i < 10; i++) {
setTimeout(function() { console.log(i); }, 1000 * i);
}
function test() {
for (var i = 0; i < 10; i++) {
setTimeout(function() {
console.log(new Date(), i);
}, 1000 * i);
}
console.log("end", new Date(), i); //为方便后边演示,这里加了打印end标志
}
test();
看一下上面的输出是什么? 10个10,每隔1s,打印一个10
我们传给
setTimeout
的每一个函数表达式实际上都引用了相同作用域里的同一个i
。
让我们花点时间思考一下这是为什么。 setTimeout 在若干毫秒后执行一个函数,事件循环机制中,i=0的时候,i++, 先把红任务setTimeout放到事件队列中,当主线程执行完后(end被打印时),timeout这些回调依次执行(队列:FIFO),此时for 循环结束后,i 的值为 10。 所以当函数被调用的时候,它会打印出 10,放到队列的时候 i=1,所以会间隔1s打印10!
解法1:IIFE
for (var i = 0; i < 10; i++) {
(function(i) {
setTimeout(function() { console.log(i); }, 100 * i);
})(i);
}
IIFE当函数变成立即执行的函数表达式时,表达式中的变量不能从外部访问,也就是说,传入内部的i覆盖了for中i
解法2:函数调用按值传递
const output = function (i) {
setTimeout(function() {
console.log(new Date, i);
}, 1000);
};
function test(){
for (var i = 0; i < 10; i++) {
output(i); // 这里传过去的 i 值被复制,而不是引用
}
console.log(new Date, i);
}
解法3:利用setTimeout第三个参数
for (var i = 0; i < 10; i++) {
setTimeout(function(i) {
console.log(new Date, i);
}, 1000, i);
}
解法4:let
针对每次迭代都会创建这样一个新作用域
for (let i = 0; i < 10 ; i++) {
setTimeout(function() {console.log(i); }, 100 * i);
}
let
和var区别:
- let声明的变量具有块级作用域。块作用域变量在包含它们的块或
for
循环之外是不能访问的。 - 暂时性死区:不能在被声明之前访问
- let不能重复对相同变量进行声明
const
作用域规则和let一样,不可以重新赋值
call/apply/bind
一般情况下对象的方法被调用时,this指向该对象
const person = {
name: 'Guanqingchao',
age: 22,
introduce() {
console.log(`Hello everyone! My name is ${this.name}. I'm ${this.age} years old.`);
}
};
Hello everyone! My name is Guanqingchao. I'm 22 years old.
通常,我们想复用对象里的方法的时候,通过call、bind、apply可以改变方法中的this指向
const person = {
name: 'Guanqingchao',
age: 22,
introduce() {
console.log(`Hello everyone! My name is ${this.name}. I'm ${this.age} years old.`);
}
};
const myFriend = {
name: 'Renjianqing',
age: 22,
};
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
function displayHobbies(...hobbies) {
console.log(`${this.name} likes ${hobbies.join(', ')}.`);
}
// 下面两个等价
displayHobbies.call({ name: 'Bob' }, 'swimming', 'basketball', 'anime'); // => // => Bob likes swimming, basketball, anime.
displayHobbies.apply({ name: 'Bob' }, ['swimming', 'basketball', 'anime']); // => Bob likes swimming, basketball, anime.
bind
bind 的函数签名是 func.bind(thisArg, arg1, arg2, ...)
。
返回一个原函数的拷贝,并拥有指定的 **this**
值和初始参数。
thisArg
调用绑定函数时作为 this
参数传递给目标函数的值。 如果使用new
运算符构造绑定函数,则忽略该值。当使用 bind
在 setTimeout
中创建一个函数(作为回调提供)时,作为 thisArg
传递的任何原始值都将转换为 object
。如果 bind
函数的参数列表为空,或者thisArg
是null
或undefined
,执行作用域的 this
将被视为新函数的 thisArg
。
arg1, arg2, ...
当目标函数被调用时,被预置入绑定函数的参数列表中的参数。
一个简单的例子:
const person = {
name: 'YuTengjing',
age: 22,
};
function introduce() {
console.log(`Hello everyone! My name is ${this.name}. I'm ${this.age} years old.`);
}
const myFriend = { name: 'dongdong', age: 21 };
person.introduce = introduce.bind(myFriend);
// person.introduce 的 this 已经被绑定到 myFriend 上了
console.log(person.introduce()); // => Hello everyone! My name is dongdong. I'm 21 years old.
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 巧妙的来转换。
let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];
Array.prototype.push.apply(arr1, arr2);
console.log(arr1); // [1, 2, 3, 4, 5, 6]
arr1.push(...arr2);
// Math.max 参数为多参数
console.log(Math.max(1, 2, 3)); // => 3
// 现在已知一个很大的元素为随机大小的整数数组
const bigRandomArray = [...Array(10000).keys()].map(num => Math.trunc(num * Math.random()));
// 怎样使用 Math.max 获取 bigRandomArray 中的最大值呢?Math.max 接受的是多参数而不是数组参数啊!
// 思考下面的写法
console.log(Math.max.apply(null, bigRandomArray)); // => 9936
可以上 ES6 的话就简单了,使用扩展运算符即可,优雅简洁。
console.log(Math.max(...bigRandomArray));
将类数组转换为数组
JavaScript类型化数组是一种类似数组的对象,它们有数组的一些属性,但是如果你用 Array.isArray() 去测试会返回 false,常见的像 arguments,NodeList 等。
function testArrayLike() {
// 有 length 属性没有 slice 属性
console.log(arguments.length); // => 3
console.log(arguments.slice); // => undefined
// 类数组不是数组
console.log(Array.isArray(arguments)); // => false
console.log(arguments); // => { [Iterator] 0: 'a', 1: 'b', 2: 'c', [Symbol(Symbol.iterator)]: [λ: values] }
const array = Array.prototype.slice.call(arguments);
console.log(Array.isArray(array)); // => true
console.log(array); // => [ 'a', 'b', 'c' ]
}
testArrayLike('a', 'b', 'c');
其实,这个用法也可以忘了,用 ES6 来转换不造多简单,ES6 大法好 。
可以使用 Array.from(arrayLike):
const array = Array.from(arguments);
还可以使用扩展运算符:
const array = [...arguments];
组合继承
function Animal(type) {
this.type = type;
}
function Bird(type, color) {
Animal.call(this, type);
this.color = color;
}
const bird = new Bird('bird', 'green');
console.log(bird); // => Bird { type: 'bird', color: 'green' }
var scope = 123;
var obj = {
scope: 456,
getScope: function () {
var scope = 789;
console.log(scope);
console.log(this.scope);
var f = function() {
console.log(scope);
console.log(this.scope);
}
f();
},
};
obj.getScope(); // example: 输出 xxx/xxx,因为 xxx
var getScope = obj.getScope;
getScope();
var a = { scope: 1 };
var b = { scope: 2 };
getScope.apply(a);
console.log(getScope.bind(a));
getScope.bind(b).call(a);
装饰器
装饰器是一个对类进行处理的函数。装饰器函数的第一个参数,就是所要装饰的目标类。
可以装饰类,类的属性和方法。不可以装饰函数,因为存在函数提升
注意,装饰器对类的行为的改变,是代码编译时发生的,而不是在运行时。这意味着,装饰器能在编译阶段运行代码。也就是说,装饰器本质就是编译时执行的函数。
场景:日志、注释、类型检查、Mixin
@testable
class MyTestableClass {
// ...
}
function testable(target) {
target.isTestable = true;
}
MyTestableClass.isTestable // true
@decorator
class A {}
// 等同于
class A {}
A = decorator(A) || A;
class MyReactComponent extends React.Component {}
export default connect(mapStateToProps, mapDispatchToProps)(MyReactComponent);
@connect(mapStateToProps, mapDispatchToProps)
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
仅仅包含元素节点
父节点:parentElement
子节点:children
firstElementChild
lastElementChild
elem.hasChildNodes()
兄弟节点:previousElementSibling
nextElementSibling
搜索
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 属性。但是非标准的特性则不会。大小写不敏感
<body id="test" something="non-standard">
<script>
console.log(document.body.id); // test
// 非标准的特性没有获得对应的属性
console.log(document.body.something); // undefined
</script>
</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)
样式和类
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.*
div.style.cssText=`color: red !important;
background-color: yellow;
width: 100px;
text-align: center;
`;
完全重写
元素大小和滚动
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,其中包括滚动出去的部分:
let scrollHeight = Math.max(
document.body.scrollHeight, document.documentElement.scrollHeight,
document.body.offsetHeight, document.documentElement.offsetHeight,
document.body.clientHeight, document.documentElement.clientHeight
);
滚动:
读取当前的滚动:
window.pageYOffset/pageXOffset。
- 更改当前的滚动:
窗口相对坐标:clientX/clientY
文档相对坐标:pageX/pageY
elem.getBoundingClientRect()
返回最小矩形的窗口坐标
事件
绑定事件
- HTML 特性(attribute):
onclick="..."
- DOM 属性(property):
elem.onclick = function
- 方法(method):
elem.addEventListener(event, handler[, phase]
)用于添加,removeEventListener
用于移除。
捕获冒泡/默认行为
冒泡 当一个事件发生在一个元素上,它会首先运行在该元素上的处理程序,然后运行其父元素上的处理程序,然后一直向上到其他祖先上的处理程序。
event.stopPropagation()
停止向上移动,但是当前元素上的其他处理程序都会继续运行。event.stopImmediatePropagation()
阻止当前元素上的处理程序运行,使用该方法之后,其他处理程序就不会被执行。event.preventDefault()
或者 return false
事件委托
如果我们有许多以类似方式处理的元素,那么就不必为每个元素分配一个处理程序 —— 而是将单个处理程序放在它们的共同祖先上。
在处理程序中,我们获取event.target以查看事件实际发生的位置并进行处理
事件对象
event
有很多属性 type
、target
、currentTarget
、eventPhase
引发事件的那个嵌套层级最深的元素被称为目标元素,可以通过**event.target**
访问。
注意与this(=event.currentTarget)
之间的区别:
event.target
—— 是引发事件的“目标”元素,它在冒泡过程中不会发生变化。事件绑定的元素this
—— 是“当前”元素,其中有一个当前正在运行的处理程序。
_defaultPrevented_
检查处理程序是否阻止了浏览器的默认行为_event.isTrusted_
真实用户操作的事件属性为true,对于脚本生成的事件属性为false。
自定义事件
鼠标事件
点击鼠标:mousedown mouseup click
移动鼠标:mouseover/out mouseenter/leave
拖拽事件
- H5原生 drag drop
- 利用鼠标事件实现
基础的拖放算法如下所示:
- 事件流:ball.mousedown→document.mousemove→ball.mouseup(不要忘记取消原生ondragstart)。
- 在拖动开始时 —— 记住鼠标指针相对于元素的初始偏移(shift):shiftX/shiftY,并在拖动过程中保持它不变。
使用document.elementFromPoint检测鼠标指针下的 “droppable” 的元素。
滚动事件
表单控件
资源加载
HTML 页面的生命周期包含三个重要事件:
DOMContentLoaded—— 浏览器已完全加载 HTML,并构建了 DOM 树,但像和样式表之类的外部资源可能尚未加载完成。
- load—— 浏览器不仅加载完成了 HTML,还加载完成了所有外部资源:图片,样式等。
- beforeunload/unload—— 当用户正在离开页面时。
每个事件都是有用的:
- DOMContentLoaded事件 —— DOM 已经就绪,因此处理程序可以查找 DOM 节点,并初始化接口。
- load事件 —— 外部资源已加载完成,样式已被应用,图片大小也已知了。
- beforeunload事件 —— 用户正在离开:我们可以检查用户是否保存了更改,并询问他是否真的要离开。
- unload事件 —— 用户几乎已经离开了,但是我们仍然可以启动一些操作,例如发送统计数据。
顺序 | DOMContentLoaded | |
---|---|---|
async | 加载优先顺序。脚本在文档中的顺序不重要 —— 先加载完成的先执行 | 不相关。可能在文档加载完成前加载并执行完毕。如果脚本很小或者来自于缓存,同时文档足够长,就会发生这种情况。 |
defer | 文档顺序(它们在文档中的顺序) | 在文档加载和解析完成之后(如果需要,则会等待),即在DOMContentLoaded之前执行。 |
在实际开发中,defer用于需要整个 DOM 的脚本,和/或脚本的相对执行顺序很重要的时候。
async用于独立脚本,例如计数器或广告,这些脚本的相对执行顺序无关紧要。
Excersize
图片懒加载
思路
- 图片加data-src特性,指向图片真实的src
- 判断图片是否在当前可视区域
在可视区域内,将data-src赋值给src
function isVisible(elem) {
let coords = elem.getBoundingClientRect();
let windowHeight = window.innerHeight||document.documentElement.clientHeight;
// 顶部元素边缘可见吗?
let topVisible = coords.top > 0 && coords.top < windowHeight;
// 底部元素边缘可见吗?
let bottomVisible = coords.bottom < windowHeight && coords.bottom > 0;
return topVisible || bottomVisible;
}
function showVisible() {
for (let img of document.querySelectorAll('img')) {
let realSrc = img.dataset.src; //img.getAttribute('data-src')
if (!realSrc) continue;
if (isVisible(img)) {
// disable caching
// this line should be removed in production code
realSrc += '?nocache=' + Math.random();
img.src = realSrc;
img.dataset.src = '';
}
}
}
window.addEventListener('scroll', showVisible);
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可以
- 语法简洁,更加语义化
2. 基于标准 Promise 实现,支持 async/await
3. 同构方便,使用 isomorphic-fetch
4.更加底层,提供的API丰富(request, response)
5.脱离了XHR,是ES规范里新的实现方式