2020年已经到来,是否是该为了更好的2020年再战一回呢? ‘胜败兵家事不期,包羞忍耻是男儿。江东子弟多才俊,卷土重来未可知’,那些在秋招失利的人,难道就心甘情愿放弃吗!javascript
此文总结2019年以来本人经历以及浏览文章中,较热门的一些面试题,涵盖从CSS到JS再到Vue再到网络等前端基础到进阶的一些知识。css
总结面试题涉及的知识点是对本身的一个提高,也但愿能够帮助到同窗们,在2020年会有一个更好的竞争能力。前端
css篇
- juejin.im/post/5e040e…Es6篇
- juejin.im/post/5e2962…Null是对象吗?java
虽然typeof null
返回的是object
,但这实际上是JavaScript长久以来遗留的一个bug,null
实质上是基本数据类型的一种node
简单数据类型与复杂数据类型在数据存储上有什么区别?jquery
简单数据类型以栈的形式存储,存储的是值es6
复杂数据类型以堆的形式存储,地址(指向堆中的值)存储在栈中。面试
❗ 小知识: 当咱们把对象赋给另一个变量时,复制的是地址,指向同一块内存空间,因此当其中一个对象改变时,另一个对象也会随之改变ajax
JavaScript中原始类型的值被直接存储在栈中,在定义变量时,栈就为其分配好内存空间
- 存储的值大小固定
- 空间较小
- 能够直接操做其保存的变量,运行效率高
- 由系统自动分配存储空间
复制代码
JavaScript中引用类型(对象类型)的值实际存储在堆内存中,
它在栈中只存储了一个固定长度的地址,这个地址指向堆内存中的值
- 存储的值大小不定,可动态调整
- 空间较大,运行效率低
- 没法直接操做其内部存储,使用其地址读取值
- 经过代码分配空间
复制代码
- 可以正确判断简单数据类型(原始类型),除了null,typeof null结果为object
- 对于对象而言,typeof不能正确判断对象类型,typeof仅能够区分开function,除此以外,结果均为object
复制代码
- 可以准确判断复杂数据类型,可是不能正确判断简单数据类型
复制代码
instanceof的原理正则表达式
instanceof是经过原型链进行判断的,A instanceof B
,在A的原型链中层层查找,查找是否有原型等于B.prototype
,若是一直找到A的原型链的顶端,即Object.prototype.__proto__
,仍然不等于B.prototype
,那么返回false,不然返回true
❗ 小知识:
var str = 'hello world' str instanceof String → false
var str = new String('hello world') str instanceof String → true
复制代码
function test(person) {
person.age = 26;
person = {
name: 'foo',
age: 30
}
return person
}
const p1 = {
name: 'bar',
age: 25
}
const p2 = test(p1)
console.log(p1) // name:bar age:26
console.log(p2) // name:foo age:30
复制代码
解析 首先函数传参是按值传递的,即传递的是对象指针的副本,到函数内部修改参数这一步,p1的值也被修改,可是当咱们从新为person分配一个对象时,是建立了一个新的地址(指针),也就和p1没有关系了,因此最终2个变量的值不一样
在条件判断中,除了undefined、null、''、false、0、-0、NaN,其余全部值都转为true,包括空数据和对象
复制代码
对象在进行类型转换时,会调用内部的[[ToPrimitive]]函数
- 若是已是原始类型,则不须要进行转换
- 调用x.value(),若是转换为基础类型,则返回转换的值
- 调用x.toString(),若是转换为基础类型,则返回转换的值
- 若是都不返回原始类型值,则报错
重写:
let a = {
valueOf(){
// toDo
},
toString(){
// toDo
},
[Symbol.toPrimitive](){
// toDo
}
}
复制代码
❗ 小知识:
引用类型 → Number
先进行valueOf,再进行toString
引用类型 → String
先进行toString,再进行valueOf
若valueOf和toString都不存在,则返回基本类型错误,抛出TypeError
例子:
const Obj1 = {
valueOf:() => {
console.log('valueOf')
return 123
},
toString:() => {
console.log('toString')
return 'Chicago'
}
}
const Obj2 = {
valueOf:() => {
console.log('valueOf')
return {}
},
toString:() => {
console.log('toString')
return '{}'
}
}
console.log(Obj1 - 1) → valueOf 122
console.log(Obj2 - 1) → valueOf toString TypeError
复制代码
加法运算与其余有所区别
- 当运算符其中一方为字符串时,那么另外一方也转换为字符串
- 当一侧为Number类型,另外一侧为原始类型,则将原始类型转换为Number
- 当一侧为Number类型,另外一侧为引用类型,则将引用类型和Number类型转换为字符串后拼接
例子:
1 + '1' → '11'
true + true → 2
4 + [1,2,3] → '41,2,3'
Ps: 特别注意 'a'+ +'b',由于 +'b' 会等于NaN,因此结果为 'aNaN'
复制代码
- NaN:和其余类型比较永远返回false(包括本身)
- Boolean:和其余类型比较,Boolean首先被转化为Number(true → 一、false → 0)
- String和Number:String先转化为Number类型,再进行比较
- Null和undefined:null == undefined → true,除此以外,null、undefined和其余任何值比较均为false
- 原始类型和引用类型:引用类型转换为原始类型
复制代码
1)因为 ! 的优先级高于 == ,![] 首先会被转换为false,而后根据Boolean转换原则,false将会转换为Number类型,即转换为 0,而后左侧的 [] 转换为原始类型,也为 0 ,因此最终结果为 true
2)数组元素为null、undefined时,该元素被看成空字符串,因此 [undefined]、[null] 都会变为 0 , 最终 0 == false → true
包装类型即 Boolean、Number、String
与原始类型的区别:
true === new Boolean → false
123 === new Number('123') → false
'Chicago' === new String('Chicago') → false
typeof new String('Chicago') → Object
typeof 'Chicago' → string
复制代码
什么是装箱和拆箱? 装箱即原始类型转换为包装类型、拆箱即包装类型转换为原始类型
如何使用原始类型来调用方法?
原始类型调用方法,实际上自动进行了装箱和拆箱操做
var name1 = 'Chicago'
var name2 = name1.substring(2)
以上2行代码,实际上发生了3个事情
- 建立一个String的包装类实例
- 在实例上调用substring方法
- 销毁实例
复制代码
手动装箱、拆箱
var name = new Number('123')
console.log(typeof name.valueOf()) → number
console.log(typeof name.toString()) → string
复制代码
依据拆箱:
const a = {
value:[3,2,1],
valueOf:function(){
return this.value.pop()
} // 每次调用,删除一个元素
}
console.log(a == 1 && a == 2 && a == 3) // true (注意仅能判断一次)
复制代码
谁调用它,this就指向谁这句话能够说即精准又带坑
(绑定方式) 影响this的指向实际有4种:
- 默认绑定:全局调用
- 隐式调用:对象调用
- 显示调用:call()、apply()、bind()
- new绑定
复制代码
function foo(){
console.log(this.a)
}
var a = 2
foo() // 2 → this指向全局
复制代码
function foo(){
console.log(this.a)
}
var obj1 = {
a = 1,
foo
}
var obj2 = {
a = 2,
foo
}
obj1.foo() // 1 → this 指向 obj1
obj2.foo() // 2 → this 指向 obj2
复制代码
function foo(){
console.log(this.a)
bar.apply( {a:2},arguments )
}
function bar(b){
console.log(this.a + b)
}
var a = 1 // 全局 a 变量
foo(3) // 1 5 → 1 说明第一个打印种 this 指向全局,5 说明第二个打印中 this 指向 {a:2}
复制代码
❗ 小知识:
call()、apply()、bind()三者区别:
call()、apply()属于当即执行函数,区别在于接收的参数形式不一样,前者是一次传入参数,后者参数能够是数组
bind()则是建立一个新的包装函数,而且返回,它不会当即执行bind(this,arg1,arg2···)
复制代码
▲ 当call、apply、bind传入的第一个参数为 undefined/null 时,严格模式下this值为传入的undefined/null,非严格模式下,实际应用默认绑定,即指向全局(node环境下指向global、浏览器环境下指向window)
function info(){
console.log(this);
console.log(this.age);
}
var person = {
age:20,
info
}
var age = 28;
var info = person.info;
info.call(null); // window 、 28
复制代码
- 构造函数返回值不是function/object
function Super(age){
this.age = age
}
let instance = new Super('26')
console.log(instance.age) // '26'
- 构造函数返回function/object
function Super(age){
this.age = age
let obj = {
a:2
}
return obj
}
let instance = new Super('26')
console.log(instance.age) // undefined → 返回的新obj中没有age
复制代码
灵魂拷问:new 的实现原理
1 - 建立一个新对象
2 - 这个新对象会被执行[[原型]]连接
3 - 属性和方法被加入到this引用的对象里,并执行构造函数中的方法
4 - 若是函数没有返回其余对象,那么this指向这个新对象,不然this指向构造函数返回的对象
复制代码
❗ 小知识:
对于this的绑定问题,优先级以下
New > 显式绑定 > 隐式绑定 > 默认绑定
复制代码
1) 箭头函数没有本身的this
当咱们使用箭头函数的时候,箭头函数会默认帮咱们绑定外层this的值,因此在箭头函数中this的值与外层的this是同样的
例子1:
const obj = {
a: () => {
console.log(this)
}
}
obj.a() //打出来的是window
由于箭头函数默认不会使用本身的this,而是会和外层的this保持一致,最外层的this就是window对象
复制代码
例子2:
let obj = {
age:20,
info:function(){
return () => {
console.log(this.age)
}
}
}
let person = { age:28 }
let info1 = obj.info()
info1() // 20
let info2 = obj.info.call(person)
info2() // 28
复制代码
2) 箭头函数不能在call方法修改里面的this
函数的this能够经过call等显式绑定的方式修改,而为了减小this的复杂性,箭头函数没法用call()来指定this
const obj = {
a: () => {
console.log(this)
}
}
obj.a.call('123') //打出来的结果依然是window对象
复制代码
无论咱们给函数进行几回bind显式绑定,函数中的this永远由 第一次bind 决定
let a = {}
let fn = function(){
console.log(this)
}
fn.bind().bind(a)() // => Window
复制代码
红宝书上对于闭包的定义:闭包是指有权访问另一个函数做用域中的变量的函数
简单来讲,闭包就是一个函数A内部有另外一个函数B,函数B能够访问到函数A的变量和方法,此时函数B就是闭包
例子:
function A(){
let a = 1
window.B = function(){
console.log(a)
}
}
A() // 定义a,赋值window.B
B() // 1 → 访问到函数A内部的变量
复制代码
闭包存在乎义
在Js中,闭包存在的意义就是让咱们能够间接访问到函数内部的变量 (函数内部变量的引用也会在内部函数中,不会由于执行完函数就被销毁,但过多的闭包会形成内存泄漏)
function getOuter(){
var data = 'outer'
function getDate(str){
console.log(str + data) // 访问外部变量 'data'
}
return getDate('I am')
}
getOuter() // I am outer
复制代码
function getOuter(){
var date = '815';
function getDate(str){
console.log(str + date); //访问外部的date
}
return getDate; //外部函数返回
}
var today = getOuter();
today('今天是:'); //"今天是:815"
today('明天不是:'); //"明天不是:815"
复制代码
function updateCount(){
var count = 0;
function getCount(val){
count = val; // 更新外部函数变量
console.log(count);
}
return getCount; //外部函数返回
}
var count = updateCount();
count(815); //815
count(816); //816
复制代码
for (var i = 0; i <= 5; i++) {
setTimeout(function () {
console.log(i)
}, 1000 * i)
} // 6,6,6,6,6,6
for(var i = 1;i <= 5; i++){
(function(j){
setTimeout( () => {
console.log(j)
},j * 1000)
})(i)
} // 0,1,2,3,4,5
// Tips:经过let定义i也可以解决,由于let具备块级做用域
复制代码
一、首先能够经过Object.assign
来实现,Object.assgin
只会拷贝全部的属性值到新的对象中,但若是属性值是一个对象的话,拷贝的是地址,因此并非深拷贝
let obj = {
a: 1,
b:{
foo:'foo',
bar:'bar'
}
}
let objCopy = Object.assign({},obj)
console.log(objCopy) // {a:1,b:{foo:'foo',bar:'bar'}}
obj.a = 2
console.log(objCopy.a) // 1 → 不会随着obj修改而修改
obj.b.foo = 'FOO'
console.log(objCopy.b) // {foo:'FOO',bar:'bar'} → 拷贝的是地址,指向同一个值,因此修改obj.b会影响到objCopy
复制代码
二、也能够经过展开运算符...来实现浅拷贝
let obj = {
a: 1
b:{
foo:'foo',
bar:'bar'
}
}
let objCopy = { ...obj }
console.log(objCopy) // {a:1,b:{foo:'foo',bar:'bar'}}
obj.a = 2
console.log(objCopy.a) // 1
obj.b.foo = 'FOO'
console.log(objCopy.b) // {foo:'FOO',bar:'bar'}
与Object.assign同样,属性值为对象的拷贝,拷贝的是地址
复制代码
一般来讲浅拷贝能够解决大部分的问题,但若是遇到下面这种状况,就须要深拷贝来解决
let a = {
age:1,
jobs:{
first:'FE'
}
}
let b = { ...a }
a.jobs.first = 'native'
console.log(b.jobs.first) // native
复制代码
浅拷贝只能解决第一层的问题,若是对象的属性仍是对象的话,该属性二者会共享相同的地址,假如咱们不想b的对象属性随a改变而改变,就须要经过深拷贝
1 - JSON.parse(JSON.stringify(object))
let a = {
age: 1,
jobs: { first: 'FE' }
}
let b = JSON.parse(JSON.stringify(a))
a.jobs.first = 'native'
console.log(b.jobs.first) // FE
复制代码
2 - lodash库中的cloneDeep()
复制代码
1 - var【声明变量】
var 没有块的概念,能够跨块访问,没法跨函数访问
2 - let【声明块中的变量】
let 只能在块做用域里访问,不能跨块访问,更不能跨函数访问
3 - const【声明常量,一旦赋值便不可修改】
const 只能在块级做用域里访问,并且不能修改值
Tips: 这里的不能修改,并非变量的值不能改动,而是变量所指向的那个内存地址保存的指针不能改动
复制代码
❗ 小知识:
var a = 1
let b = 1
const c = 1
console.log(window.a) // 1
console.log(window.b) // undefined
console.log(window.c) // undefined
在全局做用域下使用let和const声明变量,变量并不会被挂载到window上,这一点与var不一样
关于const,还有两个注意点:
- const声明以后必须立刻赋值,不然报错
- const简单类型一旦声明就不能修改,而复杂类型(数组,对象)指针指向的地址不能修改,但内部数据能够修改
复制代码
console.log(a) // undefined
var a = 1
复制代码
上面两行代码,虽然在打印a前变量并无被声明,可是却可使用这个未声明的变量,不报错,这一种状况就叫作提高,并且提高的是声明
实际上,提高不只仅只能做用于变量的声明,函数的声明也会被提高
console.log(a) // f a(){}
function a(){}
var a = 1
复制代码
函数的声明优先级高于变量的声明
console.log(a) // ReferenceError: Cannot access 'a' before initialization
let a
复制代码
为什么此次就会报错呢? 只要一进入当前做用域,所要使用得变量就已经存在了,可是不可获取,只有等到声明变量的那一行代码出现,才能够获取和使用该变量,这就是暂时性死区
var a = 123; // 声明
if (true) {
a = 'A'; // 报错 由于本块级做用域有a声明变量
let a; // 绑定if这个块级的做用域 不能出现a变量
}
复制代码
对于暂时性死区,个人理解是声明提高了,但初始化没有被提高,而提高是声明提高,并初始化为undefined
函数提高优于变量提高,函数提高会把整个函数提高到做用域顶部,变量提高只会把声明提高到做用域顶部
每个JavaScript对象(null除外)在建立的时候就会有一个与之关联的对象,这个对象就是原型对象
每个对象都会从原型上继承属性
复制代码
new
来建立一个对象的函数new
建立出来的对象,即是实例实例经过__proto__指向原型,经过constructor指向构造函数
复制代码
以Object
为例子,Object
即是一个构造函数,咱们经过它来构建实例
const instance = new Object()
复制代码
这里,instance
是Object
的实例,Object
是instance
的构造函数,构造函数拥有一个prototype
的属性来指向原型,所以有
const prototype = Object.prototype
复制代码
原型、构造函数、实例 三者关系
实例.__proto__ === 原型
原型.constructor === 构造函数
构造函数.prototype === 原型
Tips:
const instance = new Object()
instance.constructor === Object // true
当获取 实例.constructor 时,其实实例上并无constructor属性,当不能读到constructor属性时,会从实例的原型中读取
则有 instance.constructor === instance.__proto__.constructor
若是修改了instance.__proto__,instance.constructor将再也不为Object
instance.__proto__ = null
instance.constructor === Object // false
复制代码
原型链是由相互关联的原型对象组成的链状结构
每一个对象都有__proto__
属性(构造函数也有),指向建立该对象的构造函数的原型 ,__proto__
将对象连接起来组成了原型链。是一个能够用来实现继承和共享属性的有限链
Object.prototype
(Object.prototype.__proto__ === null
),假如仍是没有找到,则输出undefinedprototype
属性(b.prototype.B = 1
),但这样修改会致使全部继承于这个对象的实例的属性发生改变function Parent(value){
this.val = value // 实例属性
}
Parent.prototype.getValue = function(){ // 原型属性
console.log(this.val)
}
function Child(value){
Parent.call(this,value) // 借用构造函数来继承实例属性
}
Child.prototype = new Parent() // 原型链继承
const child = new Child(1)
child.getValue() // 1
child instancof Parent // true
复制代码
这种方式优势在于构造函数能够传参,不会与父类共享引用属性,能够复用父类的函数,但缺点就是在继承父类函数的时候调用父类构造函数,致使子类的原型上会多了不须要的父类属性,存在内存浪费
function Parent(value){
this.val = value
}
Parent.prototype.getValue = function(){
console.log(this.val)
}
function Child(value){
Parent.call(this,value)
}
Child.prototype = Object.create(Parent.prototype,{
constructor:{
value:Child,
enumerable:false.
writable:true,
configurable:true
}
}) // 将父类的原型赋值给子类,并将原型的constructor修改成子类
const child = new Child(1)
child.getValue() // 1
child instanceof Parent // true
复制代码
这种寄生组合继承是对组合继承进行优化的,核心就是将父类的原型赋值给子类,而且将构造函数设置为子类,这样解决了无用的父类属性问题,还能正确的找到子类的构造函数
其实JavaScript中并不存在类的概念,class只是一种语法糖,本质来讲仍是函数
class Person{}
Person instanceof Function // true
复制代码
Class继承
在ES6中,咱们能够经过class
实现继承
class Parent{
constructor(value){
this.val = value
}
getValue(){
console.log(this.val)
}
}
class Child extends Parent{
constructor(value){
super(value)
}
}
let child = new Child(1)
child.getValue() // 1
child instanceof Parent // true
复制代码
Class实现继承,核心在于使用extends
关键字来代表继承自哪一个父类,而且在子类构造函数中必须调用super
说到做用域,咱们要先理解什么是执行上下文
执行上下文 能够简单理解为一个对象,它具备三个类型:
eval()
执行上下文经过代码执行过程来理解执行上下文
push
到执行栈顶层active EC
,而后开始执行函数中的代码,全局执行上下文(caller)被挂起pop
移除出执行栈,控制权交回给全局执行上下文(caller),继续按照自上而下的顺序执行代码❗ 小知识:
变量对象,是执行上下文中的一部分,能够抽象为一种数据做用域
其实也能够理解为一个简单的对象,存储着该执行上下文中的全部变量和函数声明(不包括函数表达式)
活动对象(AO)- 当变量对象所处的上下文被激活时(active EC)时,称为活动对象
复制代码
做用域能够理解为当前上下文中声明的变量和函数的做用范围,它规定了如何查找变量,也就是当前执行代码对变量的访问权限
做用域能够分为 块级做用域 和 函数做用域
做用域特性:
let a = function(){
console.log(1)
}
(function a(){
a = 'a'
console.log(a)
})() // ƒ a() { a = 'a' ; console.log(a) }
复制代码
做用域链能够理解为一组对象列表,由当前环境与上层环境的一系列变量对象组成,所以咱们能够经过做用域链访问到父级里面声明的变量或者函数
做用域链由两部分组成
[[scope]]
属性:指向父级变量对象和做用域链,也就是包含了父级的[[scope]]
和活动变量(AO)
因为[[scope]]
包含父级[[scope]]
造成链状关系,便自上而下造成链式做用域
做用域链做用
保证当前执行环境里,有权访问的变量和函数是有序的(做用域链的变量只能向上访问变量,访问到window对象时被终止)
Ps:做用域链不容许向下访问
做用域链和原型继承查找时的区别 - 若是去查找一个普通对象的属性,可是在当前对象和其原型中都找不到时,会返回undefined
;但查找的属性在做用域链中不存在的话就会抛出ReferenceError
<script>
标签引入<script>
标签<script defer>
延迟加载,元素解析完毕后执行<script async>
异步加载,但执行时会阻塞元素渲染typeof null // 'Object'
null 是一个空对象,没有任何的属性和方法
typeof undefined // 'undefined'
undefined 是一个表示'无'的原始值或表示缺乏值,例如变量被声明了,但没有任何赋值时
复制代码
从内存来看null和undefined,本质区别是什么?
内存泄漏 在使用一些内存以后,若是后面再也不须要用到这些内存,但没有将它们及时释放掉,就称为内存泄漏
若是出现严重的内存泄漏,那么有可能使得内存愈来愈大,最终致使浏览器崩溃
未定义的变量会在全局对象建立一个新变量
function foo(arg){
bar = 'I am belong to global' // 未定义,会建立在全局中
}
解决方案:
- 在JavaScript头部加上`use strict`,使用严格模式避免意外的全局变量
复制代码
定义定时器(setTimeout / setInterval)后没有移除(clearTimeout / clearInterval)
复制代码
闭包的关键是匿名函数能够访问父级做用域的变量,让变量不被回收
若是不及时清除,就会致使内存泄漏
复制代码
经过GC垃圾回收机制来解决内存泄漏
所谓垃圾回收机制,是指找到内存空间中的垃圾并回收,能够再次利用这部份内存空间
垃圾回收机制有两种方式:
在JavaScript
中,咱们能够采用多种方式实现循环
while
do...while
for
for...in
for...of
- 遍历对象及其原型链上可枚举的属性
- 若是用于遍历数组,除了遍历其元素外,还会遍历数组对象自定义的可枚举属性及其原型链上的可枚举属性
- 遍历对象返回的属性名和遍历数组返回的索引都是字符串索引
- 某些状况下,可能按随机顺序遍历数组元素
复制代码
- es6 中添加的循环遍历语法
- 支持遍历数组,类数组对象(DOM NodeList),字符串,Map 对象,Set 对象
- 不支持遍历普通对象
- 遍历后输出的结果为数组元素的值
- 可搭配实例方法 entries(),同时输出数组的内容和索引
复制代码
- 返回对象自身可枚举属性组成的数组
- 不会遍历对象原型链上的属性以及 Symbol 属性
- 对数组的遍历顺序和 for in 一致
复制代码
Tips: for in
更适合遍历对象,尽可能不用for in
来遍历数组
迭代相关
对每一项运行给定函数,全true则返回true
复制代码
对数组中每一项运行函数,返回该函数会返回true项
复制代码
对数组每一项运行函数,没有返回值 (forEach没法中途跳出forEach循环,break、continue和return都不奏效。)
复制代码
对每一项运行函数,返回每次函数调用的结果组成的数组
复制代码
对每一项运行函数,若是对任一项返回了true,则返回true
复制代码
其余
经过指定链接符生成字符串
复制代码
数组尾部推入和弹出,改变原数组,返回操做项
复制代码
数组头部弹出和推入,改变原数组,返回操做项
复制代码
数组排序(fn定义排序规则)与反转,改变原数组
复制代码
链接数组,不改变原数组,返回新数组(浅拷贝)
复制代码
截断数组,返回截断后的新数组,不改变原数组
复制代码
从下标start开始,删除number个元素,并插入arg,返回所删除元素组成的数组,改变原数组
复制代码
查找数组元素,返回下标索引
复制代码
归并数组,prev为累计值,cur为当前值,defaultPrev为初始值
复制代码
链接字符串
复制代码
检索字符串、从后向前检索字符串
复制代码
找到一个或多个正则表达式的匹配
替换与正则表达式匹配的子串
检索与正则表达式匹配的值
复制代码
截取字符串片断,并在新的字符串中返回被截取的片断
复制代码
从起始索引号提取字符串中指定数目的字符
复制代码
截取字符串中两个指定的索引号之间的字符。
复制代码
用于把一个字符串经过指定的分隔符进行分隔成数组
复制代码
返回字符串
复制代码
返回某个字符串对象的原始值
复制代码
做用:生成一个数组,遍历原数组,将每个元素拿出来作一些变化后存入新数组
[1,2,3].map(item => item + 1) // [2,3,4]
另外map的回调接收三个参数,分别是当前元素、索引,原数组
常见题:['1','2','3'].map(parseInt) 结果是什么?
['1','2','3'].map(parseInt) → [1,NaN,NaN]
解析:
第一轮遍历 parseInt('1',0) // 1
第二轮遍历 parseInt('2',1) // NaN
第三轮遍历 parseInt('3',2) // NaN
复制代码
做用:生成一个新数组,在遍历数组的时候将返回值为true的元素放在新数组
场景:咱们能够利用这个函数删除一些不须要的元素(过滤)
let array = [1,2,3,4,5]
let newArray = array.filter(item => item !== 5)
console.log(newArray) // [1,2,3,4]
Tips:与map一致,也接收3个参数
复制代码
做用:将数组中的元素经过回调函数最后转换为一个值(归并)
场景:实现一个将数组里的元素所有相加获得一个值的功能
const arr = [1,2,3]
const sum = arr.reduce((acc,current) => {
return acc + current
},0)
console.log(sum) // 6
对于reduce来讲,它只接受2个参数,分别是回调函数和初始值:
- 首先初始值为0,该值会在执行第一次回调函数时做为第一个参数传入
- 回调函数接收四个参数,分别为累计值、当前元素、当前索引、原数组
- 在第一次执行回调时,当前值和初始值相加为1,该结果会做为第二次回调时的累计值(第一个参数)传入
- 第二次执行时,相加的值分别为1和2,以此类推,循环结果后得出最终值
array.reduce(function(total, currentValue, currentIndex, arr), initialValue)
- total 必选,初始值或计算结束后的返回值
- currentValue 必选,当前元素
- currentIndex 可选,当前索引
- arr 可选,当前元素所属的数组对象
- initialValue 可选,传递给函数的初始值
复制代码
mixin
extend
call/apply
二者区别在于加载方式不一样、规范不一样
require('./a')() // 假设a模块是一个函数,当即执行a模块函数
var data = reuqire('./a').data // 假设a模块导出一个对象
Tips:require写在代码哪一行均可以
import Jq from 'jquery'
import * as _ from '_'
import {a,b,c} from './a'
import {default as alias, a as a_a, b, c} from './a';
Tips:import应该用在代码行开头
复制代码
❗ 小知识:
require特色:
- 提供了服务器/浏览器的模块加载方案。非语言层面的标准。
- 只能在运行时肯定模块的依赖关系及输入/输出的遍历,没法进行静态优化
import特色:
- 语言规格层面支持模块功能。
- 支持编译时静态分析,动态绑定
复制代码
NaN与任务值比较都是false,包括他本身,判断一个变量为NaN,能够经过isNaN()
isNaN(NaN) // true
复制代码
- 消除JavaScript语法的一些不合理、不严谨之处,减小一些怪异行为
- 消除代码运行的一些不安全行为,保证代码运行的安全
- 提升编译器效率,增长运行速度
- 为将来新版本的JavaScript作好铺垫
复制代码
- 严格模式下,delete运算符后跟随非法标识符,会抛出语法错误
- 严格模式下,定义同名属性会抛出语法错误
- 严格模式下,函数形参存在同名,会抛出语法错误
- 严格模式下,不容许八进制整数直接量
- 严格模式下,arguments对象是传入函数内实参列表的静态副本
- 严格模式下,eval和arguments当作关键字,它们不能被赋值和用做变量声明
- 严格模式下,变量必须先声明,直接给变量赋值,不会隐式建立全局变量,不能用with
- 严格模式下,call/apply第一个参数为null/undefined,不会被转换为window
复制代码
JavaScript
中的变量是松散类型,所谓松散类型,就是指当一个变量被声明出来就能够保存任何类型的值,一个变量所保存值的类型也能够改变
若是事件处理函数(click)调用频率无限制,会加剧浏览器的负担,致使用户体验很是糟糕,那么咱们能够采用debounce(防抖) 和 throttle(节流) 的方式来减小调用频率,同时又不影响实际效果
例如:
🔺当持续触发scroll事件时,事件处理函数handle只在中止滚动1000毫秒以后才会调用一次,即持续触发滚动事件的过程当中,handle一直没有执行
复制代码
function debounce(handle){
let timeout = null // 建立一个标记用来存放定时器
return function(){
clearTimeout(timeout) // 每当用户调用的时候把前一个定时器清空
timeout = setTimeout(() => {
handle.apply(this,arguments)
},500) // 500ms后触发,期间再次调用,则从新计算延时
}
}
function sayHi(){
console.log('防抖成功')
}
var btn = document.getElementById('button')
btn.addEventListener('click',debounce(sayHi))
复制代码
function throttle(handle){
let canRun = true // 经过闭包保存一个标记,不被回收
return function(){
if(!canRun) return // 在函数头部判断标记是否为true,为false时不容许调用handle
canRun = false // 设置标记为false
setTimeout(() => { // 将外部传入的函数的执行放在setTimeout中
fn.apply(this, arguments)
// 最后在setTimeout执行完毕后再把标记设置为true(关键)表示能够执行下一次循环了。当定时器没有执行的时候标记永远是false,在开头被return掉
canRun = true
}, 500);
}
}
function sayHi() {
console.log('节流成功');
}
var btn = document.getElementById('button');
btn.addEventListener('click', throttle(sayHi)); // 节流
复制代码
哪些事件不支持冒泡?
- 鼠标事件:mouseleave、mouseenter
- 焦点事件:blur、focus
- UI事件:scroll、resize
- ···
复制代码
对于同一个事件,监听捕获和冒泡,分别对应相应的处理函数,监听到捕获事件时,先暂停执行,直到冒泡事件被捕获后再执行事件
非IE浏览器:event.stopPropagation()
IE浏览器:window.event.cancelBubble = true
function stopBubble(e){
// 若是提供了事件对象,则是非IE浏览器下
if(e && e.stopPropagation){
// 所以它支持W3C的stopPropagation()方法
e.stopPropagation()
}else{
// IE浏览器下,取消事件冒泡
window.event.cancelBubble = true
}
}
复制代码
非IE浏览器:event.preventDefault()
IE浏览器:window.event.returnValue = false
function stopDefault(e) {
//阻止默认浏览器动做(W3C)
if (e && e.preventDefault) e.preventDefault();
//IE中阻止函数器默认动做的方式
else window.event.returnValue = false;
return false;
}
复制代码
所谓事件委托,就是利用事件冒泡的原理,让本身所触发的事件,让其父元素代替执行
即:不在事件(直接DOM)上设置监听函数,而是在其父元素上设置监听函数,经过事件冒泡,父元素能够监听到子元素上事件的触发,经过判断事件发生在哪个子元素上来作出不一样的响应
<ul>
<li>苹果</li>
<li>香蕉</li>
<li>凤梨</li>
</ul>
// 在ul上设置监听函数(Good)
document.querySelector('ul').onclick = (event) => {
let target = event.target
if (target.nodeName === 'LI') {
console.log(target.innerHTML)
}
}
// 在每个li上监听函数(Bad)
document.querySelectorAll('li').forEach((e) => {
e.onclick = function() {
console.log(this.innerHTML)
}
})
复制代码
- 事件冒泡:父元素下不管是什么元素,点击后都会触发 box 的点击事件
- 事件委托:能够对父元素下的元素进行筛选
复制代码
高阶函数(Highter-order-function)的定义很简单,就是至少知足下列一个条件的函数:
也就是说高阶函数是对其余函数进行操做的函数,能够将它们做为参数传递,或者是返回它们。
Array.prototype.map
、Array.prototype.filter
、Array.prototype.reduce
···,它们接受一个函数做为参数,并应用这个函数到列表的每个元素对比使用高阶函数和不使用高阶函数
例子:有一个数组[1,2,3,4],咱们想要生成一个新数组,其元素是以前数组的两倍
- 不使用高阶函数
const arr1 = [1,2,3,4]
const arr2 = []
for(let i = 0; i < arr1.length; i++){
arr2.push(arr1[i] * 2)
}
- 使用高阶函数
const arr1 = [1,2,3,4]
const arr2 = []
arr2 = arr1.map( item => item * 2)
复制代码
在判断类型的时候能够同个Object.prototype.toString.call来获取对应对象返回的字符串,如:
let isString = obj => Object.prototype.toString.call( obj ) === '[object String]'
let isArray = obj => Object.prototype.toString.call( obj ) === '[object Array]'
let isNumber = obj => Object.prototype.toString.call( obj ) === '[object Number]'
能够发现这三行有许多重复代码,只须要把具体的类型抽离出来就能够封装成一个判断类型的方法,如:
let isType = (type, obj) => {
return Object.prototype.toString.call(obj) === '[Object ' + type + ']'
}
isType('String')('123'); // true
isType('Array')([1, 2, 3]); // true
isType('Number')(123); // true
复制代码
柯里化 - 简单来讲就是只传递函数一部分参数来调用它,让它返回一个新函数去处理剩下的参数。
经过add()函数来了解柯里化
const add = (...args) => args.reduce( (a, b) => a + b ) // a为初始值或计算结束的返回值,b为当前元素
// 传入多个参数,执行add函数
add(1,2) // 3
// 假设咱们实现了一个柯里化函数,支持一次传入一个参数
let sum = currying(add)
// 封装第一个参数,方便重用
let addCurryOne = sum(1)
addCurryOne(2) // 3
addCurryOne(3) // 4
复制代码
currying
函数咱们能够理解所谓的柯里化函数,就是封装一系列的处理步骤,经过闭包将参数集中起来计算,最后再把须要处理的参数传进去,那么如何实现currying
函数呢?
实现原理就是用闭包把传入的参数保存起来,当传入参数的数量足够执行函数时,就开始执行函数
实现一个健壮的currying函数:
function currying(fn, length) {
length = length || fn.length // 第一次调用获取函数 fn 参数的长度,后续调用获取 fn 剩余参数的长度
return function( ...args ) { // currying 包裹以后返回一个新函数,接收参数为 ...args
return args.length >= length // 新函数接收的参数长度是否大于等于 fn 剩余参数须要接收的长度
? fn.apply(this, args) // 知足要求,执行 fn 函数,传入新函数的参数
: currying(fn.bind(this, ...agrs), length - args.length) // 不知足要求,递归 currying 函数,新的 fn 为 bind 返回的新函数(bind 绑定了 ...args 参数,未执行),新的 length 为 fn 剩余参数的长度
}
}
// Test
const fn = currying(function(a,b,c) {
console.log([a, b, c])
})
fn("a", "b", "c") // ["a", "b", "c"]
fn("a", "b")("c") // ["a", "b", "c"]
fn("a")("b")("c") // ["a", "b", "c"]
fn("a")("b", "c") // ["a", "b", "c"]
复制代码
延迟计算:
const add = (...args) => args.reduce((a, b) => a + b);
// 简化写法
function currying(func) {
const args = [];
return function result(...rest) {
if (rest.length === 0) {
return func(...args);
} else {
args.push(...rest);
return result;
}
}
}
const sum = currying(add);
sum(1,2)(3); // 未真正求值
sum(4); // 未真正求值
sum(); // 输出 10
复制代码
Array.from(new Set(arr.flat(Infinity))).sort((a,b)=>{ return a-b}
复制代码
解析:
array.from - 从一个类数组或可迭代对象建立一个新的,浅拷贝的数组实例
array.flat - 用于将嵌套的数组'拉平'(扁平化)
[1,2,[3,4]].flat() // [1,2,3,4]
array.sort - 用于对数组的元素进行排序
- 该函数要比较两个值,而后返回一个用于说明这两个值的相对顺序的数字。
比较函数应该具备两个参数 a 和 b,其返回值以下:
- 若 a 小于 b,在排序后的数组中 a 应该出如今 b 以前,则返回一个小于 0 的值。
- 若 a 等于 b,则返回 0
- 若 a 大于 b,则返回一个大于 0 的值。
1 - arr.flat(Infinity) 先将数组扁平化
2 - new Set(arr.flat(Infinity)) 去重扁平化后的数组
3 - Array.from(new Set(arr.flat(Infinity))) 建立一个新的,浅拷贝的数组实例
4 - Array.from(new Set(arr.flat(Infinity))).sort((a,b)=>{ return a-b } 将该新数组进行升序排序
复制代码
createDocumentFragment() - 建立一个DOM片断
createElement() - 建立一个具体的元素
createTextNode() - 建立一个文本节点
复制代码
appendChild() - 添加子节点
removeChild() - 移除子节点
replaceChild() - 替换子节点
insertBefore() - 插入
复制代码
getElementsByTagName() - 经过标签名称
getElementsByName() - 经过元素的Name属性
getElementById() - 经过元素id,具备惟一性
复制代码
Javascript
是一种解释型语言,C、C++
等语言先编译后执行,而Javascript
是在程序的运行过程当中逐行进行解释Javascript
是一种基于对象的脚本语言,它不只能够建立对象,也能使用现有的对象Javascript
语言中采用的是弱类型的变量类型,对使用的数据类型未作出严格的要求,是基于Java
基本语句和控制的脚本语言Javascript
是一种采用事件驱动的脚本语言,它不须要通过Web服务器就能够对用户的输入作出响应Javascript
不依赖于操做系统,仅须要浏览器的支持/**
* 兼容低版本IE,element为须要绑定事件的元素,
* eventName为事件名(保持addEventListener语法,去掉on),fun为事件响应函数
*/
function addEvent(element, eventName, fun){
if(element.addEventListener){
element.addEventListener(eventName, fun, false)
}else{
ele.attachEvent('on' + eventName, fun)
}
}
复制代码
sort()内部是利用递归进行冒泡排序的
- 比较相邻的元素,若是第一个比第二个大,就交换它们两个
- 对每一对相邻元素作一样的工做,从开始第一对到结尾的最后一对,在这一点,最后的元素应该会是最大的数
- 针对全部的元素重复以上的步骤,除了最后一个
- 持续每次对愈来愈少的元素重复上面的步骤,知道没有任何一对数字须要比较
复制代码
var arr = [1,5,4,2]
sort()的比较逻辑为:
- 1和5比,1和4比,1和2比
- 5和4比,5和2比
- 4和2比
复制代码
- return > 0 则交换数组相邻2个元素的位置
- arr.sort(function(a,b){ ... })
- a → 表明每一次执行匿名函数时,找到数组中的当前项
- b → 表明当前项的后一项
复制代码
1 - 升序
var arr = [45, 42, 10, 147, 7, 65, -74]
console.log(arr.sort()) → 默认法 缺点:默认排序顺序是根据字符串UniCode码。由于排序是按照字符串UniCode码的顺序进行排序的(按首位排序)
// [-74, 10, 147, 42, 45, 65, 7]
console.log(
arr.sort(function(a, b) {
return a - b; // 若return返回值大于0(即a>b),则a,b交换位置
}) → 指定排序规则,return可返回任何值
)
// [-74, 7, 10, 42, 45, 65, 147]
2 - 降序
var arr = [45, 42, 10, 111, 7, 65, -74];
console.log(
arr.sort(function(a, b) {
return b - a; // 若return返回值大于零(即b>a),则a,b交换位置
}) → 指定排序规则,return可返回任何值
);
复制代码
经过判断Global对象是否为window,若是不为window,则当前脚本没有运行在浏览器中
复制代码
var a = [1, 2, 3, 5];
alert(Math.max.apply(null, a)); //最大值
alert(Math.min.apply(null, a)); //最小值
之因此须要用到apply,是由于 Math.max / Math.min 不支持传递数组过去
复制代码
offsetWidth → 返回元素的宽度(包括元素宽度、内边距和边框,不包括外边距)
offsetHeight → 返回元素的高度(包括元素高度、内边距和边框,不包括外边距)
clientWidth → 返回元素的宽度(包括元素宽度、内边距,不包括边框和外边距)
clientHeight → 返回元素的高度(包括元素高度、内边距,不包括边框和外边距)
style.width → 返回元素的宽度(包括元素宽度,不包括内边距、边框和外边距)
style.height → 返回元素的高度(包括元素高度,不包括内边距、边框和外边距)
scrollWidth → 返回元素的宽度(包括元素宽度、内边距和溢出尺寸,不包括边框和外边距),无溢出的状况,与clientWidth相同
scrollHeigh → 返回元素的高度(包括元素高度、内边距和溢出尺寸,不包括边框和外边距),无溢出的状况,与clientHeight相同
offsetTop → 返回元素的上外缘距离最近采用定位父元素内壁的距离,若是父元素中没有采用定位的,则是获取上外边缘距离文档内壁的距离。
offsetLeft → 此属性和offsetTop的原理是同样的,只不过方位不一样
scrollLeft → 此属性能够获取或者设置对象的最左边到对象在当前窗口显示的范围内的左边的距离,也就是元素被滚动条向左拉动的距离。
scrollTop → 此属性能够获取或者设置对象的最顶部到对象在当前窗口显示的范围内的顶边的距离,也就是元素滚动条被向下拉动的距离。
在函数调用的时候,浏览器每次都会传递进两个隐式参数,一个是函数的上下文对象this
,另一个则是封装实参的伪数组对象arguments
关于arguments
arguments
定义是对象,可是由于对象的属性是无序的,而arguments
是用来存储实参的,是有顺序的,它具备和数组相同的访问性质及方式,并拥有数组长度属性length
(类数组对象、用来存储实际传递给函数的参数)arguments
访问单个参数的方式与访问数组元素的方式相同,例如arguments[0]
、arguments[1]
、arguments[n]
,在函数中不须要明确指出参数名,就能访问它们。经过length
属性能够知道实参的个数。arguments
有一个callee
属性,返回正被执行的Function
对象function fun() {
console.log(arguments.callee === fun); // true
}
fun();
复制代码
arguments
对象是容许在运行时进行修改function fun() {
arguments[0] = 'sex';
console.log(arguments[0]); // sex
}
fun('name', 'age');
复制代码
一行代码实现伪数组arguments转换为数组
var args = [].slice.call(arguments)
复制代码
Javascript
做为主要运行在浏览器的脚本语言,主要用途之一就是操做Dom
若是Javascript
同时有两个线程,同时对同一个Dom
进行操做,这时浏览器应该听哪一个线程的,又如何判断优先级?
为了不这种问题,Javascript
必须是一门单线程语言
因为Javascript
是单线程语言,当遇到异步任务(如Ajax
)时,不可能一直等到异步执行完成后,再继续往下执行,由于这段时间浏览器处于空闲状态,会致使巨大的资源浪费
当执行某个函数、事件(指定过回调函数)时,就会进入执行栈中,等待主线程读取
执行栈可视化:
主线程与执行栈不一样,主线程规定了如今执行执行栈中的哪一个事件
主线程循环: 即主线程会不停的从执行栈中获取事件,而后执行完全部栈中的同步代码
当遇到一个异步事件后,并不会一直等待异步事件返回结果,而是会将这个事件挂在与执行栈不一样的队列中,这个队列称为任务队列TaskQueue
当主线程将执行栈中的全部代码都执行完后,主线程将会去查看任务队列中是否存在任务。 若是存在,那么主线程会依次执行那些任务队列中的回调函数
Javascript
异步执行的运行机制TaskQueue
)。只要异步任务有了返回结果,就在任务队列之中放置一个事件异步任务能够分为两类,不一样类型的API注册的任务会依次进入到各自对应的队列中,而后等待事件循环(EventLoop
)将它们依次压入执行栈中执行
MacroTask
script(总体代码),setTimeout,setInterval,setImmediate,UI渲染,I/O流操做,postMessage,MessageChannel
复制代码
MicroTask
Promise,MutaionObserver,process.nextTick
复制代码
Event Loop(事件循环)中,每一次循环称为 tick, 每一次tick的任务以下:
综上所述
宏任务 → 全部微任务 → 下一个宏任务
复制代码
题 1:
setTimeout(function () {
console.log(1)
});
new Promise(function(resolve,reject){
console.log(2)
resolve(3)
}).then(function(val){
console.log(val)
})
console.log(4)
复制代码
Result:
2 → 4 → 3 → 1
复制代码
题 2:
new Promise(resolve => {
resolve(1);
Promise.resolve().then(() => {
// t2
console.log(2)
});
console.log(4)
}).then(t => {
// t1
console.log(t)
});
console.log(3);
复制代码
Result:
4 → 3 → 2 → 1
复制代码
解析:
- script任务运行,首先遇到Promise实例,执行构造函数,输出4,此时微任务队列中有t2和t1
- script任务继续执行同步代码,输出3后第一个宏任务执行完成
- 执行全部的微任务,即输出2和1
??? 为何t2会比t1先执行 ???
- 根据 Promises/A+ 规范
- Promise.resolve 方法容许调用时不带参数,直接返回一个resolved 状态的 Promise 对象
- 当即 resolved 的 Promise 对象,是在本轮“事件循环”(event loop)的结束时,而不是在下一轮“事件循环”的开始时
复制代码
Javascript
是单线程工做,也就是只有一个脚本执行完以后才能够执行下一个脚本,两个脚本不能同时执行,那么若是脚本耗时很长,后面的脚本都必须排队等待,会拖延整个程序的执行
异步编程的六种方式
function f1(cb){
setTimeout(() => {
console.log('f1')
})
cb()
}
function f2(){
console.log('f2')
}
f1(f2) // f2 → f1
复制代码
function fn(a,b,cb){
var num = Math.ceil(Math.random() * (a - b) + b)
cb(num)
}
fn(10,20,function(num){
console.log("随机数" + num);
}) // 10 ~ 20 的随机数
复制代码
总结:
- 回调函数易于实现,便于理解,可是屡次回调会致使代码高度耦合
- 回调函数定义:函数A做为参数(函数引用)传递到另一个函数B,而且这个函数B执行函数A,咱们就叫函数A叫作回调函数,若是没有名称(函数表达式),咱们就叫它匿名回调函数
- 回调函数优势:简单,容易理解
- 回调函数缺点:不利于代码的阅读和维护,各部分之间高度耦合,并且每个任务只能指定一个回调函数
复制代码
监听函数有:on、bind、listen、addEventListener、observe
复制代码
优势:容易理解,能够绑定多个事件,每个事件能够接收多个回调函数,并且能够减小耦合,利于模块化
缺点:整个程序都要变成事件驱动型,运行流程会变得不清晰
复制代码
element.onclick = function(){
// toDo
}
Or:
element.onclick = handler1
element.onclick = handler2
element.onclick = handler3
缺点:
当同一个element元素绑定多个事件时,只有最后一个事件会被添加,上述只有handler3会被添加执行
复制代码
elment.attachEvent("onclick", handler1)
elment.attachEvent("onclick", handler2)
elment.attachEvent("onclick", handler3)
Result: 3 → 2 → 1
elment.addEventListener("click", handler1, false)
elment.addEventListener("click", handler2, false)
elment.addEventListener("click", handler3, false)
Result:1 → 2 → 3
(PS:该方法的第三个参数是泡沫获取,是一个布尔值:当为false时表示由里向外,true表示由外向里。)
复制代码
DOM - addEventListener()和removeListener()
addEventListenner()和removeListenner()表示用来分配和删除事件的函数。这两种方法都须要三种参数,分别为:
- 事件名称(String)
- 触发事件的回调函数(function)
- 指定事件的处理函数的时期或阶段(boolean)
复制代码
定义了一种一对多的关系,让多个观察者同时监听某一个主题对象,这一个主题对象一旦发生状态变化,就会通知全部观察者对象,使得它们可以自动更新本身
优势:
- 支持简单的广播通讯,自动通知全部已经订阅过的对象
- 页面载入后,目标对象很容易与观察者存在一种动态关联,增长灵活性
- 目标对象与观察者之间的抽象耦合关系可以单独扩展以及重用
复制代码
- promise对象是commonJS工做组提出的一种规范,一种模式,目的是为了异步编程提供统一接口
- promise是一种模式,promise能够帮忙管理异步方式返回的代码。他将代码进行封装并添加一个相似于事件处理的管理层。咱们可使用promise来注册代码,这些代码会在在promise成功或者失败后运行
- promise完成以后,对应的代码也会执行。咱们能够注册任意数量的函数再成功或者失败后运行,也能够在任什么时候候注册事件处理程序
- promise有两种状态:一、等待(pending);二、完成(settled)
- promise会一直处于等待状态,直到它所包装的异步调用返回/超时/结束
- 这时候promise状态变成完成。完成状态分红两类:一、解决(resolved);二、拒绝(rejected)
- promise解决(resolved):意味着顺利结束。promise拒绝(rejected)意味着没有顺利结束
复制代码
Javascript
只能与同一个域中的页面进行通信
两个脚本被认为是同源的条件:
XMLHttpRequest
对象实现的AJAX请求那样受到同源策略的限制,jsonP能够实现跨越同源策略XMLHttpRequest
或ActiveX
的支持callback
的方式回传结果。将回调方法的权限给了调用方。GET
请求而不支持POST
等其余类型的HTTP请求AJAX
是一种建立交互式网页应用的网页开发技术AJAX
能够实如今没必要刷新整个页面的状况下实现局部更新,与服务器进行异步通信的技术XMLHttpRequest
对象XMLHttpRequest
对象能够说是AJAX
的核心对象,是一种支持异步请求的技术。即XMLHttpRequest
使你可使用javascript
向服务器提出请求并作出响应,又不会致使阻塞用户。经过XMLHttpRequest
对象,能够实如今不刷新整个页面的状况下实现局部更新
XMLHttpRequest
对象的常见属性onreadystatechange
- 一个Js函数对象,当readyState属性改变时会调用它(请求状态改变的事件触发器)readyState
- Http请求的状态,当一个XMLHttpRequest
初次建立时,这个属性的值从0开始,直到接收到完整的Http响应,这个值增长到4
XMLHttpRequest
对象已建立或已被abort()
方法重置。open()
方法已调用,可是send()
方法未调用。请求尚未被发送send()
方法已调用,HTTP 请求已发送到 Web 服务器,但未接收到响应readyState 的值不会递减,除非当一个请求在处理过程当中的时候调用了 abort() 或 open() 方法
每次这个属性的值增长的时候,都会触发 onreadystatechange 事件句柄。
复制代码
status
- 由服务器返回的 HTTP 状态代码,如 200 表示成功,而 404 表示 "Not Found" 错误。当 readyState 小于 3 的时候读取这一属性会致使一个异常。关于Http状态码,常见以下:
1) 1XX 通知
2) 2XX 成功
3) 3XX 重定向
4) 4XX 客户端错误
5) 5XX 服务端错误
最基本的响应状态:
- 200('ok') : 服务器已成功处理了请求
- 400('bad request'):服务器没法解析该请求
- 500('Internal Server Error'):服务器内部错误服务器遇到错误,没法完成请求
- 301('Moved Permanently'):永久移动请求的网页已永久移动到新位置,即永久重定向
- 404('Not Found'):未找到服务器找不到请求的网页
- 409('Conflict'):服务器在完成请求时发生冲突
复制代码
XMLHttpRequest
对象的常见APIOpen()
- 建立http请求
url
在一个已经激活的request下(已经调用open()或者openRequest()方法的请求)再次调用这个方法至关于调用了abort()方法。
复制代码
setRequestHeader()
- 向一个打开但未发送的请求设置或添加一个Http请求(设置请求头)
send()
- 发送http请求,使用传递给open()方法的参数,以及传递给该方法的可选请求体
abort()
- 取消当前响应getAllResponseHeaders()
- 把Http响应头部做为未解析的字符串返回getResponseHeader()
- 返回指定的 HTTP 响应头部的值
XMLHttpRequest
对象Http
对象Http
请求的请求头var ajax = {}
// 兼容性建立httpRequest
ajax.httpRequest = function(){
// 判断是否支持XMLHttpRequest
if(window.XMLHttpRequest){
return new XMLHttpRequest()
}
// 兼容 Ie
var versions = [
"MSXML2.XmlHttp.6.0",
"MSXML2.XmlHttp.5.0",
"MSXML2.XmlHttp.4.0",
"MSXML2.XmlHttp.3.0",
"MSXML2.XmlHttp.2.0",
"Microsoft.XmlHttp"
]
// 定义局部xhr,存储Ie浏览器的ActiveXObject对象
var xhr
for (var i = 0; i < versions.length; i++) {
try {
xhr = new ActiveXObject(versions[i]);
break;
} catch (e) {
}
}
return xhr
}
ajax.send = function(url, callback, method, data, async){
// 默认异步
if(async === undefined){
async = true
}
var httpRequest = ajax.httpRequest()
// 建立Http请求(open)
httpRequest.open(method, url, async)
// 请求状态改变的事件触发器
httpRequest.onreadystatechange = function(){
// readyState变为4时,从服务器拿到数据
if(httpRequest.readyState === 4){
callback(httpRequest.responseText)
}
}
// 设置http请求的请求头(setRequestHeader)
if (method == 'POST') {
//给指定的HTTP请求头赋值
httpRequest.setRequestHeader('Content-type', 'application/x-www-form-urlencoded')
}
// 发送Http请求
httpRequest.send(data)
}
// 封装GET/POST请求
ajax.get = function (url, data, callback, async) {
var query = [];
for (var key in data) {
query.push(encodeURIComponent(key) + '=' + encodeURIComponent(data[key]))
}
ajax.send(url + (query.length ? '?' + query.join('&') : ''), callback, 'GET', null, async)
}
ajax.post = function (url, data, callback, async) {
var query = [];
for (var key in data) {
query.push(encodeURIComponent(key) + '=' + encodeURIComponent(data[key]))
}
ajax.send(url, callback, 'POST', query.join('&'), async)
}
复制代码
Access-Control-Allow-Origin
)<script>
标签(只能解决GET请求)document.Ready()
能够定义多个函数,按照绑定的顺序进行执行,而onload
只能执行一次,定义多个onload
,后面的会覆盖前面的onload()
是在页面全部元素都加载完后才执行document.Ready()
是在DOM绘制完就执行,没必要等到彻底加载完再执行</body>
前写Js则是运行到就开始执行,无论有没有加载完成,因此有可能出现Js操做节点时获取不到该节点Object.freeze
适用于对象值,它可以让对象不可变(该对象属性不能修改)let foo = {
a:'A'
}
let bar = {
b:'B'
}
Object.freeze(foo)
foo.a = 'a'
console.log(foo) // {a: "A"}
复制代码
相比const
,二者是不一样的概念,const
做用是声明变量(一个只读变量),一旦声明,这个变量就不能修改,而Object.freeze()
做用是让一个对象不可变
new
new
- 配合构造函数建立对象function Person(name, age, job){
this.name = name
this.age = age
this.job = job
}
var person = new Person('CHICAGO', 21, 'itMan')
复制代码
new
在建立对象的过程当中作了哪4件事function Person(){
this.name = 'CHICAGO'
}
new Person()
- 建立一个空对象 → var obj = {}
- 将空对象赋给this → this = obj
- 将空对象的 __proto__ 属性指向构造函数的 prototype → this.__proto__ = Person().prototype
- 返回这个对象(this)→ return this
复制代码
{}
this
值__proto__
指向构造函数的prototype
return
语句,则返回this
hasOwnProperty()
- 返回一个布尔值,判断对象是否包含特定的自身(非继承)属性判断自身属性是否存在
Object.prototype.c= 'C';
var obj = new Object()
obj.a = 'A'
function changeObj(){
obj.b = 'B'
delete obj.a
}
obj.hasOwnProperty('a') // true
obj.hasOwnProperty('c') // false
changeObj()
obj.hasOwnProperty('a') // false
obj.hasOwnProperty('b') // false
复制代码
若是在函数原型上定义一个变量,hasOwnProperty()方法会直接忽略掉
in
运算符若是指定的属性在指定的对象或其原型链中,则in
运算符返回true
Object.prototype.c= 'C';
var obj = new Object()
obj.a = 'A'
console.log('a' in obj) // true
console.log('c' in obj) // true
复制代码
in
运算符会检查它或者其原型链是否包含具备指定名称的属性
Object.create()
能够实现建立没有原型的对象const objHavePrototype = {}
console.log(objHavePrototype.toString()) // [Object object]
const objHaveNoPrototype = Object.create(null)
console.log(objHaveNoPrototype.toString) // TypeError: objHaveNoPrototype.toString is not a function
typeof objHaveNoPrototype // object
复制代码
咱们知道 typeof null === 'object'
,但 null 并无 prototype 属性
event.preventDefault()
event.defaultPrevented
属性,该属性返回一个布尔值用于区分是否在特定元素中使用了event.preventDefault()
undefined
,有时倒是报错var foo = {}
console.log(foo.a) // undefined
console.log(foo.a.A) // TypeError: Cannot read property 'A' of undefined
复制代码
观察上面这个例子,有人会认为都是返回undefined
或者都是报错,当咱们访问foo.a
的时候,因为foo对象并不存在a属性,因此返回的是undefined,而当咱们去访问一个undefined的属性时,就会报出TypeError: Cannot read property 'XXX' of undefined
的错误
因为计算机是经过二进制来存储东西,那么0.1
在二进制中会表示为
// (0011) 表示循环
0.1 = 2^-4 * 1.10011(0011)
复制代码
能够发现,0.1
在二进制中是一个无限循环的数字,并非精确的0.1
,其实不少十进制小数用二进制表示都会是无限循环的,由于Javascript
采用浮点数标准,致使会裁剪掉咱们的数字,那么这些循环的数字被裁剪以后,就会出现精度丢失的问题,也就形成0.1
再也不是0.1
,而是变成0.100000000000000002
0.100000000000000002 === 0.1 // true
复制代码
天然,0.2
在二进制中也是无限循环,因此
0.1 + 0.2 === 0.30000000000000004 // true
复制代码
0.1 + 0.2 != 0.3
parseFloat(str)
- 解析一个字符串,并返回一个浮点数
toFixed(num)
- 把Number四舍五入为指定小数位数的数字
parseFloat((0.1 + 0.2).toFixed(10)) === 0.3 // true
复制代码
Javascript
内容较多,本章列举了较为重要的部分,我的会继续总结知识,对该章持续更新,后续会总结Js
重点手写题,建议对本文进行收藏ES6
核心知识点