文章目录
一、数组中对象去重
// 数组中对象去重
let newAgency = []
agencyCodeArr = agencyCodeArr.reduce((cur,next)=>{
newAgency[next.agencyCode] ? "" : newAgency[next.agencyCode] = true && cur.push(next)
return cur
},[])//设置cur默认类型为数组,并且初始值为空的数组
二、判断对象长度
let obj = {a:1,b:undefined}
Object.values(obj.length) // 2
三、判断对象中是否含有某个属性
let obj = {a:1,b:undefined}
obj.hasOwnProperty(isCustomize) // false
obj.hasOwnProperty(a) // true
三、使用scrollIntoView()方法导致整个页面上移问题
方法1:
使用scrollIntoView({block: "end", inline: "nearest"})
元素的底部将与可滚动祖先的可见区域的底部对齐
document.querySelector('.tree').scrollIntoView({block: "end", inline: "nearest"});
说明:
element.scrollIntoView(alignToTop); //布尔参数
alignToTop
:
true
:元素的顶部将对齐到可滚动祖先的可见区域的顶部。对应于scrollIntoViewOptions: {block: "start", inline: "nearest"}
。(默认值)
false
:元素的底部将与可滚动祖先的可见区域的底部对齐。对应于scrollIntoViewOptions: {block: "end", inline: "nearest"}
。
注:element.scrollIntoView() 相当于 element.scrollIntoView(true)
参考文章:详细介绍scrollIntoView()方法属性
方法2:在页面最外层box上设置fixed布局(不推荐)
.box{
position: fixed;
top:0;
left:0;
right:0;
bottom:0;
overflow-y: scroll;
}
四、正则空格匹配
匹配字符串头部和尾部的空格
str.replace(/(^\s*)|(\s*$)/g, "")
匹配空格校验
const reg = new RegExp(/\s/)
reg.test('000 shjh") // true
五、解构赋值的连续写法
1、解构赋值的连续写法
let obj = {a:{b:{c:1}}}
const {a:{b:{c}}} = obj
console.log(c) // 1
2、连续结构赋值重命名
let obj2 = {a:{b:2}}
const {a:{b:data}} = obj2
console.log(data) // 2
六、call,bind,apply的用法及区别
相同点:
1、都是改变this指向的
2、第一个参数都是this要指向的对象
a)非严格模式下,若第一个参数指定,this为指定的参数值;若为null或undefined,则this指向window
b)严格模式下,若第一个参数指定,this为指定的参数值;若为null或undefined,则this对应为null或者undefined
3、都可以利用后续参数传参
不同点:
1、call和bind的参数是依次传参,一一对应的;
2、但apply只有两个参数,第二个参数为数组
3、call和apply都是对函数进行直接调用,而bind方法返回的仍是一个函数
// 'use strict'
let fn = function (a,b) {
console.log(this,a,b);
}
fn.call({type:'apply'},{a:'参数'},20); // {type:'apply'} {a:'参数'} 20
fn.call(null,{a:'参数'},20); // window {a:'参数'} 20
fn.call(undefined,{a:'参数'},20); // window {a:'参数'} 20
const fs = fn.bind({type:'bind'},{a:'参数'},20)
fs() // {type:'bind'} {a:'参数'} 20
fn.apply({type:'call'},[{a:'参数'},23]); // {type:'call'} {a:'参数'} 20
七、数据类型及内存存储
1、数据类型分类
基本类型:
* String
* Number
* Boolean
* undefined
* null
对象类型
* Object:任意对象
* Function:一种特别的对象(可以执行)
* Array:一种特别的对象(数值下标)
2、数据类型判断
* typeof
* 可以判断:undefined / 数值 / 字符串 / 布尔值 / function
* 不能判断:null与object object和array
* instanceof 判断对象的具体类型
* ===
* 可以判断undefined
* 对象原型上的方法Object.prototype.toString()
// typeof不能判断:null与object object和array
var a
a = null
console.log(typeof a, a===null) //'object' true
a = [1,2,3]
console.log(typeof a,"----") //object
// instanceof 判断对象的具体类型
var b1 = {
b2: [1, '1bc', console.log],
b3: function () {
return function () {
return 'alshagh'
}
}
}
console.log(b1 instanceof Object, b1 instanceof Array) //true false
console.log(b1.b2 instanceof Array, b1.b2 instanceof Object) //true true
console.log(b1.b3 instanceof Function, b1.b3 instanceof Object) //true true
3、内存
JS中变量都是保存在栈内存中的
基本数据类型(String,Boolean...):
* 基本数据类型的值直接在栈内存中存储
* 值与值之间是独立存在,修改一个变量不会影响其他的变量
引用数据类型:(Object)
* 对象的值是保存在堆内存中,每创建一个新的对象,就会在堆内存中开辟出一个新的内存空间
* 而变量保存的是对象的内存地址(对象的引用),如果两个变量保存的是同一个对象引用
* 当一个通过一个变量修改属性时,另一个也会受影响
// 基本数据类型
var c = 11
var d = c
c++
console.log(c) //12
console.log(d) //11
// 引用数据类型
var obj = new Object()
obj.name = '孙悟空'
var obj2 = obj
obj.name = '猪八戒'
console.log(obj.name) //猪八戒
console.log(obj2.name) //猪八戒
obj2 = null
console.log(obj) //{name:'猪八戒'}
console.log(obj2)//null 相当于把地址清空了断开了与obj的联系
/*
当比较两个基本数据类型的值时,就是比较值
而比较两个引用数据类型时,它是比较的对象的内存地址
如果两个对象是一模一样的,但是地址不同,它也会返回false
*/
var obj3 = new Object()
var obj4 = new Object()
obj3.name = '沙和尚'
obj4.name = '沙和尚'
console.log(obj3 === obj4) // false
4、undefined与null的区别
* undefined代表定义了但是未赋值
* null定义了并赋值了,只是值为null
5、什么时候给变量赋值为null
* 初始赋值,表面将要赋值为对象
* 结束前,让b指向的对象成为垃圾对象(被垃圾回收器回收)
八、this指向问题
1、 函数调用
函数名()和匿名函数调用,this指向window
function fn1() {
console.log(this) // window --函数名调用
}
fn1()
;(function() {
console.log(this) // window --匿名函数调用
})()
var fn2 = function(){
console.log(this) // window --函数名调用
}
fn2()
2、 方法调用
this指向对象
var obj1 = {
name: 'tom',
setName: function() {
console.log(this) // obj1对象
}
}
obj1.setName()
3、 构造器调用
this指向实例化对象
function Person(){
console.log(this) // 实例化对象p1
}
var p1 = new Person()
4、 间接调用
使用call和apply实现
this对应传的第一个参数
如果不传值或者第一个值为null,undefined时this指向window(如果开启了严格模式则传啥是啥)
如果传值,就是传的对应的值
function fn3() {
console.log(this)
}
fn3.apply({name:'tom'}) // this为{name:'tom'}
fn3.call(8) // this为8
5、 箭头函数中this
箭头函数没有自己的this,里面的this和定义有关,和调用无关
箭头函数的this指向是父级程序的this指向,如果没有父级程序或者父级程序没有this那么箭头函数的this执向是window。
;(() => {
console.log(this) // window
})()
let arrowFun = () => {
console.log(this) // window
}
arrowFun()
let arrowObj = {
arrFun: function() {
() => {
console.log(this) // arrowObj对象
}
}
}
arrowObj.arrFun()
九、构造函数
构造函数与普通函数
相同点:创建方式相同
不同点:
* 构造函数习惯上大写
* 普通函数是直接调用,而构造函数需要使用new关键字来调用
备注:使用同一个构造函数创建的对象,我们称之为一类对象,也将一个构造函数称之为类
我们将通过一个构造函数创建的对象,成为是该类的实例
function Person(name,age) {
this.name = name
this.age = age
this.sayName = function () {
console.log(this.name)
}
}
var per = new Person("tom",18)
var per2 = new Person("mara",16)
console.log(per.name,per.age)
console.log(per.sayName === per2.sayName) // false 构造函数执行一次就会创建一个新的sayName方法
function Dog() {}
var d = new Dog()
console.log(per instanceof Person) //true
console.log(d instanceof Person) //false
十、原型链
* 我们所创建的每一个函数,解析器都会向函数中添加一个属性prototype,它默认指向一个Object空对象(即称为: 原型对象)
* 原型对象中有一个属性constructor, 它指向函数对象
* 如果函数作为普通函数调用prototype没有任何作用
* 当函数以构造函数调用时,它所创建的对象中都会有一个隐含的属性,
指向该构造函数的原型对象,我们可以通过__pproto__来访问属性
* 原型对象就相当于一个公共的区域,所有同一个类的实例都可以访问到这个原型对象
我们可以将对象中共有的内容,统一设置到原型对象中
* 对象共有的属性和方法,可以统一添加到构造函数的原型对象中
图解1:
function MyClass() {}
MyClass.prototype.a = 123
MyClass.prototype.sayName = function(name) {
console.log(name)
}
console.log(MyClass.prototype.constructor === MyClass) //true
var mc = new MyClass()
var mc2 = new MyClass()
console.log(MyClass.prototype === mc.__proto__) //true
console.log(mc.__proto__ === mc2.__proto__) //true
mc.a = '我是mc中的a' // 向mc中添加a属性
console.log(mc.a) // 对象的属性或方法首先会在自身中寻找,若没有才会去原型中寻找
in
:检查对象中是否含有某个属性时,如果对象中没有但是原型中有,也会返回true
hasOwnProperty()
:检查对象自身中是否含有该属性时,只有对象自身中含有属性时才会返回true
onsole.log(mc.hasOwnProperty("name"))//false
console.log(mc.hasOwnProperty("hasOwnProperty")) //false 该方法在原型的原型里面
原型对象也是对象,所有它也有原型,
当我们使用一个对象的属性或方法时,会先在自身中寻找
* 自身中如果有,则直接使用
* 如果没有则去原型对象中寻找,如果原型对象中有,则使用
* 如果没有则去原型的原型中寻找,直到找到Object对象的原型
* Object对象的原型没有原型,如果在Object中依然没有找到,则返回undefined
console.log(mc.__proto__.hasOwnProperty("hasOwnProperty"))//false
console.log(mc.__proto__.__proto__.hasOwnProperty("hasOwnProperty")) //true
关于引用变量赋值问题:
* 2个引用变量指向同一个对象,通过一个变量修改对象内部的值,另一个变量看到的是修改之后的数据
* 2个引用变量指向同一个对象,让其中一个引用变量指向另一个对象,另一个引用变量依然指向前对象
var a = {age:12}
var b = a
a = {name:'tom', age:13}
b.age = 14
console.log(b.age, a.name, a.age) // 14 tom 13
function fn2 (obj) {
obj = {age:15}
}
fn2(a)
console.log(a.age) //13
图解2:
问题: 在js调用函数时传递变量参数时,是值传递还是引用传递?
* 理解1:都是值(基本/地址值)传递
* 理解2:可能是值传递,也可能是引用传递(地址值)
十一、继承
1、原型链继承
关键点:子类型的原型为父类型的一个实例对象
function Supper() {
this.subProp = 'Supper property'
}
Supper.prototype.showSupperprop = function () {
console.log(this.subProp)
}
function Sub() {
console.log(this.subProp)
}
// 子类型的原型为父类型的一个实例对象
Sub.prototype = new Supper()
// 让子类型原型的constructor指向子类型
Sub.prototype.constructor = Sub
Sub.prototype.showSubprop = function () {
console.log(this.subProp)
}
var sub = new Sub()
sub.showSupperprop() //继承了父类的方法
sub.showSubprop()
sub.toString()
console.log(sub.constructor) //Sub
原型链继承图解:
2、构造继承
关键点: 在子类型构造函数中通用call()调用父类型构造函数
function Person(name, age) {
this.name = name
this.age = age
}
function Student(name, age, price) {
Person.call(this, name, age) //相当于:this.Persaon(name, age)
this.price = price
}
var s = new Student('Tom', 20, 24000)
console.log(s.name, s.age, s.price)
3、组合继承
关键点: 原型链+借用构造函数的组合继承
- 利用原型链实现对父类型对象的方法继承
- 利用call()借用父类型构建函数初始化相同属性
function Person(name,age) {
this.name = name
this.age = age
}
Person.prototype.setName = function (name) {
this.name = name
}
function Student(name,age) {
Person.call(this, name, age)
}
Student.prototype = new Person() //为了能看到父类型的方法
Student.prototype.constructor = Student //为了修正constructor方法
var s = new Student('Tom', 24)
s.setName('Bob')
4、extends继承
class Person {
// constructor是构造方法
constructor(name,age) {
this.name = name
this.age = age
}
speak() {
console.log("Person的speak方法")
}
}
class Student extends Person {
constructor(name,age,sno){
super(name, age)
this.sno = sno
}
say() {
console.log(this.name + "的年龄是" + this.age + ", 学号是" + this.sno)
}
}
let s = new Student('tom',18,156786568)
s.speak()
s.say()
十二、arguments
在调用函数时,浏览器会传递两个隐藏的参数:
1、函数的上下文对象this
2、封装实参的对象arguments
- arguments是一个类数组对象,它也可以通过索引来操作数据,也可获取长度
- 在调用函数时,我们所传递的参数都会在arguments中保存
- arguments.length可以用来获取实参的长度
- 我们即使不定义形参,也可以通过arguments来使用实参
只不过比较麻烦
arguments[0] 表示第一个实参
arguments[0] 表示第二个实参
- 它里边有一个属性叫做callee
这个属性对应一个函数对象,就是当前正在指向的函数的对象
function fun() {
console.log(arguments instanceof Array) //false
console.log(Array.isArray(arguments)) //false
console.log(arguments[0], arguments[1]) //tom 18
console.log(arguments.length)
console.log(arguments.callee)
}
fun('tom',18)
十三、IIFE
IIFE:立即执行函数
作用:隐藏实现/不会污染外部命名空间
// ()及[]的上一句语句要加;,避免修改时上一句不加,将;加到()/[]前面即可
;(function () {
var a = 1
function test () {
console.log(++a)
}
window.$ = function () { //向外暴露一个全局函数
return {
test: test
}
}
})()
$().test() //1、$是一个函数 2、$执行后返回的是一个对象
十四、闭包
产生: 当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时, 就产生了闭包
理解: 闭包是嵌套的内部函数
常见的闭包:
1、 将函数作为另一个函数的返回值
function fn1 () {
var a = 2
function fn2 () {
a++
}
return fn2
}
var f = fn1()
f() //3
f() //4
2、将函数作为实参传递给另一个函数调用
function showDelay(msg, time) {
setTimeout(function() {
console.log(msg)
},time)
}
showDelay('lilg', 2000)
十五、函数的防抖和节流
防抖和节流:限制函数的执行次数
使用场景: 不想频繁的触发事件(如搜索框实时发请求,表单验证,按钮提交事件,onmousemove
,resize
,onscroll
等等)
html部分及通用js部分:
<input type="text">
<input id="input" type="submit">
var btn = document.getElementById("input")
// btn.addEventListener('click', submit, false)
// btn.addEventListener('click', debounce(submit, 2000), false)
btn.addEventListener('click', throttle(submit, 2000), false)
function submit(e) {
console.log(e)
console.log(this)
}
1、防抖
通过setTimeout的方式,在一定时间间隔内,将多次触发变为一次触发
function debounce(fn, timer) {
var t = null
return function() {
var firstClick = !t
if(t) clearTimeout(t)
if(firstClick) {
fn.apply(this, arguments) // 修正submit中this指向和参数
}
t = setTimeout(() => {
t = null
}, timer)
}
}
2、节流
减少一定时间的触发频率
function throttle(fn, delay) {
var begin = 0
return function () {
var cur = new Date().getTime()
if(cur - begin > delay) {
console.log(cur - begin)
fn.apply(this, arguments)
begin = cur
}
}
}