juejin.cn/post/684490…
juejin.cn/post/684490…
数据类型
基本类型:Boolean、Number、String、Null、Undefined:按值存放在占内存中,存储空间较小,值不可变,比较是值比较,复制新值时互不影响
引用类型:Object、Array、Function :存储的值大小不定,可动态调整(添加/删除属性/方法),地址指针保存在栈中,值保存在堆中,复制新值时相互影响
var obj1 = {name:"ConardLi"},obj2 = {age:18},obj3 = function(){...},obj4 = [1,2,3,4,5,6,7,8,9]
复制代码
以数组为例,以下方法都会改变他自身的值:pop,push,shift,unshift,reverse,sort,splice
检测数据类型方式
- typeof
只能检测基本类型:boolean、undefined、string、number、symbol,而typeof检测null ,Array、Object是Object ,返回字符串
- Object.prototype.toString.call()
- Object原型上toString方法,且让方法中的this变为value(value->是我们要检测数据类型的值)、每个继承 Object 的对象都有 toString 方法,但许多引用类型都重写了Object继承的的toStrong方法,需用call或apply来改变toString方法的执行上下文、这种方法对所有基本类型都能进行判断
Object.prototype.toString 返回的 [object,class] 字符串中,class 准确的表示了各个数据的类型,与 typeof 不同的是,class 所代表的数据类型字符串首字母是大写的,而不像 typeof 返回的是小写字符串
function type(obj) { return typeof obj !== "object" ? typeof obj : Object.prototype.toString.call(obj).slice(8, -1).toLowerCase(); }
复制代码
- instanceof:返回布尔值、判断引用类型,不能判断基本类型 、左边对象的隐式原型等于右边构造函数的显示原型
instanceof 右边必须是对象或构造函数,否则会抛出 TypeError 的错误
在类的原型继承中,instanceof检测是不准确的、且所有对象类型 instanceof Object 都是 true:[] instanceof Array; // true、[] instanceof Object; // true
- Object.prototype.hasOwnProperty([1,2,3])
返回布尔值,表示该实例对象自身是否具有该属性,还是继承自原型对象的属性(只检测自己是否拥有过这个属性,不考虑原型链上的)
- construtor:利用原型链:[].constructor === Array
深浅拷贝
juejin.im/post/5b5dcf…
juejin.im/post/5d6aa4…
mp.weixin.qq.com/s/g526P8gAa…
赋值,深/浅拷贝的区别
- 赋值:把一个对象赋给一个新变量时、赋的其实是该对象的在栈中的地址,而不是堆中的数据。即两个对象指向的是同一个存储空间,无论哪个对象发生改变,其实都是改变的存空间的内容,因此,两个对象是联动的
- 浅拷贝:创建一个新对象,若属性是基本类型,拷贝的就是基本类型的值,若属性是引用类型,拷贝的就是内存地址 ,所以其中一个对象改变了这个地址会影响到另一个对象\\基本类型不影响,引用类型相互影响
- Object.assign():把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象:let obj2 = Object.assign({}, obj1)
- 展开运算符…与 Object.assign ()的功能相同:let obj2= {… obj1}
- 函数库lodash的_.clone方法:var obj2 = _.clone(obj1)
- Array.prototype.concat()
- Array.prototype.slice()返回一个新的副本对象
- 深拷贝:将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象
- JSON.parse(JSON.stringify()):利用JSON.stringify将对象转成JSON字符串,再用JSON.parse把字符串解析成对象,而且对象会开辟新的栈,实现深拷贝:但不能处理函数(变为null)和正则(变为空对象)
- 函数库lodash的_.cloneDeep方法:var obj2 = _.cloneDeep(obj1)
- 手写递归方法:实现深度克隆原理:遍历对象、数组直到里边都是基本数据类型,然后再去复制,就是深度拷贝
数组有哪些方法
修改:push,pop,unshift,splice,reverse,sort
访问:slice,contact,join,indexOf,includes,toString
迭代:forEach,map,filter,reduce,some,every
自己实现map,reduce,reverce
纯函数、非纯函数:9801.me/?p=2754
不该变原数组:join、split、slice、indexOf、lastIndexOf、includes、some、every、filter、findInex、、、、
返回新数组:splice、unshift、reverse、sort、filter、find、、、、、、、
unshift(item1,item2,….):开头添加一个或更多元素,返回新数组长度,
shift():开头删除、返回第一个元素的值
push():最后添加一个或多个元素,并返回新数组的长度
pop():最后删除,返回被删除的元素的数组
reduce:“迭代的遍历”、归并类方法,让数组中的前项和后项做某种计算,并累计最终值,常用于叠加、求数组最大值、数组去重,统计每个值出现的次数,求和,可代替forEach
reduce(callback,initiaValue)会传入两个变量,回调函数(callback)和初始值(initiaValue)。
假设函数有4个传入参数,prev和next,index和array。 Prev和next是你必须要了解的。
当没有传入初始值时,prev是从数组中第一个元素开始的,next数组是第二个元素。
但是当传入初始值(initiaValue)后,第一个prev将是initivalValue,next将是数组中的第一个元素
原型和原型链
能知道原型链的概念,知道源头是null,下一层是Object.prototype
js本身是基于原型的继承的语言,es6之前通过原型来实现继承,es6之后通过class实现继承
继承方式:和class的区别
class原型本质怎么理解:constructor和superhttps://juejin.im/post/5b4c0b26f265da0f6c7a82a1
- 原型和原型链的一个图示:prototype和__proto__
- 属性和 方法的执行规则
声明类:class实际是函数
class Peoplet{constructor(name){this.name = name}}
通过类 new 对象/实例 const xia = new Peoplet(‘test’,100)
继承:子类继承父类 extends
class Student extends People{
constructor(){
super(name)//调用父类的属性或方法}}
复制代码
和instanceof:隐式原型和显示原型
每个class类,每个函数都有显示原型prototype
每个实例,每个引用类型都有隐式原型__proto__
实例的__proto__指向class的prototype
基于原型的执行规则:当获取一个对象的属性或方法时 ,若这个对象本身没有这个属性,那就会去它的隐式原型__proto__(即它的构造函数的prototype)中寻找
Object.prototype.__proto__永远指向null
instanceof:返回布尔值
hasOwnProperty:检测是否是自身的属性和方法
继承
es6继承:github.com/Advanced-Fr…
for in中一般会用hasOwnProperty做判断:目的是为了遍历某对象他自身的属性,并不是来自他原型上的属性
继承
- 原型继承
Student.prototype = new Person('b')
Student.prototype.constructor = Student
缺点:
子类型无法超类型传递参数
子类的方法必须写在new Person后面,不然会被覆盖
复制代码
- 类式继承
function Child(name, parentName) {
Parent.call(this, parentName);
this.name = name;
}
缺点:没有原型,每次创建都会执行Parent.call
复制代码
- 组合继承
function Child(name, parentName) {
Parent.call(this, parentName);
this.name = name;
}
Child.prototype = new Parent();
Child.prototype.constructor = Child;
缺点:父类构造函数会被调用两次
复制代码
- 寄生组合
function Person(name) {
this.name = name
}
Person.prototype.a = function () {
console.log('person a')
}
Person.prototype.b = function () {
console.log('person b')
}
function Student(name, superName) {
Person.call(this, superName)
this.name = name
}
Student.prototype = Object.create(Person.prototype)
Student.prototype.constructor = Student
Student.prototype.b = function () {
console.log('student b')
}
const person = new Person('p')
const student = new Student('s')
console.log(person)
console.log(student)
person.a()
person.b()
student.a()
student.b()
console.log(student instanceof Person)
复制代码
作用域和闭包
作用域
全局作用域、函数作用域、块级作用域
作用域是说某个变量的合法的适用范围:
自由变量:一个变量在当前作用域没有定义,但被使用了,想上级作用域一层一层依次寻找,直到找到为止(即使在往外一层还有定义的该变量,那也只用最先找到的那层的变量),若到全局作用域都还没找到,就会报错
this、new、call、apply、bind原理
this的由来和备份的作用:www.ruanyifeng.com/blog/2018/0…
this的使用场景:作为普通函数,使用call,apply,bind,作为对象方法被调用,在class方法中调用,剪头函数
this取什么值是在函数执行时确认的,不是在函数定义时确认的,箭头函数中this的取值时取其上级作用域的值
this 永远指向最后调用它的那个对象:值是在函数执行时确认的,不是在函数定义时确认的
this表示当前对象,var that=this是将当前this对象复制一份到that变量中意义是:this对象在程序中随时会变,备份后that没变之前仍是指向当时的this,这样就不会出现找不到原来的对象
1.普通函数的this指向window
2.定时器方法的this指向window
3.构造函数中(被 new 调用)的this指向实例对象(新生成的对象)
4.对象方法中的this指向实例对象(该对象):作为对象调用,返回本身
5.原型对象方法中的this指向实例对象
被 call、apply、bind 调用:this指向指定的上下文
this 根据优先级:new 绑定 > 显示绑定 > 隐式绑定 > 默认绑定
new/call/apply/bind原理:juejin.im/post/5c73a6…
bind是返回一个函数的
闭包
闭包是作用域和作用域链的一个呈现
两种常见方式:&自由变量查找规则
正常情况下函数在哪里定义,就要在哪里调用,而闭包定义和调用的地方是不一样的
作用域应用的特殊情况,有两种表现方式:函数作为参数被传递,函数作为返回值被返回
作为参数:
function create() {
const a = 100
return function () {console.log(a)}}
const fn = create()//执行create返回一个函数、而return的函数并没被执行,只是被返回
const a = 200;
fn() //100、
复制代码
作为返回值:
function print(fn) {let a = 200
fn()}
let a = 100
function fn() {console.log(a)}
print(fn)//100
function print(fn){let a = 200
fn()}
let a = 100
function fn(){console.log(a)}
print(fn)//100
复制代码
所有的自由变量的查找是在函数定义的地方,向上级作用域查找,而不是在执行的地方!!!
实际开发中的应用:隐藏数据:通过闭包把一些数据隐藏起来,不让外界改变它
应用场景
防抖和节流
- 防抖:throttle,在某段时间内,不管你触发了多少次回调,我都只认第一次,并在计时结束时给予响 应。
- debounce:在某段时间内,不管你触发了多少次回调,我都只认最后一次
juejin.im/post/5c87b5…
juejin.im/post/5b7b88…
都是为了解决短时间内大量触发某函数而导致的性能问题
防抖(debounce):原理:事件响应函数在 n 秒内函数只执行一次,若n秒内再触发则清空定时器,重新生成一个定时器去重新计算时间
节流(thorttle):在 n 秒内只会执行一次,若持续出发,每隔一段时间仅执行一次
闭包是什么?有什么特性和负面影响?
作用域和自由变量(自由变量的查找要在函数定义的地方而非执行的地方)
应用场景:作为参数被传递,作为返回值被返回
负面影响:变量就常驻内存,得不到释放,闭包不要乱用,,闭包并不是说造成内存泄漏,是无法判断以后还用不用,所以保存起来了
事件循环eventLoop:函数调用栈,宏任务,微任务
segmentfault.com/a/119000001…
zhuanlan.zhihu.com/p/72507900
- 函数调用栈:当引擎第一次遇到 JS 代码时,会产生一个全局 执行上下文并压入调用栈。后面每遇到一个函数调用,就会往栈中压入一个新的函数上下文。JS引擎会执行栈顶的 函数,执行完毕后,弹出对应的上下文
- js特性是单线程+异步:有些异步的任务不需要被立即执行,所以它被派发时,并不具备进入调用栈的资格,于是这些被待执行的任务,按照一定规则,乖乖排起长队,等待着被推入调用栈的时刻到来——这个队列,就叫 做“任务队列”。而宏任务与微任务式对任务队列的进一步划分
- macro: setTimeout、setInterval、 setImmediate、 script(整体代码)、I/O 操作等。
- micro : process.nextTick、Promise、MutationObserver,Object.observe 等
promise,setTimeout(),promise.then(),async,await:www.sohu.com/a/285466361…
- Promise 构造函数中函数体的代码都是立即执行的
- async/await 在底层转换成了 promise 和 then 回调函数,每次await, 解释器都创建一个 promise 对象,然后把剩下的 async 函数中的操作放到 then 回调函数中。async 是“异步”的简写,而 await 的简写可认为是等待异步方法执行完成async
复制代码
function f() { await p console.log('ok') } 简化理解为:function f() { return RESOLVE(p).then(() => { console.log('ok') }) }
复制代码
- await会产生一个微任务(Promise.then是微任务)。但是我们要注意这个微任务产生的时机,它是执行完await之后,直接跳出async函数,执行其他代码(此处就是协程的运作,A暂停执行,控制权交给B)。其他代码执行完毕后,再回到async函数去执行剩下的代码,然后把await后面的代码注册到微任务队列当中
- 带async关键字的函数,它使得你函数的返回值必定是promise对象即若async关键字函数返回的不是promise,会自动用Promise.resolve()包装
复制代码
- eventloop流程:微任务又会在宏任务之前执行
- 执行全局的script代码
- 全局Script代码执行完毕后,调用栈Stack会清空;
- 取出位于微任务队首的任务,放入调用栈中,直到把微队列中所有任务都执行完。若在执行微任务的过程中,又产生了微任务,那会加入到队列的末尾,也会在这个周期被调用执行;
- 微队列中的所有任务都被依次取出执行完后,直到微队列为空,调用栈也为空;
- 取出宏队列中位于队首的任务,放入调用栈中执行,tips:宏队列一次只从队列中取一个任务执行,执行完后去执行微任务队列中的任务
- 执行完毕后,调用栈为空;
- 重复第3-6个步骤
- 总结:浏览器可以理解成只有1个宏任务队列和1个微任务队列,先执行全局Script代码,执行完同步代码调用栈清空后,从微任务队列中依次取出所有的任务放入调用栈执行,微任务队列清空后,从宏任务队列中只取位于队首的任务放入调用栈执行,,只取一个,然后继续执行微队列中的所有任务,再去宏队列取一个,以此构成事件循环
其中return Promise.resolve(4);等价于:
return new Promise((reslove) => {
reslove(4)
})
复制代码
promise:特性、原理:回答特性思路:代理对象、三个状态、状态切换 机制、Promise.all(),怎么实现3个promise,其中一个抛错,另外两个继续执行
promise解决了回调嵌套的问题
异步和单线程:
基于js是单线程情况:同步会阻塞代码执行、异步不会阻塞代码执行
前端实用异步的场景有哪些?ajax,图片请求,定时器
跨域
juejin.im/post/5c2399…
juejin.im/post/5e948b…
www.jianshu.com/p/d771bbc61…
cors:纯服务端去做的
同源策略:ajax请求,浏览器要求当前网页和serve请求必须是同一个协议,域名,端口
如:http://a.baidu.com:8080和https://b.baidu.com:443
加载图片,css,js可无视同源策略
所有的跨域都必须经过serce端允许和配合、未经serve端允许就实现跨域,说明浏览器有漏洞
原生ajax
juejin.im/post/5b1ceb…
juejin.im/post/58c883…
原生ajax及4个步骤和过程:
var xhr = new XMLHttpRequest();//1、创建异步对象
xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded");//2、设置请求基本信息,并加上请求头
xhr.open('post', 'test.php' ,'true');//3、配置用get还说post请求,发送的url请求是否是异步
xhr.send('name=Lan&age=18');//4、发送请求
xhr.onreadystatechange = function () {if (xhr.readyState === 4 && xhr.status === 200) {// 这步为判断服务器是否正确响应
console.log(xhr.responseText);} };//获取异步调用返回的数据
复制代码
手写ajax:
function ajx(url) {
const p = new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest() //初始化一个实例
xhr.open('GET', url, true) //true是开启异步请求,false是同步请求
chr.onreadystatechange = function () {
if (xhr.readystate === 4) {
//0未初始化,还未调用send()、1载入,已调send(),正在发送请求,2载入完成,send()执行完成,已接受全部响应内容,3交互:正在解析响应内容还未发送回来,4完成:响应内容解析完成,可在客户端调用
if (xhr.status === 200) {
//状态码,2XX:成功处理请求,3xx需要重定向,浏览器直接跳转,4xx客户端请求错误,5xx服务端错误
resolve(JSON.parse(xhr.responseText))
} else if (xhr.status === 404) { reject(new Error('404 not found')) } }}
xhr.send(null)
})
return p }
const url = '/data/test.json'
ajx(url).then(res => console.log(res)).catch(err => console.log(err))
复制代码
JSONP的实现原理:
script可以绕过跨域限制、服务器可以任意动态拼接数据返回,还要符合html格式要求、所以,script就可以获得跨域的数据,只要服务端愿意返回
其他
设计模式