一、数据类型
1. 数据类型
基本数据类型:number,string,boolean,undefined,null,symbol
复杂数据类型:object。对象又可以分成三个子类型:狭义的对象(object),数组(array),函数(function)
2. 数据类型的判断
- typeof 运算符
typeof 123 // "number"
数值:返回 "number"
字符串:返回 "string"
布尔值:返回 "boolean"
symbol: 返回 "symbol"
undefined:返回 "undefined"
null:返回 "object"
object:返回 "object"
function:返回 "function"
-
instanceof 运算符
返回一个布尔值,表示对象是否为某个构造函数的实例。
var o = {};
var a = [];
o instanceof Array // false
a instanceof Array // true
-
Object.prototype.toString
返回值的构造函数。
Object.prototype.toString.call(123);// "[object Number]"
数值:返回[object Number]。
字符串:返回[object String]。
布尔值:返回[object Boolean]。
undefined:返回[object Undefined]。
null:返回[object Null]。
数组:返回[object Array]。
arguments 对象:返回[object Arguments]。
函数:返回[object Function]。
Error 对象:返回[object Error]。
Date 对象:返回[object Date]。
RegExp 对象:返回[object RegExp]。
其他对象:返回[object Object]。
3. 数据类型转换
强制转换,自动转换
-
parseInt() && parseFloat()
将字符串转为整数 && 浮点数。
如果字符串头部有空格,空格会被自动去除。
parseInt(' 81') // 81
如果parseInt的参数不是字符串,则会先转为字符串再转换。
parseInt(1.23) // 1
字符串转为整数的时候,是一个个字符依次转换,如果遇到不能转为数字的字符,就不再进行下去,返回已经转好的部分。
parseInt('8a') // 8
parseInt('12**') // 12
如果字符串的第一个字符不能转化为数字(后面跟着数字的正负号除外),返回NaN。
parseInt('abc') // NaN
parseInt('.3') // NaN
如果字符串以0x或0X开头,parseInt会将其按照十六进制数解析。
parseInt('0x10') // 16
如果字符串以0开头,将其按照10进制解析。
parseInt('011') // 11
-
Number()
第一步,调用对象自身的valueOf方法。如果返回原始类型的值,则直接对该值使用Number函数,不再进行后续步骤。
第二步,如果valueOf方法返回的还是对象,则改为调用对象自身的toString方法。如果toString方法返回原始类型的值,则对该值使用Number函数,不再进行后续步骤。
第三步,如果toString方法返回的是对象,就报错。
valueOf()方法返回包装对象实例对应的原始类型的值。
new Number(123).valueOf() // 123
new String('abc').valueOf() // "abc"
new Boolean(true).valueOf() // true
toString()方法返回对应的字符串形式。
new Number(123).toString() // "123"
new String('abc').toString() // "abc"
new Boolean(true).toString() // "true"
-
String() 先调用对象自身的toString方法。如果返回原始类型的值,则对该值使用String函数,不再进行以下步骤。
如果toString方法返回的是对象,再调用原对象的valueOf方法。如果valueOf方法返回原始类型的值,则对该值使用String函数,不再进行以下步骤。
如果valueOf方法返回的是对象,就报错。
-
Boolean() 它的转换规则相对简单:除了以下五个值的转换结果为false,其他的值全部为true。
undefined
null
0(包含-0和+0)
NaN
''(空字符串)
4. 函数的参数
- 如果一个对象的所有键名都是正整数或零,并且有length属性,那么这个对象就很像数组,语法上称为“类似数组的对象”。数组专有的方法(比如slice和forEach),不能在类数组上直接使用。
- arguments对象包含了函数运行时的所有参数,arguments[0] 就是第一个参数,arguments[1] 就是第二个参数,以此类推。这个对象只有在函数体内部,才可以使用。
- 数组的slice方法可以将“类似数组的对象”变成真正的数组。
var arr = Array.prototype.slice.call(arrayLike)
预期什么类型的值,就调用该类型的转换函数。
5. 闭包和立即执行函数
闭包就是函数f2,即能够读取其他函数内部变量的函数。由于在 JavaScript 语言中,只有函数内部的子函数才能读取内部变量,因此可以把闭包简单理解成“定义在一个函数内部的函数”。 作用:
读取函数内部的变量 让这些变量始终保持在内存中,即闭包可以使得它诞生环境一直存在。 是封装对象的私有属性和私有方法。
function f1() {
var n = 999
function f2() {
console.log(n)
}
return f2
}
var result = f1()
result() // 999
立即调用的函数表达式(IIFE) 作用:
不必为函数命名,避免了污染全局变量 IIFE 内部形成了一个单独的作用域,可以封装一些外部无法读取的私有变量。
(function(){ /* code */ }());
// 或者
(function(){ /* code */ })();
二、标准库
1. Array 数组常用方法
let a = [1, 2, 3]
a.splice(1, 1, 4, 5) // [2]
a // [1, 4, 5, 3]
["a", "b", "c"].slice(1,2) // ["b"]
["a", "b", "c"].join('-') // "a-b-c"
["a", "b", "c"].concat("d") // ["a", "b", "c", "d"]
[3,1,2].sort((a,b) => a-b) // [1, 2, 3]
["a", "b", "c"].reverse() // ["c", "b", "a"]
push(), pop(), unshift(), shift()
[3, 10, 18, 20].some(val => val>18) // true
[3, 10, 18, 20].every(val => val>18) // false
[3, 10, 18, 20].filter(val => val>18) // [20]
[1,2,3].forEach(val => val+1) // undefined
[1,2,3].map(val => val+1) // [2, 3, 4]
arr.reduce(callback,[initialValue])
[1, 2, 3].reduce(function(prev, cur, index, arr) {
console.log(prev, cur, index, arr);
return prev + cur;
},1) // 7
[1,2,3].indexOf(2) // 1
2. String 字符串常用方法
"hello".indexOf('h') // 0
"hello".match("e") // ["e", index: 1, input: "hello", groups: undefined] String.match(regexp)
"hello".search("h") //0 String.search(regexp)
"hello".charAt(1) // "e"
"hello".concat("world") // "helloworld"
"hello".replace("o","ooo") // "hellooo"
"abc".slice(1,2) // "b"
"abc".split("") // ["a", "b", "c"]
"hello".substr(2,3) // "llo"
"hellow".substring(2,3) // "l"
toLowerCase(), toUpperCase()
3. Object
Object.keys({a:1,b:2}) // ["a", "b"]
Object.values({a:1,b:2}) // [1, 2]
三、面向对象编程
1. new 和 构造函数
- 构造函数,就是专门用来生成实例对象的函数。
var Vehicle = function () {
this.price = 1000;
}
- 函数体内部使用了this关键字,代表了所要生成的对象实例。
- 生成对象的时候,必须使用new命令。
- new 命令将执行
- 创建一个空对象,作为将要返回的对象实例。
- 将这个空对象的原型,指向构造函数的prototype属性。
- 将这个空对象赋值给函数内部的this关键字。
- 开始执行构造函数内部的代码。
- 注意事项
- 如果构造函数内部有return语句,而且return后面跟着一个对象,new命令会返回return语句指定的对象;否则,就会不管return语句,返回this对象。
- 如果对普通函数(内部没有this关键字的函数)使用new命令,则会返回一个空对象。
- Object.create() 创建实例对象
var person1 = {
name: '张三',
age: 38,
greeting: function() {
console.log('Hi! I\'m ' + this.name);
}
};
var person2 = Object.create(person1);
person2.name // 张三
person2.greeting() // Hi! I'm 张三
2. 原型链
所有的JS对象都有一个prototype属性,指向它的原型对象(prototype)。当试图访问一个对象的属性时,如果没有在该对象上找到,它还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。
所有对象都继承了Object.prototype 的属性。
Object.prototype 的原型是null。null 没有任何属性和方法,也没有自己的原型。因此,原型链的尽头就是null。
arr.__proto__ = Array.prototype
Array.__proto__ = Function.prototype
Array.prototype.__proto__ === Object.prototype
Function.__proto__ === Object.prototype
Object.prototype.__proto__ === null
3. constructor 属性
原型 prototype 对象有一个 constructor 属性,默认指向 prototype 对象所在的构造函数。
constructor 属性的作用是,可以得知某个实例对象,到底是哪一个构造函数产生的。
4. 继承
// ES5
function Animal(name) {
this.name = name
}
Animal.prototype.run = function() {
return 'I am running!'
}
//继承
function Dog(name, color){
this.color = color
Animal.call(this, name)
}
Dog.prototype = new Animal()
Dog.prototype.constructor = Dog
var awu = new Dog("awu", "yellow")
// ES6
class Animal{
constructor(name){
this.name = name
}
run(){
return 'I am running!'
}
}
class Dog extends Animal{
constructor(name, color){
super(name)
this.color = color
}
}
var awu = new Dog("awu", "yellow")
5. call,apply,bind
可以改变this的指向。this 是你想指定的上下文,他可以是任何一个 JavaScript 对象。
- call:需要把参数按顺序传递进去。
fun.call(thisArg, arg1, arg2, ...)
- apply:是把参数放在数组里。
func.apply(thisArg, [argsArray])
- bind:以创建它时传入 bind()方法的第一个参数作为 this,传入 bind() 方法的第二个以及以后的参数加上绑定函数运行时本身的参数按照顺序作为原函数的参数来调用原函数。
function.bind(thisArg[, arg1[, arg2[, ...]]])
6. 深拷贝和浅拷贝
| -- | 和原数据是否指向同一对象 | 第一层数据为基本数据类型 | 原数据中包含子对象 |
|---|---|---|---|
| 赋值 | 是 | 改变会使原数据一同改变 | 改变会使原数据一同改变 |
| 浅拷贝 | 否 | 改变不会使原数据一同改变 | 改变会使原数据一同改变 |
| 深拷贝 | 否 | 改变不会使原数据一同改变 | 改变不会使原数据一同改变 |
- 浅拷贝(shallow copy):只复制指向某个对象的指针,而不复制对象本身,新旧对象共享一块内存
function shallowCopy(src) {
var dst = {}
for (var prop in src) {
if (src.hasOwnProperty(prop)) {
dst[prop] = src[prop]
}
}
return dst
}
- 深拷贝(deep copy):复制并创建一个一摸一样的对象,不共享内存,修改新对象,旧对象保持不变。
// JSON.parse(JSON.stringify())
let arr = [1, 3, {
username: ' kobe'
}]
let arr4 = JSON.parse(JSON.stringify(arr))
arr4[2].username = 'duncan'
console.log(arr, arr4)
// 递归
7. 柯里化
四、异步操作
1. 同步任务和异步任务
-
同步任务是那些没有被引擎挂起、在主线程上排队执行的任务。只有前一个任务执行完毕,才能执行后一个任务。
-
异步任务是那些被引擎放在一边,不进入主线程、而进入任务队列的任务。只有引擎认为某个异步任务可以执行了(比如 Ajax 操作从服务器得到了结果),该任务(采用回调函数的形式)才会进入主线程执行。排在异步任务后面的代码,不用等待异步任务结束会马上运行,也就是说,异步任务不具有“堵塞”效应。
2. 任务队列和事件循环
-
任务队列:JavaScript 运行时,除了一个正在运行的主线程,引擎还提供一个任务队列(task queue),里面是各种需要当前程序处理的异步任务。 首先,主线程会去执行所有的同步任务。等到同步任务全部执行完,就会去看任务队列里面的异步任务。如果满足条件,那么异步任务就重新进入主线程开始执行,这时它就变成同步任务了。等到执行完,下一个异步任务再进入主线程开始执行。一旦任务队列清空,程序就结束执行。
-
事件循环:IO 操作(输入输出)很慢(比如 Ajax 操作从网络读取数据),不得不等着结果出来,再往下执行。JavaScript 语言的设计者意识到,这时 CPU 完全可以不管 IO 操作,挂起处于等待中的任务,先运行排在后面的任务。等到 IO 操作返回了结果,再回过头,把挂起的任务继续执行下去。JavaScript 引擎怎么知道异步任务有没有结果,能不能进入主线程呢?答案就是引擎在不停地检查,一遍又一遍,只要同步任务执行完了,引擎就会去检查那些挂起来的异步任务,是不是可以进入主线程了。这种循环检查的机制,就叫做事件循环(Event Loop)。
-
宏任务和微任务:Promise 的回调函数不是正常的异步任务,而是微任务(microtask)。它们的区别在于,正常任务追加到下一轮事件循环,微任务追加到本轮事件循环。这意味着,微任务的执行时间一定早于正常任务。
3. 异步操作的模式
- 回调函数
- 事件监听
- 发布/订阅
- Generator:Generator 函数是一个状态机,封装了多个内部状态。执行Generator函数会返回一个遍历器对象,也就是说,Generator函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。
function* helloWorldGenerator() {
yield 'hello'
yield 'world'
return 'ending'
}
var hw = helloWorldGenerator() // undefined
hw.next() // {value: "hello", done: false}
hw.next() // {value: "world", done: false}
hw.next() // {value: "ending", done: true}
hw.next() // {value: undefined, done: true}
- Promise:Promise 异步操作有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。除了异步操作的结果,任何其他操作都无法改变这个状态。Promise 对象只有从 pending 变为 fulfilled 和从 pending 变为 rejected 的状态改变。只要处于 fulfilled 和 rejected ,状态就不会再变了即 resolved(已定型)。
const promise = new Promise(function(resolve, reject) {
if (/* 异步操作成功 */){
resolve(value)
} else {
reject(error)
}
})
promise.then(function(value) {
// success
}.catch(function(error) {
// faild
}).finally(function() {
// code
})
const p = Promise.all([p1, p2, p3])
// 只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled;只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected。
const p = Promise.race([p1, p2, p3])
// 只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。
- async:async函数返回一个 Promise 对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。
- async 函数内部return语句返回的值,会成为then方法回调函数的参数。
- async 函数内部抛出错误,会导致返回的 Promise 对象变为reject状态。抛出的错误对象会被catch方法回调函数接收到。
- await 命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值。
// await命令后面的Promise对象,运行结果可能是rejected,所以最好把await命令放在try...catch代码块中。
// good
async function myFunction() {
try {
await somethingThatReturnsAPromise();
} catch (err) {
console.log(err);
}
}
// bad
async function myFunction() {
await somethingThatReturnsAPromise()
.catch(function (err) {
console.log(err)
})
}
五、DOM 和 事件模型
1. DOM增删改查
let element = document.createElement(tagName[, options])
var child = node.appendChild(child)
var child = parentNode.insertBefore(newNode, referenceNode)
element.remove()
document.querySelector()
Node.textContent
Element.getAttribute()
Element.setAttribute()
Element.classList
2. 事件相关API
target.addEventListener(type, listener[, useCapture])
target.removeEventListener(type, listener[, useCapture]);
target.dispatchEvent(event)
3. 事件模型
事件模型分为3个阶段:捕获阶段-目标阶段-冒泡阶段
- 第一阶段:从window对象传导到目标节点(上层传到底层),称为“捕获阶段”(capture phase)。
- 第二阶段:在目标节点上触发,称为“目标阶段”(target phase)。
- 第三阶段:从目标节点传导回window对象(从底层传回上层),称为“冒泡阶段”(bubbling phase)。
<div style="padding:50px;border:1px solid blue">
<button>你好</button>
</div>
<script>
let btn = document.querySelector("button")
let div = document.querySelector("div")
//true - 事件在捕获阶段执行
//false - 默认。事件在冒泡阶段执行
div.addEventListener("click",()=>{console.log(1)},true)
btn.addEventListener("click",()=>{console.log(2)},true)
div.addEventListener("click",()=>{console.log(3)},false)
btn.addEventListener("click",()=>{console.log(4)},false)
//点击button: 1,2,4,3
</script>
点击button:
- 捕获阶段:事件从div向p传播时,触发div的1事件;
- 目标阶段:事件从div到达p时,触发p的2,4事件;
- 冒泡阶段:事件从p传回div时,再次触发div的3事件。
4. 事件委托
事件在冒泡过程中会上传到父节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件,这种方式称为事件代理。
<ul>
<li>列表1</li>
<li>列表2</li>
<li>列表3</li>
</ul>
<script>
var ul = document.querySelector('ul')
//currentTarget指向事件绑定的元素,而target则是指向事件触发的元素。
ul.addEventListener('click', function (event) {
if (event.target.tagName.toLowerCase() === 'li') {
console.log(event.target.textContent)
}
})
</script>
5. mouseover和mouseenter
- mouseover:当鼠标移入元素或其子元素都会触发事件,所以有一个重复触发,冒泡的过程。对应的移除事件是mouseout
- mouseenter:当鼠标移除元素本身(不包含元素的子元素)会触发事件,也就是不会冒泡,对应的移除事件是mouseleave
6. js拖拽功能的实现
- mouseup,mousemove,mousedown
- event.clientX/Y:相对于浏览器可视区域
- event.offsetX/Y:相对被触发dom的左上角距离
- 拖拽事件
- dragstart:开始拖放
- darg:正在拖放
- dragenter:被拖放元素进入某元素
- dragleave:被拖放元素移出目标元素
- dragend:整个拖放操作结束
六、浏览器模型
1. script标签
- 正常的网页加载流程是这样的。
- 浏览器一边下载 HTML 网页,一边开始解析。也就是说,不等到下载完,就开始解析。
- 解析过程中,浏览器发现script元素,就暂停解析,把网页渲染的控制权转交给 JavaScript 引擎。
- 如果script元素引用了外部脚本,就下载该脚本再执行,否则就直接执行代码。
- JavaScript 引擎执行完毕,控制权交还渲染引擎,恢复往下解析 HTML 网页。
- defer属性
- 浏览器开始解析 HTML 网页。
- 解析过程中,发现带有defer属性的script元素。
- 浏览器继续往下解析 HTML 网页,同时并行下载script元素加载的外部脚本。
- 浏览器完成解析 HTML 网页,此时再回过头执行已经下载完成的脚本。
- async属性
- 浏览器开始解析 HTML 网页。
- 解析过程中,发现带有async属性的script标签。
- 浏览器继续往下解析 HTML 网页,同时并行下载script标签中的外部脚本。
- 脚本下载完成,浏览器暂停解析 HTML 网页,开始执行下载的脚本。
- 脚本执行完毕,浏览器恢复解析 HTML 网页。
2. 渲染引擎处理网页
- 解析代码:HTML 代码解析为 DOM,CSS 代码解析为 CSSOM(CSS Object Model)。
- 对象合成:将 DOM 和 CSSOM 合成一棵渲染树(render tree)。
- 布局:计算出渲染树的布局(layout)。
- 绘制:将渲染树绘制到屏幕。
3. BOM相关API
- window
- window.innerWidth:文档宽度
- window.outerWidth:浏览器宽度
- window.open():打开新窗口
- window.scroll():滚动
- window.scrollTo():滚动至
- window.alert():警告框
- window.confirm():确认框
- window.prompt():输入
- window.setTimeout
- window.setInterval
- window.clearInterval
-
Navigator
-
Screen
-
history
- history.go():前进或后退指定的页面数
- history.back():后退一页
- history.forward():前进一页
- location
- location.href:返回或设置当前文档的URL
- location.reload():重载当前页面
4. XMLHttpRequest 对象
var request = new XMLHttpRequest()
request.open('GET', '/a/b/c?name=ff', true)
request.onreadystatechange = function () {
if(request.readyState === 4 && request.status === 200) {
console.log(request.responseText)
}}
request.send()
5. 同源限制
“同源”指的是“三个相同”。
- 协议相同
- 域名相同
- 端口相同
解决同源限制问题:
- JSONP
- postMessage
otherWindow.postMessage(message, targetOrigin, [transfer])
window.addEventListener('message', receiveMessage, false)
- CORS
6. cors
6.1 简单请求和非简单请求
CORS 请求分成两类:简单请求和非简单请求,只要同时满足以下两大条件,就属于简单请求。
-
请求方法是以下三种方法之一:HEAD,GET,POST
对于简单请求,浏览器直接发出 CORS 请求。具体来说,就是在头信息之中,增加一个Origin字段,表名请求来自哪个域名。
-
HTTP 的头信息不超出以下几种字段:Accept,Accept-Language,Content-Language,Last-Event-ID,Content-Type:application/x-www-form-urlencoded、multipart/form-data、text/plain(只限于三个值)
非简单请求的 CORS 请求,会在正式通信之前,增加一次 HTTP 查询请求,称为“预检”请求(preflight)。浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些 HTTP 方法和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。
6.2 跨域请求
- 前端
// cookies:
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
- 后端
Access-Control-Allow-Origin
Access-Control-Expose-Headers
Access-Control-Allow-Methods
Access-Control-Allow-Credentials
7. Storage
| 特性 | Cookie | localStorage | sessionStorage |
|---|---|---|---|
| 数据的生命期 | 一般由服务器生成,可设置失效时间。如果在浏览器端生成Cookie,默认是关闭浏览器后失效 | 除非被清除,否则永久保存 | 仅在当前会话下有效,关闭页面或浏览器后被清除 |
| 存放数据大小 | 4K左右 | 一般为5MB | |
| 与服务器端通信 | 每次都会携带在HTTP头中,如果使用cookie保存过多数据会带来性能问题 | 仅在客户端(即浏览器)中保存,不参与和服务器的通信 | |
| 易用性 | 需要程序员自己封装,源生的Cookie接口不友好 | 源生接口可以接受,亦可再次封装来对Object和Array有更好的支持 | |
8. 读写文件
// HTML 代码如下
// <input type="file" accept="image/*" multiple onchange="fileinfo(this.files)"/>
function fileinfo(files) {
for (var i = 0; i < files.length; i++) {
var f = files[i]
console.log(
f.name, // 文件名,不含路径
f.size, // 文件大小,Blob 实例属性
f.type, // 文件类型,Blob 实例属性
f.lastModifiedDate // 文件的最后修改时间
)
}
}
// HTML 代码如下
// <input type=’file' onchange='readfile(this.files[0])'></input>
function readfile(f) {
var reader = new FileReader()
reader.readAsText(f)
reader.onload = function () {
console.log(reader.result)
}
reader.onerror = function(e) {
console.log(e)
}
}