你不知道的JS!

1.数据类型

数组、函数的本质其实是Object。

 (1)五种基本数据类型 + 一种引用数据类型

五种基本数据类型: number、string、boolean、undefined、null。
一种引用数据类型: object

(2)数据类型检测

方法1: typeof 变量
方法2: 变量 instanceof Array

(3)数据类型转换(核心

// 隐式转换
(1)转number: let n = +a
(2)转string: let n = a + ''
(3)转Boolean:let n = !a  // 转Boolean时,只有false、null、undefined、0、NaN、''的情况下为false。
(4)短路运算: 最终返回的结果是判定后的结果,而不是boolean值。

// 强制转换

(4)包装类

JS为boolean、string、number单独创建了一个构造函数,将它们包装成一个对象。

2.基础知识

01.事件循环、事件流

02.事件绑定

方法1:使用addEventListener

//  1.绑定事件:默认是冒泡方式。
dom.addEventListener('事件类型','事件处理程序',[事件冒泡方式], { once: true })

//  2.删除事件 
dom.removeEventListener('事件',需要取消绑定的函数名称) 	// 函数不需要加小括号

方法2:传统方式

//  1.绑定事件
对象.onclick = function(){}    // 缺点:只能绑定一个事件。

//  2.删除事件
标签.事件 = null

03.事件大全、事件对象

随时可以查询。

1.事件大全 

//  1. 鼠标事件       
(1)click:    	鼠标左键。
(2)mouseover:	鼠标经过。(有冒泡) 
	mouseenter:  鼠标经过。(没有冒泡,推荐!)
(3)mouseout: 	鼠标离开。(有冒泡)
	mouseleave:  鼠标离开。(没有冒泡,推荐!)// mouserover和mouseenter的区别:mouseover经过自身会触发,经过子元素还会触发。
(4)mousemove:	鼠标移动。
(5)mouseup:  	鼠标按下后,然后弹起时触发。
(6)mousedown:	鼠标按下触发。    
(7)contextmenu: 禁止鼠标右键菜单(给document绑定,触发时回调函数写:e.preventDefault())
(8)selectstart: 禁止鼠标选中文字(给document绑定,触发时回调函数写:e.preventDefault())   


//  2. 键盘事件
(1)keyup:按键松开时触发	 // keydown和keypress键盘按下触发,但是文字还没落入到文本框就触发了,但是keyup是恰恰相反。
(2)keydown: 按键按下时触发(识别功能键)
(3)keypress:按键按下时触发(不识别功能键,如Ctrl,Shift等)  // keyup事件不区分大小写,keypress事件可以区分大小写。  


// 	3. 触摸事件(移动端)         
(1)touchstart:手指摸到dom时触发
(2)touchmove: 手指在dom上滑动时触发
(3)touchend:  手指从dom中离开时触发     


//  4. 视频播放位置改变事件
(1)ontimeupdate:当视频、音频的当前播放位置改变时触发。(需要配合节流)
(2)onloadeddata:在当前帧的数据加载完成,并且还没有足够的数据播放音频、视频的下一帧时触发。
(3)获取音频、视频当前播放的时间:video.currentTime    


//  5. 表单获取焦点事件
(1)focus:
(2)blur:      
(3)change:当表单内容发生改变时才触发。

//  6. 用户输入事件
(1)input:


//  7. 页面加载事件
(1)load:等待页面所有资源都加载完,才会触发。	// 给资源对象绑定,如:window、img等。		                        
(2)DOMContentLoaded:当HTML解析完结构就触发,无需等待css样式,图片等加载完。// 给document对象绑定


//  8. 页面尺寸改变事件
(1) resize:


//  9. 页面滚动事件
/*  需要关注属性scrollTop、scrollLeft,这个属性是HTML标签的属性哦, 比如div.scrollTop, html的scrollTop   */
(1)scroll:页面滚动时触发。 // 给window对象绑定(其实给某一个元素绑定也行)    

2.事件对象

//  1. 公共事件对象的属性和方法
(1)e.target:		  返回触发事件的对象。(标准)
(2)e.srcElement:	  返回触发事件的对象。(非标准,IE6-8使用)
(3)e.type:			  返回事件类型,如click。        
(4)e.returnValue:	   阻止默认行为,比如让链接不跳转(非标准IE6-8使用)
(5)e.preventDefault(): 阻止默认行为(标准)     
(7)e.stoppPropagation() 阻止事件冒泡(标准)

//  2. 鼠标事件对象的属性        
(1)e.clientX:	返回鼠标相对浏览器窗口可视区的x坐标。
(2)e.clientX:	返回鼠标相对浏览器窗口可视区的y坐标。 
(3)e.pageX:		返回鼠标相对文档页面的x坐标。
(4)e.pageY:		返回鼠标相对文档页面的y坐标。        

//  3. 键盘事件对象的属性
(1)e.keycode(已淘汰!)
(2)e.key(获取用户按了哪个键)

//  4. 手指触摸事件对象的属性   (前三个获取集合,集合里面放着下面的属性)
(1)e.touches 	    返回正在触摸屏幕的所有手指的集合。(屏幕被多少个手指触摸)
(2)e.targetTouches	返回正在触摸当前DOM元素上的手指的集合。(DOM元素被多少个手指触摸)
(3)e.changedTouches	手指状态发生改变的集合,从有到无,从无到有。      
(1)e.targetTouches[0].pageX:	返回手指相对文档页面的x坐标。
(2)e.targetTouches[0].pageY:	返回手指相对文档页面的y坐标。   

04.原型链

(1)必须疏通一个理念:函数也是一个对象。

//  函数也是对象
(1) 构造函数既是一个函数,也是一个对象,Function是c++提供的函数,用于创建构造函数用的。(Function理解为创世神一般的存在)

//  只要是个对象,就有__proto__属性
(2) 每个对象都有一个原型对象,通过该'对象.__proto__'可以访问的到原型对象。

//  只要是个函数,就有prototype属性
(3) 每个函数有一个原型对象,通过'函数名.prototype'。(原型对象: 就是创造者的原型对象)

(2)原型链关系图必须吃透!

(3)原型的使用场景

//  1.如果想让所有对象,都拥有同一个属性或方法,可以这样做。
Object.prototype.myMethod = function() {
	console.log("该方法让所有调用者共享")
}

************************************************************************************

//  2.如果想让所有的函数,同时拥有一个方法,可以这样做:
Function.prototype.bind = function() {
	console.log("这就是为什么函数能调用bind方法")
}

************************************************************************************
    
//  3.创建一个干净的对象,即:一个没有原型的对象。(调用Object.create(原型对象, [属性组成的对象]))  
const obj = Object.create(null)
const user = {
    name: '思密达',
    age: 15
}    
Object.serPrototyOf(user, obj)	// 含义:把对象user的原型对象设置为obj。
    
************************************************************************************   
    
//  4.一次性往原型上添加多个属性/方法    
Star.prototype = {
  constructor: Star, 	// 看过来,需要将原型对象的constructor属性加回去,才能保证原型链不断开。
  sing: function(){},
  dance: function(){}
}    

(4)原型的面试题

__proto__指向的是:该对象创造者的原型对象。

05.计时器


(1)两种类型的计时器

//  倒计时定时器
(1) setTimeout(回调函数, 延迟毫秒数)
(2) clearTimeout(定时器名称)

//  循环计时器
(1) setInterval(回调函数, 间隔时间毫秒数)
(2) clearInterval(定时器名称)

(2)定时器的使用细节

//  开启定时器
function start() {
    if(timerId) return
	let timerId = setTimeout(function() {
		...
	}, 3000)
}

//  销毁定时器
function stop() {
    clearTimeout(timerId)
    timerId = null	// 不仅仅要clearTimeout(),还需要将原来的timerId设置为null哦。
}

06.本地存储


(1)SessionStorage

//(1)生命周期
页面共享: 只能同一个标签页才能访问,不同标签页不能访问。
生命周期: 当关闭浏览器窗口后,sessionStorage对象销毁。

//(2)方法(如果存储对象,需要转为JSON格式)
(1) 存储数据: sessionStorage.setItem(key,value)
(2) 获取数据: sessionStorage.getItem(key)
(3) 删除一个数据: sessionStorage.removeItem(key)
	删除全部数据:sessionStorage.clear()   

(2)LocalStorage

//(1)生命周期
页面共享: 存储在浏览器中,可以实现多页面共享。(但是不能跨域,跨域的时候无法获取数据)   
生命周期: 除非手动删除,否则数据一直存在。  

//(2)方法(如果存储对象,需要转为JSON格式)
(1) 存储数据: LocalStorage.setItem(key,value)
(2) 获取数据: LocalStorage.getItem(key)
(3) 删除一个数据: LocalStorage.removeItem(key)
	删除全部数据:LocalStorage.clear()      

07.Promise

08.动画

09.渲染帧


(1)问题描述:使用setInterval()会存在渲染帧率浮动的问题

(2)解决方案:使用H5新增的API:requestAnimationFrame()

10.ES6

1.展开运算符

妙用:把伪数组 => 真数组

//  示例1:
let arr = [1, 2, 3, 4]
console.log(...arr)

//  示例2:
let ary1 = [1, 2, 3, 4]
let ary2 = [7, 8, 9, 10];
let ary3 = [...ary1, ...ary2];  // 输出结果是:[1,2,3,4,7,8,9,10]

//  示例3: 
let lis = document.querySelector('li');
let arr = [...lis]; 

2.解构

(1)数组解构

// 准备数据:
let arr = [1, 2, 3, '草莓', ['小米','华为'] ];

// 方法1:直接解构赋值
let [s1, ...s2] = arr; 	// 此时s1的值为1,...s2的值为[2,3]
let [a, b, c, d, [e, f] ] = array;

// 方法2:间隔取值,比如说我只想取到草莓的值。
const [, , , a] = arr

(2)对象解构

// 准备数据
let person = {
  name: "张三",
  age: 18,
  address: {
      province: '广东',
      city: '深圳'
  }
};

// 方法1:解构后的变量用原名。
let { name, age, address: {province, city} } = person;
let { address: { province, city }} = person;

// 方法2:解构后的变量用别名。
const { name: username, age: myAge } = person

//  解构时使用默认值,如果person中不存在nickname属性,也不会导致解构出来是undefined。
const { name, age, nickname='思密达' } = person

(3)对象数组解构

// 准备数据
let pig = [
    {
      uname: '佩奇',
      age: 18
	},
    {
      uname: '小猪',
      age: 28
	}
];

// 解构赋值
let [{ uname:peiqi, age:page },{ uname:xiaozhu, age:xage }] = pig;

3.剩余参数

其实就是相对于Java中的可变形参,当数组看即可。

function fun(a, b, ...arr) {
   console.log(arr) 
}
fun(1,2);		 // 输出结果:[]
fun(1,2,3,4,5);   // 输出结果:[3,4,5]

4.this指向问题

(1)确定this指向的是谁

//  (1) 普通函数: 谁调用this就指向谁。(运行时才知道)

//  (2) 箭头函数: 这个this,无需知道谁调用函数,找到箭头函数的上一级作用域的this,就是箭头函数的this。(写代码时就能确定了)
function debounce() {
   return () => {
     console.log(this);  // 箭头函数的this = debounce函数的this,debounce函数被window调用,所以this指向window对象。
   }
}

debounce()()

(2)修改this指向(三种方法)

function add(a, b) {
   console.log(a, b);
   console.log(this);
}

//  方法1: 修改this的同时,还会调用函数。  	call(this指向的对象, 实参1, 实参2...) (了解!)
add.call(obj, 3, 4)

//  方法2: 修改this的同时,还会调用函数。 		 apply(this指向的对象, [实参1, 实参2...]) 
add.apply(obj, [3, 4])

//  方法3: 只会修改this指向,但是不会调用函数。   bind(this指向的对象, 实参1, 实参2...) (掌握!)
add.bind(obj, 3, 4)()


//  使用案例    
var btn = document.querySelector('button');
btn.onclick = function() {
   this.disabled = true;
   setTimeout(function(){
       this.disabled = false;
   }.bind(btn),3000);
}

5.属性描述符

(1)什么是属性描述符

// 源码
const user = {
  name: 'monica',
  age: 17
}

// 原理
const user = {
  // 属性 name 的描述符
  name: {
    value: 'monica',
    configurable: true, // 该属性的描述符是否可以被重新定义
    enumerable: true,   // 该属性是否允许被遍历,会影响for-in循环
    writable: true      // 该属性是否允许被修改
  },
  // 属性 age 的描述符
  age: {
    value: 'monica',
    configurable: true, // 该属性的描述符是否可以被重新定义
    enumerable: true,   // 该属性是否允许被遍历,会影响for-in循环
    writable: true      // 该属性是否允许被修改
  }
}

(2)获取对象的一个属性描述符:Object.getOwnPropertyDescriptor(obj, propertyName)

const user = {
  name: 'monica',
  age: 17
}

Object.getOwnPropertyDescriptor(user, 'name');
/*
{
    value: 'monica',
    configurable: true, // 该属性的描述符是否可以被重新定义
    enumerable: true, // 该属性是否允许被遍历,会影响for-in循环
    writable: true // 该属性是否允许被修改
}
*/

(3)修改对象的一个属性描述符:Object.defineProperty(obj, propertyName, descriptor)

const user = {
  name: 'monica',
  age: 17
};

Object.defineProperty(obj, 'name', {
  value: '邓哥', // 将其值进行修改
  enumerable: false, // 让该属性不能被遍历
  writable: false // 让该属性无法被重新赋值
})

6.getter、setter(vue数据劫持)

属性描述符中有两个特殊的配置,分别为getset,通过它们,可以把属性的取值和赋值变为方法调用。

const obj = {
    a: 10
};

Object.defineProperty(obj, 'a', {
  get() { 		// 读取属性a时,得到的是该方法的返回值
    return 1;
  },
  set(val) { 	// 设置属性a时,会把值传入val,调用该方法
    console.log(val)
  }
})

console.log(obj.a); // 输出:1
obj.a = 3; // 输出:3
console.log(obj.a); // 输出:1

7.参数默认值

// 对参数 b 使用了默认值1, 对参数 c 使用默认值2
const method = (a, b = 1, c = 2, d) => {
  console.log(a, b, c, d)
}
method(1, 2); // 1 2 2 undefined
method(1); // 1 1 2 undefined
method(1, undefined, undefined, 4); // 1 1 2 4

11.BOM、DOM操作

待更新。。。

12.回流、重绘

回流:就是重新计算DOM节点的大小、位置。(耗费性能
重绘:就是重新绘界面。

(1)回流

// 1.引起回流的情况
(1)修改DOM的几何信息的大小、位置。(例如:width、height、margin、padding)
(2)读取DOM的几何信息的大小、位置。(例如:clientWidth、clientHeight)

// 2.浏览器对回流的优化:如果发生了连续回流,就只会回流一次。
dom.style.width = '100px'
dom.style.height = '200px'
dom.style.left = '100px'
dom.style.top = '100px'

// 3.强制回流
去看第一个引起回流的情况即可。

(2)重绘

// 1.什么情况会引起重绘
(1)修改背景颜色、字体颜色
(2)修改圆角边框
(3)修改背景图片

(3)回流和重绘哪个耗费性能?

回流耗费性能,它需要利用CPU进行计算。

重绘几乎不会耗费性能,它是利用GPU工作的。

(4)怎么解决回流问题?

使用transform来代替margin,但是transform本身其实是会导致回流的,真正的解决方案是:给消耗性能的盒子设置【transform:translateZ(0px)】,开启三维空间,此时该盒子就会被分配到另外一个图层,该图层完全由GPU进行管理,这就是所说的开启了GPU加速,此时才不会发生回流,但是一定会发生重绘。

13.async和defer的区别

相同点:async和defer都是异步加载的,也就是说JS会和HTML同时加载。
不同点:async的话,JS加载完马上执行,执行期间HTML会停止解析,等JS执行完毕。

defer的话,当JS加载完后,不会马上执行,要等HTML解析完后,才会执行JS。


defer的好处:能解决首次加载出现白屏的问题。(因为它会等HTML执行完,才执行JS)

3.JS骚操作

01.防抖、节流


(1)防抖

// 防抖函数
function debounce(callback, duration) {
   let timer = null;
   return function(...args) {
       if (timer) clearTimeout(timer)
       timer = setTimeout(() => { callback.apply(this, args) }, duration) 
   }
}

// 防抖函数的使用示例
let box = document.querySelector('.box')
let debounce_instace = debounce(function () {
    console.log(111);
}, 1000)

box.addEventListener('keyup', debounce_instace)

(2)节流

// 节流函数
function throttle(callback, duration) {
   let flag = true;
   return function(...args) {
       if (flag) {
           flag = false;
           setTimeout(() => {
               callback.apply(this,args)
               flag = true
           }, duration)
       }
   }
}


// 节流函数的使用示例
box.addEventListener('mousemove', throttle())function play() {
   this.innerHTML = parseInt(this.innerHTML) + 1
   console.log(this);
}

let div = this.document.querySelector('div')
div.addEventListener('mousemove', throttle(play, 3000))

02.对象克隆


深克隆(三种方法)

//  方法1: 使用Lodash库,调用"_.cloneDeep(传入要拷贝的对象)"方法,返回新的克隆对象。
    
//  方法2:把对象转为JSON字符串,然后再把JSON字符串转回对象。(很妙!)

//  方法3:递归实现深拷贝
function deepClone(oldObject) {
  let newObject = null;
  oldObject instanceof Array ? newObject = [] : newObject = {};
  for (let key in oldObject) {
      if (oldObject[key] instanceof Array) {
          newObject[key] = deepClone(oldObject[key]);
      } else if (oldObject[key] instanceof Object) {
          newObject[key] = deepClone(oldObject[key]);
      } else {
          newObject[key] = oldObject[key]
      }
  }
  return newObject;
}

使用示例:

//  准备数据
var data = {
  id: 1,
  name: '家电',
  arr: [11, 22, 33, 44, { username: 'zhangsan', password: 123 } ],
  goods: {
      id: 11,
      gname: '冰箱',
      family: {
          hobby: 1,
          sex: '男'
      }
  }
}   

// 调用深克隆函数
deepClone(data)

03.对象合并


方法1:循环判断

function mixObject(obj1, obj2) {
	let newObj = {}
    // 将obj2的键值对拷贝到newObj中
    for(let prop in obj2) {
        newObj[prop] = obj2[prop]
    }
    // 找obj1有的属性,但是obj2中没有的属性,放到newObj中
    for(let prop in obj1) {
        if(!(prop in obj2)) {
            newObj[prop] = obj1[prop]
        }
    }
    return newObject
}

方法2:Object.assign(gn(对象1, 对象2, 对象3, ... )

// 功能:该方法会将后面的对象的属性合并到前面的对象,一直往前合并到最前面的对象,然后返回最前面的那个对象。
// 常用写法如下:这样就不怕修改最前面的那个对象本身了。

Object.assign({}, obj1, obj2)

方法3:ES6的扩展运算符

const obj = {...obj1, ...obj2}

应用场景:一个函数,需要传入对象options作为参数,如果参数不全,则需要用默认值来替代。

// (1)准备一个对象作为数据
const obj = {
    name: '张益达',
    dog: '小明'
}

// (2)该函数需要传入对象options作为参数,如果参数不全,则需要用默认值来替代,最终返回合并后的对象。
function complicate(options) {
    const obj = {
        name: '思密达',
        age: 18,
        home: 'China',
        dog: '小妹'
    }
    return Object.assign({}, obj, options)
}

// (3)最终合并后的结果:{ name: '张益达', age: 18, home:'China', dog:'小明'  }
complicate(obj)

04.树形结构 转 平铺结构


步骤1:准备数据1:平铺结构

  //  准备数据
let oldArray = [
    {
        id: 1,
        pid: 0,
        name: '传智教育',
    },
    {
        id: 2,
        pid: 1,
        name: '财务部',
    },
    {
        id: 3,
        pid: 1,
        name: '教育部',
    },
    {
        id: 4,
        pid: 3,
        name: '教育部长',
    },
    {
        id: 5,
        pid: 2,
        name: '财务部长',
    }
]

步骤1:准备数据2:树状数据


let oldArray = [
    {
        id: 1,
        pid: 0,
        name: '传智教育',
        children: [
            {
                id: 2,
                pid: 1,
                name: '财务部',
                children: [
                    {
                        id: 5,
                        pid: 2,
                        name: '财务部长',
                    }
                ]
            },
            {
                id: 3,
                pid: 1,
                name: '教育部',
                children: [
                    {
                        id: 4,
                        pid: 3,
                        name: '教育部长',
                    },
                ]
            },
        ]
    }
]

步骤2:转化函数:平铺转树状核心

/*
* @params {Array} list 要转化的数组。 
* @params {DOM} rootValue 根节点,开头为0。
*/
function getTreeData(list, rootValue) {
   const arr = []
   list.forEach(item => {
       if(item.pid === rootValue) {
          arr.push(item)
          const children = getTreeData(list, item.id)
          item.children = children
       }
   })
   return arr
}

步骤2:转换函数:树状转平铺核心

// 递归(树形结构 转 平铺结构)
function getArrayData(list) {
    const arr = []
    list.forEach(item => {
        arr.push(item)

        // 判断有没有children属性
        if(item.children) {
           const newArr = getArrayData(item.children)
           arr.push(...newArr)
           // 如果有些人item的chidren为空数组,那没必要存在这个属性了,删掉。
           delete item.children 
        }
    })
	return arr
}

4.JS项目技术

01.无缝轮播图

02.大文件分片

03.模态框

04.参数归一化

05.手风琴效果

06.省市区三级联动效果

07.京东放大镜

  • 12
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值