js部分
JS 部分
- 0.JS数据类型
- 1.闭包
- 2.深拷贝和浅拷贝的实现方式分别有哪些?
- 3. call 、bind 、 apply
- 4.this 指向
- 5.数组操作
- 6.判断一个对象是否是数组
- 7.时间队列顺序
- 8.数组去重
- 9. promise
- 10.JS 事件顺序
- 11. 判断类型
- 12. require 和 import 区别
- 13. 总结下 var、let 和 const 的区别
- 14. js 中的事件委托(事件代理)详解
- 15. typeof 和 instanceof
- 16. 防抖和节流
- 17.箭头函数和普通函数的区别
- 18.函数的 arguments
- 19.for in 和 for of
- 20.原型链
- 21. js循环中断
- 22.dom和bom
# 0.JS数据类型
- 基本数据类型:Number、String、Undefind、null、Bollean、symbol、bigInt
- 引用数据类型:Object 。包含Object、Array、Fuction
- 基本数据类型存在栈中,引用数据类型实际的值存在堆中,栈里保存的是引用地址
# 1.闭包
- 闭包就是能够读取其他函数内部变量的函数,常见表现形式往往是函数返回一个函数。其实现象就是把一个函数的状态和里面的变量能保存下来
- 原因:只要将闭包赋给一个变量,闭包不会被垃圾回收机制回收。除非这个变量被销毁
//外面的函数,return出内部的函数
function f1() {
var n = 999
function f2() {
alert(n)
}
return f2
}
var result = f1()
result() // 999
闭包的用途:
- 在函数外部使用函数内部的变量
- 隐藏变量
- 最主要的用途就是保存外层函数里的变量
闭包的注意点:
- 内存消耗很大,过多使用会造成网页的性能问题,在 IE 中可能导致内存泄露
实际使用场景
- 防抖节流 因为防抖是在事件被触发 n 秒后再执行回调,如果在这 n 秒内又被触发,则重新计时。每次触发都要看计时器是否存在,所以是需要保存这个计时器的。由于防抖可能会在多处被使用,不适合每次都新建一个全局变量,所以可以利用闭包
- 高阶函数、回调函数也属于闭包
- 单例模式,会利用闭包。就是用闭包保存一个实例,然后判断如果这个实例不存在则新建一个,如果存在就返回这个实例
- 给计算属性传参
/* * fn [function] 需要防抖的函数 * delay [number] 毫秒,防抖期限值 */ function debounce(fn, delay) { let timer = null //借助闭包 return function () { if (timer) { //利用闭包,这个timer在执行完不会被销毁, clearTimeout(timer) //进入该分支语句,说明当前正在一个计时过程中,并且又触发了相同事件。所以要取消当前的计时,重新开始计时 timer = setTimeOut(fn, delay) } else { timer = setTimeOut(fn, delay) // 进入该分支说明当前并没有在计时,那么就开始一个计时 } } } let a = debounce(func, 5000) //事件保存到一个变量里,a其实是一个闭包方法。比如click的时候可以直接调用a(func)。重复点击触发a,每次的timer都是同一个 a(func) //这里就是第一此调用,会在5秒后执行 a(func) //第二次调用,重置时间 let b = debounce(func2, 6000) //这是另一处调用防抖的时间,里面的timer和a的不是同一个 b(func2) //跟a没有任何关联
//单例模式 let GlobalUser = (function() { let instance // 闭包保存的唯一实例对象 return function(name) { if (instance) return instance // (首次)创建实例 instance = { name: '张三', id: 1003 } return instance } })() // 立即执行,这里相当于把闭包赋给了GlobalUser console.log(new GlobalUser('张四').name) // 张三 console.log(new GlobalUser('李四').name) // 张三,依然是张三,复用了第一次创建的实例 console.log(new GlobalUser() === new GlobalUser()) // true
http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Closures
# 2.深拷贝和浅拷贝的实现方式分别有哪些?
引用类型的数据,在直接赋值的时候,实际上传递的是栈里面的指针。堆里的内容其实是一样的。所以传递过程中不同的变量,实际上对应的是同一个内容。如果一个改变,就会全部改变。
无论是深拷贝还是浅拷贝,其实上要做的是,重新创建一个栈内存地址出来。
浅拷贝的话,是通过某些方法,重新返回出一个新的对象,但是如果对象里面还有对象,里面的对象是不会有新的地址的,所以里面和原对象还存在联系。
深拷贝的话,不管里面有多少层,都会重新创建一个和原对象没有任何联系的新对象
浅拷贝:(1) Object.assign的方式 (2) 通过对象扩展运算符 (3) 通过数组的slice方法 (4) 通过数组的concat方法。
深拷贝:(1) 通过JSON.stringify来序列化对象(如果obj里有函数,undefined,则序列化的结果会把函数或 undefined、原型属性丢失;)
(2) 手动实现递归的方式。
# 3. call 、bind 、 apply
- 作用都是改变函数的 this 指向
- 第一个参数都是 this
- call 的参数是从第二个参数开始一个个以逗号分隔
- apply 是第二个传参,以数组形式传进去
- call 和 apply 作用一样,只是参数形式不一样,当参数个数不定的时候,apply 更好一点
- bind 和 call 差不多,但是返回值是一个函数,所有调用的时候要最后加个括号 obj.myFun.bind(db)()
# 4.this 指向
https://blog.51cto.com/11871779/2129522
https://zhuanlan.zhihu.com/p/82504422
https://zhuanlan.zhihu.com/p/93446296
# 5.数组操作
# 6.判断一个对象是否是数组
语法: A instanceof B,意思是对象A的原型是否是B.prototype。如果是,返回true,如果不是,返回false。
# 7.时间队列顺序
macrotask: script(你的全部JS代码,“同步代码”.有人人为主代码是宏任务第一队列,也有人人为不算宏任务), setTimeout, setInterval, setImmediate, I/O,UI rendering
microtask:process.nextTick,Promises(这里指浏览器原生实现的 Promise), Object.observe, MutationObserver
JS引擎首先执行JS主代码,将microtask queue中的所有任务取出,按顺序全部执行;
然后再从macrotask queue(宏任务队列)中取下一个,执行完毕后,再次将microtask queue(微任务队列)中的时间按照先入先出的顺序全部取出;
循环往复,直到两个queue中的任务都取完。
# 8.数组去重
1.最简单
[...new Set(arr)] //去除不了空对象
3.利用indexOf去重 新建一个空的结果数组,for 循环原数组,判断结果数组是否存在当
前元素,如果有相同的值则跳过,不相同则push进数组
function unique(arr) {
if (!Array.isArray(arr)) {
console.log('type error!')
return
}
var array = [];
for (var i = 0; i < arr.length; i++) {
if (array .indexOf(arr[i]) === -1) {
array .push(arr[i])
}
}
return array;
}
4.利用filter
function unique(arr) {
return arr.filter(function(item, index, arr) {
//当前元素,在原始数组中的第一个索引==当前索引值,否则返回当前元素
return arr.indexOf(item, 0) === index;
});
}
5.数组对象去重
unique(arr) {
const map = new Map();
//新建一个map,然后开始筛选,判断res里有没有当前item的id,如果有,就返回且把ID放进map,如果没有就过
return arr.filter((arr) => !map.has(arr.id) && map.set(arr.id, 1))
}
# 9. promise
# 10.JS 事件顺序
参考资料:https://blog.csdn.net/yun_hou/article/details/88697954 (opens new window) 2 https://www.jianshu.com/p/b4fd76c61dc9 (opens new window)
async function async1() {
console.log('async1 start')
await async2()
console.log('asnyc1 end')
}
async function async2() {
console.log('async2')
}
console.log('script start')
setTimeout(() => {
console.log('setTimeOut')
}, 0)
async1()
new Promise(function (reslove) {
console.log('promise1')
reslove()
}).then(function () {
console.log('promise2')
})
console.log('script end')
结果
script start
async1 start
async2
promise1
script end
asnyc1 end
promise2
setTimeOut
流程:
- 执行 console.log('script start'),输出 script start;
- 执行 setTimeout,是一个异步动作,放入宏任务异步队列中;
- 执行 async1(),输出 async1 start,继续向下执行;
- 执行 async2(),输出 async2,并返回了一个 promise 对象,await 让出了线程,把返回的 promise 加入了微任务异步队列,async1()下面的代码也要等待上面完成后继续执行;
- 执行 new Promise,输出 promise1,然后将 resolve 放入微任务异步队列;
- 执行 console.log('script end'),输出 script end;
- 到此同步的代码就都执行完成了,然后去微任务异步队列里去获取任务
- 接下来执行 resolve(async2 返回的 promise 返回的),输出了 async1 end。
- 然后执行 resolve(new Promise 的),输出了 promise2。
- 最后执行 setTimeout,输出了 settimeout。
# 11. 判断类型
Object.prototype.toString.call() 适用任何类型
Object.prototype.toString.call(an) // "[object Array]" Object.prototype.toString.call('An') // "[object String]" Object.prototype.toString.call(1) // "[object Number]" Object.prototype.toString.call(Symbol(1)) // "[object Symbol]" Object.prototype.toString.call(null) // "[object Null]" Object.prototype.toString.call(undefined) // "[object Undefined]" Object.prototype.toString.call(function () {}) // "[object Function]" Object.prototype.toString.call({ name: 'An' }) // "[object Object]"
instanceof 只能判断对象类型。就是去原型链里找对应的原型
[] instanceof Array // true <!-- instanceof相当于 array.__proto__ === Array.proto-->
Array.isArray() 检测是否为数组
# 12. require 和 import 区别
模块加载的时间 require:运行时加载 import:编译时加载(效率更高)【由于是编译时加载,所以 import 命令会提升到整个模块的头部】
require:模块就是对象,引入时直接引入整个对象
import:ES6 模块不是对象,而是通过 export 命令显式指定输出的代码,再通过 import 命令输入。导入时可以按需导入
# 13. 总结下 var、let 和 const 的区别
var:只有全局作用域和函数作用域概念,没有块级作用域的概念。但是会把{}内也假称为块作用域。
let:只有块级作用域的概念 ,由 { } 包括起来,if 语句和 for 语句里面的{ }也属于块级作用域。
let 和 var 的第二点不同是,在变量声明之前就访问变量的话,会直接提示 ReferenceError,而不像 var 那样使用默认值 undefined:
var 存在变量提升,而 let,const 其实也有变量提升,但是暂时性死区所以用 let 定义的变量一定要在声明后再使用,否则会报错。
可以看出,var:变量可以多次声明,而 let 不允许在相同作用域内,重复声明同一个变量。
使用 var 和 function 声明的全局变量依旧作为全局对象的属性,使用 let, const 命令声明的全局变量不属于全局对象的属性。
<script> var a = 10; console.log(window.a); //10 console.log(this.a) //10 let b = 20; console.log(window.b); // undefined console.log(this.b) // undefined </script>
# 14. js 中的事件委托(事件代理)详解
https://www.cnblogs.com/lauzhishuai/p/11263210.html (opens new window)
就是利用事件冒泡,将子元素的事件写到父元素中,再通过 JS 去判断写不同的方法 核心是 click 父元素,e.target 可以得到实际点击的那个元素,然后就可以进行操作 好处:1.如果子元素很多,需要写很多个 onckilck,事件代理的话只需要写一个。减少 dom 操作 2.如果有新增子元素,不需要手动在写一个 onclick,直接添加就能有
# 15. typeof 和 instanceof
typeof 只能判断基础类型,引用类型都是 object。 instanceof 用来判断一个引用数据类型是否属于某构造函数 https://www.jianshu.com/p/4ff2332228be (opens new window)
var a = [34, 4, 3, 54],
b = 34,
c = 'adsfas',
d = function () {
console.log('我是函数')
},
e = true,
f = null,
g
console.log(typeof a) //object
console.log(typeof b) //number
console.log(typeof c) //string
console.log(typeof d) //function
console.log(typeof e) //boolean
console.log(typeof f) //object !!特殊
console.log(typeof g) //undefined
function Person(name,age,sex){
this.name = name;
this.age = age;
this.sex = sex;
}
function Student(score){
this.score = score;
}
var per = new Person("小明",20,“男”);
var stu = new Student(98);
console.log(per instanceof Person); // true
console.log(stu instanceof Student); // true
console.log(per instanceof Object); // true
console.log(stu instanceof Object); // true
# 16. 防抖和节流
https://juejin.cn/post/6963969900742180901?utm_source=gold_browser_extension (opens new window)
防抖使用场景:
按钮防止二次点击
搜索框输入查询
scroll 滚动触发
浏览器窗口缩放时,resize 事件
vuex 加解密里面,防止初次加载的时候多次加解密
节流使用场景:
- 浏览器窗口缩放时,resize 事件
- 抢购按钮
# 17.箭头函数和普通函数的区别
- 箭头函数是匿名函数,不能作为构造函数,不能使用 new
- 箭头函数不能绑定 arguments,取而代之用 rest 参数...解决
- 箭头函数没有原型属性
- 箭头函数的 this 永远指向其上下文的 this,没有办改变其指向,即使是用 apply,call,bind
# 18.函数的 arguments
https://blog.csdn.net/weixin_42561383/article/details/90812917 (opens new window)
每个函数(非箭头)在被调用时都会自动取得两个特殊变量:this 和 arguments。 arguments 是一个类数组对象,里面保存着调用函数时传入的实参,第一个参数索引为 0。
;(function (age, name) {
console.log(arguments) //[23,"XD"]
console.log(arguments.length) //2
console.log(arguments[0]) //23
console.log(arguments[1]) //XD
})(23, 'XD')
修改 arguments 会影响参数。
;(function (age) {
console.log(arguments[0]) //23
console.log(age) //23
arguments[0] = 18
console.log(age) //18
})(23)
arguments 虽然可以通过下标获取其中的元素,也有 length 属性,但是却不是一个数组,因此不能使用数组的方法。
;(function () {
console.log(arguments.reverse()) //报错
})(23, 'XD')
arguments 还有一个 callee 属性,这个属性指向 arguments 所在函数本身。
;(function fun() {
console.log(arguments.callee === fun) //true
})()
用 arguments 对象判断传递给函数的参数个数,即可模拟函数重载: !!!!重要
function fun() {
if (arguments.length === 1) {
console.log(arguments[0])
} else if (arguments.length === 2) {
console.log(arguments[0] + arguments[1])
}
}
fun(18) //18
fun(18, 23) //41
可以使用 Array.prototype.slice.apply(arguments)和 Array.prototype.concat.apply([], arguments)将 arguments 转化为真正的数组。
# 19.for in 和 for of
- for-in :可以遍历数组和对象、字符串。 总是得到得到下标。为了防止遍历到原型链上的属性,需要配合xx.hasOwnProperty(key)进行判断
- for-of 不能遍历对象。总是得到数组的 value 或数组、字符串的值,另外还可以用于遍历 Map 和 Set。
# 20.原型链
# 一、原型和原型链
# 1.1 原型
- 每一个函数,都会有一个默认属性
prototype
,这个就是原型对象
,在原型对象上,又会有一个constructor
,这个属性又会指向函数本身 - 当通过构造函数实例化一个对象时,会在对象上添加一个属性__proto__.这里面是构造函数的原型对象。里面会有原型对象里的各种属性和方法。可以直接使用
- 任何非null数据,其实都是由构造函数创建而来。包括srting,number,object等。所以也都会有对应的__proto__,也就是其构造函数的原型对象。
function fun(name) {
this.name = name
}
fun.prototype // 原型对象
const obj = new fun('moyuanjun') // 函数被作为构造函数进行调用
obj.__proto__ === fun.prototype // true, 实例对象.__proto__ 指向 构造函数.prototype
# 1.2 原型链
- 每一个函数都会有自己的原型对象,这个原型对象往往是个object,那也会有自己的__proto__,指向Object函数的原型,而Object的原型对象又是null。
- 原型链的顶端就是null。
# 1.3 作用
当对象没有这个方法和属性时,会往原型链上一层一层的查找,直接使用。实现继承效果
那在原型上加上属性或者方法,可以让每个由这个构造函数创建出来的对象拥有这个属性和方法,相当于公共方法和属性。
# 1.4 「proto」与「[[Prototype]]」
- 对象在浏览器重直接打印的话,出来的是
[[Prototype]]
。因为__proto__并非是ECMAScript标准,是浏览器给我们的一个属性,方便我们操作原型对象。实际上两者是一个东西。[[Prototype]]
里面是有__proto__
的
# 1.5 任何 非空数据 , 本质上都是通过对应 构造函数 构建出来的
而一个数据的数据类型,可以通过原型对象去判断
// 数字
const num = 1
// 数字是通过 Number 构建的, 那么其原型对象等于 Number.prototype
num.__proto__ === Number.prototype // true
// 字符串
const str = 'str'
// 字符串是通过 String 构建的, 那么其原型对象等于 String.prototype
str.__proto__ === String.prototype // true
// 布尔类型
const bool = false
// 布尔值是通过 Boolean 构建的, 那么其原型对象等于 Boolean.prototype
bool.__proto__ === Boolean.prototype // true
// Symbol
const sym = Symbol('symbol')
// sym 是通过 Symbol 构建的, 那么其原型对象等于 Symbol.prototype
sym.__proto__ === Symbol.prototype // true
// BigInt
const big = BigInt(1)
// big 是通过 BigInt 构建的, 那么其原型对象等于 BigInt.prototype
big.__proto__ === BigInt.prototype // true
// 对象
const obj = { age: 18 }
// 对象是通过 Object 构建的, 那么其原型对象等于 Object.prototype
obj.__proto__ === Object.prototype // true
// 函数
const fun = () => {}
// 函数是通过 Function 构建的, 那么其原型对象等于 Function.prototype
fun.__proto__ === Function.prototype // true
// 数组
const arr = [1, 2, 3]
// 数组是通过 Array 构建的, 那么其原型对象等于 Array.prototype
arr.__proto__ === Array.prototype // true
# 二、new做了哪些事情
- 创建一个新的空对象 A
- 挂载 原型对象: 对象 A 创建 proto 属性, 并将 构造函数 的 prototype 属性赋值给 proto
- 改变 构造函数 this 指向, 指向对象 A,并执行函数
- 得到一个新对象
1.var obj={};
2.obj.__proto__=test.prototype;
3.test.call(obj);
4.把obj的地址赋值给等式左边的变量
一般是返回第一步创建的对象 A 但是如果 构造函数 也返回了一个对象 B 则返回对象 B 否则返回对象 A
# 21. js循环中断
for循环 直接break
forEach
return会结束本次循环,进入下一次循环。无中断效果
break & continue报错
Array.map 同forEach
for...in break对遍历无影响 return报错
for...of break 跳出循环 return 报错
while return报错 break跳出循环
# 22.dom和bom
- dom:文档对象模型。也就是页面上的dom树
- bom:浏览器对象模型。是浏览器提供的一系列API。简单的说就是window对象