原型原型链
- 参照 js原型与原型链解析
继承
- 参照 js的几种继承方式
call,apply,bind用法及实现
- 作用:简单来说就是改变
this
的指向,在一个对象中调用另一个对象的方法
用法
-
apply
与call
的用法相似,会直接执行函数
A.sayName.call(B,'tom')
A.sayName.apply(B,['tom'])
-
bind
用法与call
相似,不同的是,她不会立即执行,他会返回原函数的拷贝
let bSay = A.sayName.bind(B,'tom') //拷贝
bSay()//调用
实现
//A.myCall(B,arg1,arg2)
//挂载到Function 的原型对象上,以便继承给函数对象调用
Function.prototype.myCall = function (originFun, ...args) {
//A点出的myCall,这里this 指向A函数
console.log(this)
//在B里面定义一个私有属性指向A函数
originFun.__thisFn__ = this
//B调用A函数,this指向B
let result = originFun.__thisFn__(...args)
//删除无用属性
delete originFun.__thisFn__
return result
}
判断类型的方法
-
typeof
与instanceof
-
Object.prototype.toString.call()
;每个对象都存在toString()
方法,默认情况下,被每个Object
对象继承,如果未被覆盖,会返回[object type]
,type
是对象的类型。比如:let obj= new Object() obj.toString() //输出 "[object Object]"
但是对于其他情况
var arr =new Array(1,2) arr.toString() // 输出 "1,2"
这是因为所谓的
Array
、String
等类型在继承于基类Object
时,重写了toString
方法,在调用的时候,通过原型链向上查找方法时,找到Array
上面的toString
就停止查找,直接调用了,所以输出与预想的有偏差,那如何让arr
去调用Object
上面的该方法呢?显而易见借用call()
Object.prototype.toString.call(arr) // 输出 "[object Array]"
然后在利用
slice
方法截取出来类型// 是否字符串 export const isString = (o) => Object.prototype.toString.call(o).slice(8, -1) === 'String' // 是否数字 export const isNumber = (o) => Object.prototype.toString.call(o).slice(8, -1) === 'Number' // 是否boolean export const isBoolean = (o) => Object.prototype.toString.call(o).slice(8, -1) === 'Boolean' // 是否函数 export const isFunction = (o) => Object.prototype.toString.call(o).slice(8, -1) === 'Function' // 是否为null export const isNull = (o) => Object.prototype.toString.call(o).slice(8, -1) === 'Null' // 是否undefined export const isUndefined = (o) => Object.prototype.toString.call(o).slice(8, -1) === 'Undefined' // 是否对象 export const isObj = (o) => Object.prototype.toString.call(o).slice(8, -1) === 'Object' // 是否数组 export const isArray = (o) => Object.prototype.toString.call(o).slice(8, -1) === 'Array' // 是否时间 export const isDate = (o) => Object.prototype.toString.call(o).slice(8, -1) === 'Date' // 是否正则 export const isRegExp = (o) => Object.prototype.toString.call(o).slice(8, -1) === 'RegExp' // 是否错误对象 export const isError = (o) => Object.prototype.toString.call(o).slice(8, -1) === 'Error' // 是否Symbol函数 export const isSymbol = (o) => Object.prototype.toString.call(o).slice(8, -1) === 'Symbol' // 是否Promise对象 export const isPromise = (o) => Object.prototype.toString.call(o).slice(8, -1) === 'Promise'
捕获与冒泡
- 事件捕获与冒泡过程
捕获
-
事件捕获阶段,会由外到内,一层一层的检查是否注册了事件
-
如何在捕获阶段注册事件?
- 使用
addEventListener
注册事件,第三个参数代表是否在捕获阶段处理事件,设置为true
<div class="box"> <button class="btn">按钮</button> </div> <script> //先显示div 后显示btn const box = document.querySelector('.box') box.addEventListener( 'click', () => { alert('div被点击了') }, true ) const btn = document.querySelector('.btn') btn.addEventListener( 'click', () => { alert('button被点击了') }, true ) </script>
- 使用
冒泡
-
与捕获相反,在事件冒泡阶段,会从里到外来检查是否注册事件
-
默认情况下,所有的事件都是在冒泡阶段进行注册,如何阻止事件冒泡?
-
w3标准
event.stopPropagation()
-
IE浏览器
event.cancelBubble=true
-
事件委托
- 多个元素注册了相同的事件,可以把这些事件委托到它的父元素上,比较常见的是
ul
里面的li
标签点击事件
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
<script>
const ul = document.querySelector('ul')
ul.addEventListener('click', (e) => {
if (e.target.nodeName == 'LI') {
alert(e.target.innerText)
}
})
</script>
闭包问题
- 在一个内层函数中访问到外层函数的作用域
隔离作用域
var makeCounter = function() {
var privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment: function() {
changeBy(1);
},
decrement: function() {
changeBy(-1);
},
value: function() {
return privateCounter;
}
}
};
// Counter1与Counter2互不影响,在闭包内修改变量,不会影响到另一个闭包中的变量
var Counter1 = makeCounter();
var Counter2 = makeCounter();
console.log(Counter1.value()); /* logs 0 */
Counter1.increment();
Counter1.increment();
console.log(Counter1.value()); /* logs 2 */
Counter1.decrement();
console.log(Counter1.value()); /* logs 1 */
console.log(Counter2.value()); /* logs 0 */
经典for循环闭包问题
for (var i = 1, arr = []; i < 5; i++) {
setTimeout(() => {
console.log(i)
}, 500)
}// 5,5,5,5
//使用闭包
for (var i = 1, arr = []; i < 5; i++) {
;(function (i) {
setTimeout(() => {
console.log(i)
}, 1000 * i)
})(i)
}//1,2,3,4
new对象时发生了什么,实现一个new
const myNew = (constructorFn, ...args) => {
// 创建一个新的对象
let obj = {}
// 创建一个私有属性,指向构造函数的原型对象
obj.__proto__ = constructorFn.prototype
// 执行构造函数
constructorFn.call(obj, ...args)
//返回这个对象
return obj
}
function Person(name, age) {
this.name = name
this.age = age
}
const Per1 = new Person('Tom', 12)
console.log('Per1', Per1)
const Per2 = myNew(Person, 'Tom', 12)
console.log('Per2',Per2)
防抖与节流
闭包
深浅拷贝
- 基本类型
String
、Number
等都是按值访问的,它的值存储在栈中 - 引用类型都是 按引用访问的,它将引用地址存储在栈上,然后再去堆上开辟空间存放他们的值
浅拷贝
- 如果是基本类型,就直接拷贝基本类型的值,如果是引用类型,就拷贝引用类型的引用地址
- 浅拷贝对于基本类型来说,他们之间改变值不会相互影响,但是对于引用类型来说,由于拷贝的是地址,这些地址最终都指向同一个堆里面,所以会互相影响
实现方式
- ES6展开运算符
let obj1 ={name:'tom',age:12,addr:{lng:26.0,lag:45.0},friends:['john','jerry']}
let obj2={...obj1}
obj2.name='jerry'
obj2.frinds[0]='tom'
ES6展开运算符对于不同结构的数据,存在不同的表现.对于一维的对象或者数组,进行的深拷贝,对于多维的对象或者数组进行的是浅拷贝
- Object.assign()
let obj1 ={name:'tom',age:12,addr:{lng:26.0,lag:45.0},friends:['john','jerry']}
let obj2= Object.assign({}, obj1);
与解构赋值的作用一样
- lodash.clone()
var objects = [{ 'a': 1 }, { 'b': 2 }];
var shallow = _.clone(objects);
console.log(shallow[0] === objects[0]);
深拷贝
- 主要针对于引用类型,他会在对上面重新开辟一空间存放对象,这样两个深拷贝的对象就不会互相影响了
实现方式
- JSON.parse(JSON.stringfy() )
let obj1 ={name:'tom',friends:['jerry','john']}
let obj2 =JSON.parse(JSON.stringify(obj1))
obj2.name='jerry'
obj2.friends[0]='tom'
- lodash.cloneDeep()
var objects = [{ 'a': 1 }, { 'b': 2 }];
var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
- 递归遍历实现
function clone(targetObj) {
//判断是否未对象,是对象去遍历拷贝
if (typeof targetObj === 'object') {
//判断源数据是对象还是数组
let cloneTarget = Array.isArray(targetObj) ? [] : {}
for (const key in targetObj) {
//递归拷贝
cloneTarget[key] = clone(targetObj[key])
}
return cloneTarget
} else {
//不是对象直接返回
return targetObj
}
}