整合篇整体内容比较多,详细点可以查询目录
一、JavaScript面向对象
1、面向对象编程介绍
1.1 两大编程思想
分为面向过程
和面向对象
两种编程思想是根据不同的程序需要来选择的,
比较简单的
还是推荐使用面向对象
来写,因为实现的步骤要更加简单一些
。
如果项目比较大
,需要多人合作的
,则选择面向过程
,因为便于维护和复用
。
2、ES6中的类和对象
<script>
// 1. 创建类 class 创建一个 明星类
class Star {
constructor(uname) {
this.uname = uname
}
}
// 2. 利用类创建对象 new
var ldh = new Star ('刘德华')
console.log(ldh.uname)
</script>
<script>
// 1. 创建类 class 创建一个 明星类
class Star {
constructor(uname) {
this.uname = uname
}
}
// 2. 利用类创建对象 new
var ldh = new Star ('刘德华')
var zxy = new Star ('张学友')
console.log(ldh.uname)
console.log(zxy.uname)
</script>
<script>
// 1. 创建类 class 创建一个 明星类
class Star {
constructor(uname,age) {
this.uname = uname
this.age = age
}
}
// 2. 利用类创建对象 new
var ldh = new Star ('刘德华',18)
var zxy = new Star ('张学友',20)
console.log(ldh) // 输出对象的所有属性
console.log(zxy)
</script>
1.我们类里面所有的函数不需要写function
2.多个函数方法之间不需要添加逗号分隔
<script>
// 1. 创建类 class 创建一个 明星类
class Star {
constructor(uname,age) {
this.uname = uname
this.age = age
}
sing(song){
// console.log('我唱歌')
console.log(this.uname + song)
}
}
// 2. 利用类创建对象 new
var ldh = new Star ('刘德华',18)
var zxy = new Star ('张学友',20)
console.log(ldh)
console.log(zxy)
// 1.我们类里面所有的函数不需要写function
// 2.多个函数方法之间不需要添加逗号分隔
ldh.sing('冰雨')
zxy.sing('李香兰')
</script>
3、类的继承
<script>
// 1.类的继承
class Father {
constructor(){
}
money(){
console.log(100)
}
}
class Son extends Father {
}
var son = new Son()
son.money()
</script>
<script>
class Father {
constructor(x,y){
this.x = x
this.y = y
}
sum(){
console.log(this.x + this.y)
}
}
class Son extends Father {
constructor(x,y){
super(x,y) // 调用父类中的构造函数
}
}
var son = new Son(1,2)
var son1= new Son(11,22)
son.sum()
son1.sum()
</script>
<script>
// super 关键字调用父级普通函数
class Father {
say(){
return '我是父级'
}
}
class Son extends Father {
say() {
// console.log('我是子级')
console.log(super.say())
// super.say() 就是调用父级中的普通函数 say()
}
}
var son = new Son()
son.say()
// 继承中的属性或者方法查找原则:就近原则
// 1.继承中,如果实例化子类输出一个方法,先看子类有没有这个方法,如果有就先执行子类的
// 2.继承中,如果子类里面没有,就去查找父类有没有这个方法,如果有,就执行父类的这个方法(就近原则)
</script>
利用super 调用父类的构造函数,super必须在子类this之前调用
<body>
<button>点击</button>
<script>
class Star {
constructor(uname,age) {
this.uname = uname
this.age = age
// 2.类里面的共有的属性和方法一定要加this使用.
// this.sing()
this.btn = document.querySelector('button')
// 应该是this.sing而不是this.sing(),加了括号那么打开网页的瞬间会直接调用
this.btn.onclick = this.sing
}
sing() {
// 不能直接log我们的uname,因为uname是构造函数里的,需要使用this进行指定
console.log(this.uname)
}
}
// 1.在 ES6中类没有变量提升,所以必须先定义类,才能通过类实例化对象
var ldh = new Star('刘德华')
</script>
4、案例:面向对象案例
该方法网址:https://developer.mozilla.org/zh-CN/docs/Web/API/Element/insertAdjacentHTML
重点注意
重新获取全部元素的方法
效果图:
Html文件,引入外部js文件
html代码和js代码如下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>面向对象 Tab</title>
<link rel="stylesheet" href="./styles/style.css">
<link rel="stylesheet" href="./styles/tab.css">
</head>
<body>
<main>
<h4>
Js 面向对象 动态添加标签页
</h4>
<div class="tabsbox" id="tab">
<!-- tab 标签 -->
<nav class="firstnav">
<ul>
<li class="liactive"><span>测试1</span><span class="iconfont icon-guanbi"></span></li>
<li><span>测试2</span><span class="iconfont icon-guanbi"></span></li>
<li><span>测试3</span><span class="iconfont icon-guanbi"></span></li>
</ul>
<div class="tabadd">
<span>+</span>
</div>
</nav>
<!-- tab 内容 -->
<div class="tabscon">
<section class="conactive">测试1</section>
<section>测试2</section>
<section>测试3</section>
</div>
</div>
</main>
<script src="../js/tab.js" ></script>
</body>
</html>
// 设置一个全局变量,方便调用
var that
class Tab {
constructor(id){
// 把我们constructor中的this赋值给全局变量that
that = this
// 获取元素
// 实例化一个最大的对象
this.main = document.querySelector(id)
this.add = this.main.querySelector('.tabadd')
// li的父元素
this.ul = this.main.querySelector('.firstnav ul:first-child')
// section的父元素
this.fsection = this.main.querySelector('.tabscon')
this.init()
}
init(){
this.updateNode()
// init 初始化操作让相关的元素绑定事件
// add不需要循环 所以不放在for里面
this.add.onclick = this.addTab
for(var i = 0; i < this.lis.length; i++){
this.lis[i].index = i
this.lis[i].onclick = this.toggleTab
this.remove[i].onclick = this.removeTab
this.spans[i].ondblclick = this.editTab
this.sections[i].ondblclick = this.editTab
}
}
// 因为动态添加元素 需要重新获取
updateNode() {
this.lis = this.main.querySelectorAll('li')
this.sections = this.main.querySelectorAll('section')
this.remove = this.main.querySelectorAll('.icon-guanbi')
this.spans = this.main.querySelectorAll('.firstnav li span:first-child')
}
// 1.切换功能
toggleTab(){
// this指的是init中对接的这个小li
// console.log(this.index)
// this.index即代表小li的索引号
// 先调用清空函数,注意不能直接使用,要用that调用
that.clearClass()
this.className = 'liactive'
that.sections[this.index].className = 'conactive'
}
// tab点击之后,使用 '' 来清空其他的样式
clearClass(){
for(var i = 0; i < this.lis.length; i++){
this.lis[i].className = ''
this.sections[i].className = ''
this.remove[i].onclick = this.removeTab
}
}
// 2.添加功能
addTab(){
that.clearClass()
var random = Math.random()
// (1)创建li元素和section元素
var li = '<li class="liactive"><span>新选项卡</span><span class="iconfont icon-guanbi"></span></li>'
var section = '<section class="conactive">新选项卡内容'+ random +'</section>'
// (2)把这两个元素追加到对应的父元素里面
that.ul.insertAdjacentHTML('beforeend', li)
that.fsection.insertAdjacentHTML('beforeend', section)
// 点击加号之后添加的小li和section属于后来的,init方法没法获取
// 点击加号之后再调用一次init 重新拿一次小li,给他们绑定事件获取最新的元素
that.init()
// that.className = 'liactive'
// that.sections[this.index].className = 'conactive'
}
// 3.删除功能
removeTab(e){
e.stopPropagation() //添加阻止冒泡,这样就不会触发点击事件
// 把父元素的索引号赋给它
var index = this.parentNode.index
console.log(index);
// 根据索引号删除对应的li 和section
// remove方法可以直接删除指定的元素
that.lis[index].remove()
that.sections[index].remove()
// 重新再调用init
that.init()
// 当我们删除的不是选中状态的li 的时候,原来的选中状态li保持不变
if(document.querySelector('.liactive')) return // return 则不再执行下面的代码
// 当我们删除了选中状态的这个li 的时候,让它的前一个li 处于选定状态
// 思路可以是当我删除这个li时,触发一个点击事件,点击这个li前面的li
index--
// 手动调用我们的点击事件 不需要鼠标触发
// 判断逻辑是that.lis[index]为真时 后面的点击事件才会触发
that.lis[index] && that.lis[index].click()
}
// 4.修改功能
editTab(){
var str = this.innerHTML
// 双击禁止选定文字
window.getSelection ? window.getSelection().removeAllRanges() : document.selection.empty()
// alert(11)
// 双击生成输入框
this.innerHTML = '<input type="text" />'
var input = this.children[0]
input.value = str
input.select() //文本框里面的文字处于选定状态
// 当光标离开了文本框就把文本框里面的值给span
input.onblur = function(){
this.parentNode.innerHTML = this.value
}
// 按下回车也可以把文本框里面的值给span
input.onkeyup = function(e) {
// 按下回车键时keyCode为13
if(e.keyCode === 13){
this.blur()
}
}
}
}
new Tab('#tab')
二、构造函数和原型
1. 构造函数和原型
<script>
// 1. 利用 new Object() 创建对象
var obj1 = new Object()
// 2. 利用 对象字面量创建对象
var obj2 = {}
// 3. 利用构造函数创建对象
function Star(uname, age){
this.uname = uname
this.age = age
this.sing = function(){
console.log('我会唱歌')
}
}
var ldh = new Star('刘德华',18)
var zxy = new Star('张学友',19)
console.log(ldh)
console.log(zxy)
ldh.sing()
zxy.sing()
</script>
<script>
// 构造函数中的属性和方法我们称为成员,成员可以添加
function Star(uname, age){
this.uname = uname
this.age = age
this.sing = function(){
console.log('我会唱歌')
}
}
var ldh = new Star('刘德华',18)
// 1.实例成员就是构造函数内部通过this添加的成员 uname age sing 就是实例成员
// 实例成员只能通过实例化的对象来访问
console.log(ldh.uname)
ldh.sing()
// console.log(Star.uname) // 不可以通过构造函数来访问实例成员
// 2. 静态成员 在构造函数本身上添加的成员 sex 就是静态成员
Star.sex = '男'
// 静态成员只能通过构造函数来访问
console.log(Star.sex)
console.log(ldh.sex)
</script>
解决方案就是使用原型对象prototype:
查找规则:根据原型链一直往上找
可以给对象添加原来没有的功能,就是自定义一些自己想使用的功能
<script>
// 原型对象的应用 扩展内置对象方法
console.log(Array.prototype);
Array.prototype.sum = function() {
var sum = 0
for(var i = 0; i < this.length; i++) {
sum += this[i]
}
return sum
}
var arr = [1, 2, 3]
console.log(arr.sum());
// log输出的结果就是数组的和:6
</script>
2. 继承
call()最主要的目的就是修改函数的this指向
注释
:不能直接将父级的原型
赋值给子级的原型
,这样的话如果修改了子级原型对象
,父级原型对象
也会跟着一起变化,修改思路是:创建一个父级实例对象
作为中介,Son原型对象
通过Father实例对象
由__proto__
访问Father原型对象
拿到money属性
ES6之前通过 构造函数 + 原型 实现面向对象编程
ES6 通过 类 实现面向对象编程
class本质上还是函数
语法糖
:语法糖就是一种便捷写法,简单理解,有两种方法可以实现同样的功能,但是一种写法更加清晰、方便,那么这个方法就是语法糖。
3. ES5中的新增方法
3.1 ES5新增方法概述
3.2 数组方法
我们挑更加重要的方法进行讲解
以前都是使用for循环进行数组遍历,以后可以用forEach()
<script>
// forEach 迭代(遍历)数组
var arr = [1,2,3]
var sum = 0
arr.forEach(function(value, index, array){
console.log('每个数组元素' + value)
console.log('每个数组元素的索引号' + index)
console.log('数组本身' + array)
sum += value
})
console.log(sum)
</script>
输出结果:
filter翻译过来就是“过滤器”的意思,主要用于筛选数组
<script>
// filter 筛选数组
var arr = [12, 88, 122, 201, 277]
var newArr = arr.filter(function(value, index){
// return value >= 20
// 即筛选数组中能被2整除的数(偶数)
return value % 2 === 0
})
console.log(newArr)
</script>
输出结果:
注意:
如果找到第一个满足条件的元素,则终止循环,不再继续查找了。(效率比较高)
<script>
var arr = [10,30,4,40]
var re = arr.some(function(value){
return value >= 20
})
console.log(re)
// if(re != 0) {
// alert(11)
// }
var arr1 = ['ts','ning','rookie','jk','bl']
var re1 = arr1.some(function(value){
return value == 'jk'
})
console.log(re1)
</script>
总结:
补充some方法的知识点:
some方法最后得到的是布尔值(true或false),如果返回布尔值是true则终止遍历,如果返回的是false则继续往下遍历
,有一个使用技巧
是可以在some里面添加if判断条件
对想要的数据进行检测筛选。
思路(代码选自本文商品案例):
点击按钮时,函数对data数组里的数据进行检测,是否有某个数据的pname和我们输入的内容product.value相同,符合条件则把这段数据放进中介数组arr中,同时一定记得要return布尔值返给some(因为some需要得到布尔值)
searchTwo.addEventListener('click',function(){
// alert(11)
// 中介arr数组 存放符合条件的对象
var arr = []
data.some(function(value){
if(value.pname === product.value) {
// console.log(value)
// 符合条件就把它放入到设置的数组arr中
arr.push(value)
return true // return后面必须写true
}
})
// 把数据渲染到页面中
setDate(arr)
})
案例:查询商品案例
案例代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
table {
width: 400px;
border: 1px solid #000;
border-collapse: collapse;
margin: 0 auto;
}
td,
th {
border: 1px solid #000;
text-align: center;
}
input {
width: 50px;
}
.search {
width: 600px;
margin: 20px auto;
}
</style>
</head>
<body>
<div class="search">
按照价格查询: <input type="text" class="start"> - <input type="text" class="end"> <button class="search-price">搜索</button> 按照商品名称查询: <input type="text" class="product"> <button class="search-pro">查询</button>
</div>
<table>
<thead>
<tr>
<th>id</th>
<th>产品名称</th>
<th>价格</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<script>
// 利用新增数组方法操作数据
var data = [{
id: 1,
pname: '小米',
price: 3999
}, {
id: 2,
pname: 'oppo',
price: 999
}, {
id: 3,
pname: '荣耀',
price: 1299
}, {
id: 4,
pname: '华为',
price: 1999
}, ];
// 1. 获取相应的元素
var tbody = document.querySelector('tbody')
var searchOne = document.querySelector('.search-price')
var start = document.querySelector('.start')
var end = document.querySelector('.end')
var searchTwo = document.querySelector('.search-pro')
var product = document.querySelector('.product')
setDate(data)
// 2. 把数据渲染到页面中 需要一个形参(mydata)来帮助与实参对接
function setDate(mydata) {
// 先清空里面的数据
tbody.innerHTML = ''
mydata.forEach(function(value){
// console.log(value)
var tr = document.createElement('tr')
tr.innerHTML = '<td>'+ value.id +'</td><td>'+ value.pname +'</td><td>'+ value.price +'</td>'
tbody.appendChild(tr)
})
}
// 3. 根据价格查询商品
// 当我们点击了按钮,就可以根据我们的商品价格去筛选数组里面的对象
searchOne.addEventListener('click',function(){
// alert(11)
// 筛选符合输入价格内的商品 放入newPrice数组中
var newPrice = data.filter(function(value){
// console.log(value.price)
return value.price >= start.value && value.price <= end.value
})
console.log(newPrice)
// 把筛选之后的对象渲染到页面上
setDate(newPrice)
})
// 4. 根据商品名称查找商品
// 如果查询数组中唯一的元素,用some方法更合适,因为它找到这个元素,就不再进行循环,效率更高。
searchTwo.addEventListener('click',function(){
// alert(11)
// 中介arr数组 存放符合条件的对象
var arr = []
data.some(function(value){
if(value.pname === product.value) {
// console.log(value)
// 符合条件就把它放入到设置的数组arr中
arr.push(value)
return true // return后面必须写true
}
})
// 把数据渲染到页面中
setDate(arr)
})
</script>
</body>
</html>
some和forEach的区别:
forEach():
即使找到了元素或者遇到return,也不会终止迭代
some():
从头开始找,一找到元素就会返回布尔值true,终止遍历,迭代效率更高
(如果查询数组中唯一的元素,用some方法更合适)
3.3 字符串方法
<body>
<input type="text"> <button>点击</button>
<div></div>
<script>
// trim方法 去除字符串两侧空格
var str = ' andy '
console.log(str);
var str1 = str.trim()
console.log(str1);
var input = document.querySelector('input')
var btn = document.querySelector('button')
var div = document.querySelector('div')
btn.onclick = function() {
// 避免在只输入几个空格时,被判断为有内容,使用trim方法对空格进行去除
// 使用str表示input.value.trim(),简化代码
var str = input.value.trim()
if(str === '') {
alert('您还没有输入内容')
} else {
console.log(str)
console.log(str.length)
div.innerHTML = str
}
}
</script>
</body>
效果如下:
3.4 对象方法
1. Object.keys()
<script>
// 用于获取对象自身所有的属性
var obj = {
id: 1,
pname: '小米',
price: 1999,
num: 2000
}
// 把获取的属性放到数组arr里面
var arr = Object.keys(obj)
console.log(arr)
// 遍历数组可以用forEach,拿到每个值
arr.forEach(function(value){
console.log(value)
})
</script>
(Object.keys)代码结果如下:
2. Object.defineProperty()
以往要给对象添加新属性或者修改原有属性,我们都是用=赋值来实现(如下图)
现在我们可以使用新方法Object.defineProperty()
在使用时,它的三个属性都是必要的
writable:(true/false)
:用于一些很重要的数据,false使这个数据不能被修改。
enumerable: (true/false)
:设置数据是否可以被遍历,比如一些地址和个人信息,它在对象中正常存在,但是在遍历时我们想保密隐私,给它设置enumerable: false(默认就是false)
configurable
:数据属性是否可以删除或再次修改
三、函数进阶
1. 函数的定义和调用
1.1 函数的定义方式
1.2 函数的调用方式
<script>
// 函数的调用方式
// 1. 普通函数
function fn() {
console.log('人生');
}
// 它有两种调用方式
fn() // 或者是:
fn.call()
// 2. 对象的方法
var o = {
sayHi: function() {
console.log('人生')
}
}
o.sayHi()
// 3. 构造函数
function Star() {}
new Star()
// 4. 绑定事件函数
btn.onclick = function() {} // 点击了按钮就可以调用函数
// 5. 定时器函数
setInterval(function() {}, 1000) // 这个函数是定时器自动1秒钟调用一次
// 6. 立即执行函数
(function() {
console.log('人生')
})()
// 立即执行函数是自动调用
</script>
2. this
代码结果:
巧用bind(): 在bind括号里面添加this就可以对延迟函数的this进行修改,因为bind是在btn1的点击函数里面,所以延迟函数的this经过修改就指向onclick的this了,而onclick的this就是点击对象btn1
有了这个bind方法,就可以不用像以前要加var that = this
来申明this对象
bind()的使用在后面是一个重点
bind应用面向对象tab栏:
使用bind()来替代原来的var that = this
思路解释:
情况是在功能方法中有些元素还在使用this,所以不能一刀切直接用bind引入that,而是在绑定事件时,使用bind先绑定元素自己,然后跟上一个this参数,再在功能方法里写入(that)形参对接bind里面的this实参。
// var that;
class Tab {
constructor(id) {
// 获取元素
// that = this;
this.main = document.querySelector(id);
this.add = this.main.querySelector('.tabadd');
// li的父元素
this.ul = this.main.querySelector('.fisrstnav ul:first-child');
// section 父元素
this.fsection = this.main.querySelector('.tabscon');
this.init();
}
init() {
this.updateNode();
// init 初始化操作让相关的元素绑定事件
this.add.onclick = this.addTab.bind(this.add, this);
for (var i = 0; i < this.lis.length; i++) {
this.lis[i].index = i;
this.lis[i].onclick = this.toggleTab.bind(this.lis[i], this);
this.remove[i].onclick = this.removeTab.bind(this.remove[i], this);
this.spans[i].ondblclick = this.editTab;
this.sections[i].ondblclick = this.editTab;
}
}
// 因为我们动态添加元素 需要从新获取对应的元素
updateNode() {
this.lis = this.main.querySelectorAll('li');
this.sections = this.main.querySelectorAll('section');
this.remove = this.main.querySelectorAll('.icon-guanbi');
this.spans = this.main.querySelectorAll('.fisrstnav li span:first-child');
}
// 1. 切换功能
toggleTab(that) {
// console.log(this.index);
that.clearClass();
this.className = 'liactive';
that.sections[this.index].className = 'conactive';
}
// 清除所有li 和section 的类
clearClass() {
for (var i = 0; i < this.lis.length; i++) {
this.lis[i].className = '';
this.sections[i].className = '';
}
}
// 2. 添加功能
addTab(that) {
that.clearClass();
// (1) 创建li元素和section元素
var random = Math.random();
var li = '<li class="liactive"><span>新选项卡</span><span class="iconfont icon-guanbi"></span></li>';
var section = '<section class="conactive">测试 ' + random + '</section>';
// (2) 把这两个元素追加到对应的父元素里面
that.ul.insertAdjacentHTML('beforeend', li);
that.fsection.insertAdjacentHTML('beforeend', section);
that.init();
}
// 3. 删除功能
removeTab(that, e) {
e.stopPropagation(); // 阻止冒泡 防止触发li 的切换点击事件
var index = this.parentNode.index;
console.log(index);
// 根据索引号删除对应的li 和section remove()方法可以直接删除指定的元素
that.lis[index].remove();
that.sections[index].remove();
that.init();
// 当我们删除的不是选中状态的li 的时候,原来的选中状态li保持不变
if (document.querySelector('.liactive')) return;
// 当我们删除了选中状态的这个li 的时候, 让它的前一个li 处于选定状态
index--;
// 手动调用我们的点击事件 不需要鼠标触发
that.lis[index] && that.lis[index].click();
}
// 4. 修改功能
editTab() {
var str = this.innerHTML;
// 双击禁止选定文字
window.getSelection ? window.getSelection().removeAllRanges() : document.selection.empty();
// alert(11);
this.innerHTML = '<input type="text" />';
var input = this.children[0];
input.value = str;
input.select(); // 文本框里面的文字处于选定状态
// 当我们离开文本框就把文本框里面的值给span
input.onblur = function() {
this.parentNode.innerHTML = this.value;
};
// 按下回车也可以把文本框里面的值给span
input.onkeyup = function(e) {
if (e.keyCode === 13) {
// 手动调用表单失去焦点事件 不需要鼠标离开操作
this.blur();
}
}
}
}
new Tab('#tab');
3. 严格模式
<script>
'use strict'
// 1. 我们的变量名必须先声明再使用
// num = 10 // 应该是 var num = 10
// console.log(num)
// 2. 我们不能随意删除已经声明好的变量
// delete num // 删除已经声明好的变量会报错
</script>
函数必须声明在顶层,不能放到if或者for等的里面
网页链接:link
4. 高阶函数
<script>
// 高阶函数 - 函数可以作为参数传递
function fn(a, b, callback) {
console.log(a + b)
// 执行回调函数
callback && callback()
}
fn(1, 2, function() {
console.log('我是最后调用的')
})
</script>
5. 闭包
JS中的闭包和异步并称为两大难点
闭包本身就是一个函数
案例1. 循环注册点击事件
(很典型的闭包面试题)
<body>
<ul class="nav">
<li>榴莲</li>
<li>臭豆腐</li>
<li>鲱鱼罐头</li>
<li>大猪蹄子</li>
</ul>
<script>
// 闭包应用-点击li输出当前li的索引号
// 方法1. 我们可以利用动态添加属性的方式
var lis = document.querySelector('.nav').querySelectorAll('li')
// for(var i = 0; i< lis.length; i++) {
// lis[i].index = i
// lis[i].onclick = function(){
// console.log(this.index)
// }
// }
// 方法2. 利用闭包的方式得到当前小li的索引号
for(var i = 0; i < lis.length; i++){
// 利用for循环创建了4个立即执行函数
// 立即执行函数也称为小闭包 因为立即执行函数里面任何一个函数都可以使用它的i变量
(function(i) {
lis[i].onclick = function(){
console.log(i)
}
// 为立即执行函数接收i
})(i)
}
</script>
案例2. 循环中的setTimeout()
<body>
<ul class="nav">
<li>榴莲</li>
<li>臭豆腐</li>
<li>鲱鱼罐头</li>
<li>大猪蹄子</li>
</ul>
<script>
// 闭包应用-3秒钟之后,打印所有li元素的内容
var lis = document.querySelector('.nav').querySelectorAll('li')
for(var i = 0; i < lis.length; i++) {
// 用立即执行函数这个小闭包配合定时器
(function(i) {
setTimeout(function() {
console.log(lis[i].innerHTML)
}, 3000)
})(i)
}
</script>
案例3. 计算打车价格
<script>
// 闭包应用-计算打车价格
// 打车起步价13¥(3公里内),之后每公里5¥,用户输入公里数就可以计算打车价格
// 如果有拥堵情况,总价格多收取10¥拥堵费
var car = (function fn() {
var start = 13 // 起步价
var total = 0 // 总价
return {
// 正常的总价price
price: function(n) {
if(n <= 3) {
total = start
} else {
total = start + (n - 3) * 5
}
return total
},
yd: function(flag) {
return flag ? total + 10 : total
}
}
})()
console.log(car.price(5)) // 23
console.log(car.yd(true)); // 33
console.log(car.price(1)); // 13
console.log(car.yd(false)); // 13
</script>
6. 递归
递归实用1:求求1~n的阶乘 1 * 2 * 3 * 4 * ..n
<script>
// 利用递归函数求1~n的阶乘 1 * 2 * 3 * 4 * ..n
function fn(n) {
if(n == 1) {
return 1
}
// 原理就是 从n开始一直往前面减一,一直到fn(n)中的n=1就return结束
return n * fn(n - 1)
}
// fn(3) = 3 * fn(2) = 3 * 2 * fn(1) = 3x2x1 =6
console.log(fn(3)); // fn(3) = 6
console.log(fn(4)); // fn(4) = 24
</script>
递归实用2:求斐波那契数列
<script>
// 利用递归函数求斐波那契数列(兔子数列) 1、1、2、3、5、8、13、21...
// 用户输入一个数字n就可以求出 这个数字对应的兔子序列值
// 我们只需要知道用户输入的n 的前面两项(n-1 n-2)就可以计算出n对应的序列值
function fb(n) {
if(n === 1 || n ===2) {
return 1
}
return fb(n-1) + fb(n-2)
}
console.log(fb(3)) // fb(3) = 2
console.log(fb(6)) // fb(6) = 8
</script>
<script>
var data =[{
id: 1,
name: '家电',
goods: [{
id: 11,
gname: '冰箱',
goods: [{
id: 111,
gname: '海尔'
}, {
id: 112,
gname: '美的'
}, ]
}, {
id: 12,
gname: '洗衣机'
}]
}, {
id: 2,
name: '服饰'
}]
// 我们想要做输入id号,就可以返回的数据对象
// 1. 利用forEach 去遍历里面的每一个对象
function getID(json, id) {
// o对象 是专门用来存放筛选完的数据的
var o = {}
json.forEach(function(item) {
// console.log(item); // 两个数组元素
if(item.id == id) {
// console.log(item);
o = item
// 2. 我们想得到里层的数据 11 12 可以用递归函数
// 里面应该有goods这个数组并且数组的长度不为0
// if()在外层找不到对应的id则else if往里面找id
} else if (item.goods && item.goods.length > 0) {
// 满足条件的话则调用getID()返回item.goods里面的数据对象
o = getID(item.goods, id)
}
})
return o
}
console.log(getID(data, 1));
console.log(getID(data, 2));
console.log(getID(data, 11));
console.log(getID(data, 12));
console.log(getID(data, 111));
</script>
浅拷贝:
<script>
// 浅拷贝只是拷贝一层,更深层次对象级别的只拷贝引用
// 深拷贝拷贝多层,每一级别的数据都会拷贝
var obj = {
id: 1,
name: 'andy',
msg: {
age: 18
}
}
var o = {}
// 方法1:for循环实现浅拷贝
// for(var k in obj) {
// k 是属性名 obj[k] 属性值
// o[k] = obj[k]
// }
// console.log(o)
// o.msg.age = 20
// console.log(obj)
// 方法2:语法糖 es6新增方法也可以实现浅拷贝,并且更简单
// 实际开发更推荐 Object.assign()
Object.assign(o, obj)
console.log(o)
o.msg.age = 20
console.log(obj)
</script>
深拷贝:
<script>
var obj = {
id: 1,
name: 'andy',
msg: {
age: 18
},
color: ['pink', 'red']
}
var o = {}
// 封装函数
function deepCopy (newobj, oldobj) {
for(var k in oldobj) {
// 判断我们的属性值属于哪种数据类型
// 1. 获取属性值 oldobj[k]
var item = oldobj[k]
// 2. 判断这个值是否是数组 (数组也属于Object,需要先把数组筛出去)
if (item instanceof Array) {
newobj[k] = []
deepCopy (newobj[k], item)
} else if (item instanceof Object) {
// 3. 判断这个值是否是对象
newobj[k] = {}
deepCopy(newobj[k], item)
} else {
// 4. 属于简单数据类型
newobj[k] = item
}
}
}
deepCopy(o, obj)
console.log(o)
var arr = []
// 数组也属于Object
console.log(arr instanceof Object);
// 深拷贝是拷贝里面的数据,所以这里修改age=20,影响不了obj里的age,因为age=18已经拷贝过来了不会被修改
o.msg.age = 20
console.log(obj)
</script>
四、ES6
1. ES6简介
旨在学习一些最新的、好用的语法规范
2. ES6的新增语法
2.1 let
let可以防止循环变量变成全局变量
2.2 const
2.3 解构赋值
解构:分解数据结构
赋值:为变量赋值
如果解构不成功,变量的值为undefined
注意:
{name: myName}中冒号左边的name只用于属性匹配,冒号右边的myName才是真正的变量
2.4 箭头函数
箭头函数是用来简化函数定义语法的
<script>
// 箭头函数是用来简化函数定义语法的
// const fn = () => {
// console.log(123);
// }
// fn()
// 在箭头函数中 如果函数体中只有一句代码
// 并且代码的执行结果就是函数的返回值,函数体大括号可以省略
const sum = (n1, n2) => n1 + n2
console.log(sum(1, 2));
</script>
<script>
// 在箭头函数中,如果形参只有一个,那么形参外侧的小括号也是可以省略的
const fn = v => {
alert(v)
}
fn(20)
</script>
<script>
// 箭头函数不绑定this,箭头函数没有自己的this关键字
// 如果在箭头函数中使用this,this关键字将指向箭头函数定义位置中的this
const obj = { name: '张三'}
function fn() {
console.log(this)
return () => {
console.log(this)
}
}
// fn.call(obj) call方法: fn的this指向obj
const resFn = fn.call(obj)
resFn()
</script>
箭头函数面试题
问:alert弹出的值是多少?
答案是undefined
,因为obj是一个对象
,对象是不能产生作用域的
,所以箭头函数是被定义在全局作用域下
,调用say方法的时候alert(this.age)中的this是指向window
,而在window下没有age属性,因此弹出来的值是undefined
2.5 剩余参数
// ...args是形参(args是一个数组),与实参对应,
// 在args前面加省略号代表接收所有的实参。
const sum = (...args) => {
let total = 0
// forEach用于遍历数组(数组有多少个数就循环多少次)
// item表示当前这一次循环的这一项
args.forEach(item => total += item)
return total
}
console.log(sum(10, 20)) // 输出结果30
console.log(sum(10, 20, 30)) // 输出结果60
<script>
let students = ['ts', 'ning', 'rk','jk', 'bl']
let [top1, ...mid] = students
console.log(top1); // ts
console.log(mid); // ['ning', 'rk', 'jk', 'bl']
let students = ['ts', 'ning', 'rk','jk', 'bl']
let [top1, jug, ...mid] = students
console.log(top1); // ts
console.log(jug); // ning
console.log(mid); // ['rk', 'jk', 'bl']
</script>
3. ES6的内置对象扩展
3.1 Array的扩展方法
3.1.1 扩展运算符
扩展运算符和剩余参数正好是相反的,剩余参数是将剩余的数组放到一个数组中,而扩展运算符是将数组或对象拆分成用逗号分隔的参数序列。
<script>
let ary = [1, 2, 3]
// ...ary 即为 1, 2, 3
console.log(...ary); //输出 1 2 3
console.log(1, 2, 3); //输出 1 2 3
</script>
console.log(…arg)输出结果中没有逗号是因为逗号被认作是console.log方法的分隔符。
<script>
// 合并数组
// 方法1:
let ary1 = [1, 2, 3]
let ary2 = [4, 5, 6]
// 将数组1和数组2合并,名为数组3
let ary3 = [...ary1, ...ary2]
console.log(...ary3) // 1, 2, 3, 4, 5, 6
// 方法2:
let ary1 = [1, 2, 3]
let ary2 = [4, 5, 6]
// push方法:将数组2中的456放到数组1中了
ary1.push(...ary2)
console.log(...ary1) // 1, 2, 3, 4, 5, 6
</script>
注意:
JS中伪数组
又叫类数组
<body>
<div>1</div>
<div>4</div>
<div>3</div>
<div>6</div>
<div>2</div>
<div>5</div>
<script>
let oDivs = document.getElementsByTagName('div')
console.log(oDivs); // 输出伪数组
// oDivs 是一个伪数组,在oDivs前加...使其变成以逗号分隔的参数序列
// 再在参数序列...oDivs外面加[],把它变成真正的数组
// 为什么转真数组?因为真数组下面的方法才能被调用
var ary = [...oDivs]
console.log(ary); // 输出真数组
ary.push('a')
console.log(ary)
</script>
</body>
上面代码打印结果如下:
3.1.2 构造函数方法:Array.from()
注意:
JS中伪数组
又叫类数组
<script>
// arr 的值是一个伪数组
let arr = {
'0': 'a',
'1': 'b',
'2': 'c',
length: 3
}
// Array.from方法接收一个伪数组,返回值就是一个真正的数组
let newArr = Array.from(arr)
// 真数组newArr
console.log(newArr);
</script>
item => item*2 是第二参数,意为在函数内部将所处理的每个值乘以2
代码输出结果如下:
3.1.3 实例方法:find()
<script>
let ary = [{
id: 1,
name: '张三'
}, {
id: 2,
name: '李四'
}]
// 可以通过id或name进行查找
// item指当前循环到的那个值,index当前循环到的索引(当前没用到index所以可以省略)
// let target = ary.find((item, index) => item.id == 2)
let target = ary.find((item) => item.name == '李四')
console.log(target)
3.1.4 实例方法:findIndex()
<script>
let ary = [1, 5, 10, 15]
// 寻找数组中第一个大于9的数的位置
let index = ary.findIndex((value, index) => value > 9)
console.log(index) // 输出2(10大于9,10的索引号index是2)
// let index1 = ary.findIndex((value, index) => value > 90)
// console.log(index1) // 输出-1(没有找到)
</script>
3.1.5 实例方法:includes()
<script>
let re = [1, 2, 3].includes(2)
console.log(re); // true
let re1 = [1, 2, 3].includes(22)
console.log(re1); // false
let ary = ["a", "b", "c"]
let ce = ary.includes('a')
console.log(ce) // true
let ce1 = ary.includes('e')
console.log(ce1) // false
</script>
3.2 String的扩展方法
3.2.1 模板字符串
<script>
let name = '张三'
let sayHello = `hello,my name is ${name}`
console.log(sayHello); // hello,my name is 张三
</script>
3.2.2 实例方法:startsWith() 和 endsWith()及repeat()
3.3 Set数据结构
注意:Set不会存储重复值
set数据解构可以用作记录搜索历史记录,方便下次直接点击历史搜索。使用时Set会判断是否重复,如果重复则不会存储。
<script>
const s1 = new Set()
console.log(s1.size) // 输出0,因为没有数据
// size表示数据的数量
const s2 = new Set(["a", "b"])
console.log(s2.size) // 输出2,有a和b两个数据
// 利用Set做数组去重
const s3 = new Set(["a", "a","b", "b"])
console.log(s3.size) // 仍然输出2,因为Set可以筛选重复值
// 用剩余参数 得到数组
const ary = [...s3]
console.log(ary)
</script>
<script>
const s = new Set()
s.add(1).add(2).add(3) // 向Set结构中添加值
console.log(s); // Set(3) {1, 2, 3}
s.delete(2) // 删除Set结构中的2值
console.log(s); // Set(2) {1, 3}
console.log(s.has(3)); // 输出true
console.log(s.has(2)); // 输出false
s.clear() // 清空Set数据结构里的值
console.log(s); // Set(0) {size: 0}
</script>
<script>
const re = new Set(['a', 'b', 'c'])
// 对re数组中的元素进行遍历,并且打印输出
re.forEach(value => {
console.log(value);
})
</script>
代码输出结果如下:
结语:到这里这篇博客终于结束了,这一部分的内容我觉得可以整合在一块,于是决定写一篇整合篇,这一写就是两个月(2022.5.19-2022.7.21),中间各种考试和工作耽误了很久,但总归是学完了,此时我一身轻松,感谢大家的阅读,有不对的地方欢迎指正。