web前端 - 系统化知识入门系列二: Es6知识巩固

212 阅读19分钟

由于文章字数限制,Es5部分的jQuery剩余小部分,迁移至了Es6 : 知识巩固

jQuery

jQuery 位置操作

位置主要有三个: offset()、position()、scrollTop()/scrollLeft()

1. offset() 设置或获取元素偏移

offset() 方法设置或返回被选元素相对于文档的偏移坐标,跟父级没有关系。 ② 该方法有2个属性 lefttopoffset().top 用于获取距离文档顶部的距离,offset().left 用于获取距离文档左侧的距离。
③ 可以设置元素的偏移:offset({ top: 10, left: 30 });

2: position() 获取元素偏移

position() 方法用于返回被选元素相对于带有定位的父级偏移坐标,如果父级都没有定位,则以文档为准。 ② 该方法有2个属性 lefttopposition().top 用于获取距离定位父级顶部的距离,position().left 用于获取距离定
位父级左侧的距离。
③ 该方法只能获取

scrollTop()/scrollLeft() 被卷去的头部和左侧

3. scrollTop()/scrollLeft() 设置或获取元素被卷去的头部和左侧
① scrollTop() 方法设置或返回被选元素被卷去的头部。 ② 不跟参数是获取,参数为不带单位的数字则是设置被卷去的头部。

$(function () {
// 1. 获取设置距离文档的位置(偏移) offset
console.log($('.son').offset()) // {top: 110, left: 110}
console.log($('.son').offset().top) // 110
$('.son').offset({ //设置子元素的位置
    top: 150, // 注意值不允许用引号包裹和携带px单位
    left: 150,
})

// 2. 获取距离带有定位父级位置(偏移) position   如果没有带有定位的父级,则以文档为准
console.log($('.son').position()) // {top: 10, left: 10}
$('.son').position({ // 设置无效,这个方法只能获取不能设置偏移
    top: 150,
    left: 150,
})
})

点击返回顶部

$(function () {
var containerTop = $('.container').offset().top
// 1:显示与隐藏返回顶部按钮
$(window).scroll(function () {
    if ($(document).scrollTop() >= containerTop) {
    $('.back').fadeIn()
    } else {
    $('.back').fadeOut()
    }
})
// 2:点击回到顶部
$('.back').click(function () {
    $('body, html').stop().animate({
    scrollTop: 0,
    })
})
})

购物车案例 画图分析↓↓↓ 电梯导航案例

事件绑定和解绑

事件处理on绑定一个或者多个事件

1:单个事件注册

$("div").click(function() {
    $(this).css("background", "purple");
});

事件处理 on() 绑定事件

on() 方法在匹配元素上绑定一个或多个事件的事件处理函数
$('div').on({
    mouseenter: function () {
    $(this).css('background', 'red')
    },
    click: function () {
    $(this).css('background', 'blue')
    },
    mouseleave: function () {
    $(this).css('background', 'yellow')
    },
}) 

如果事件处理程序相同
$('div').on('mouseenter mouseleave', function () { //多个事件中不用, 逗号隔开,直接空格连写
    $(this).toggleClass('current')
})   

on实现事件委派和给动态元素绑定事件

可以事件委派操作 。事件委派的定义就是,把原来加给子元素身上的事件绑定在父元素身上,就是把事件委派给父元素。
$('ul').on('click', 'li', function () {
    $(this).css('background', 'yellow')
})

动态创建的元素,click() 没有办法绑定事件, on() 可以给动态生成的元素绑定事件
$('ol').on('click', 'li', function () { // 先绑定事件
    $(this).css('background', 'skyblue')
})
var li = $('<li>我是后面创建的li</li>') // 后创建元素
$('ol').append(li)

微博发布案例

$(function () {
// 1.点击发布按钮, 动态创建一个小li,放入文本框的内容和删除按钮, 并且添加到ul 中
$('.btn').click(function () {
    var li = $('<li></li>')
    li.html($('.txt').val() + '<a href="javascript:;">删除</a>')
    $('ul').prepend(li)
    li.slideDown()
    $('.txt').val('')
})
// 2.点击的删除按钮,可以删除当前的微博留言li
$('ul').on('click', 'a', function () {
    $(this)
    .parents('li')
    .slideUp(function () {
        $(this).remove()
    })
})
})

off解绑事件

$(function () {
$('div').on({
    click: function () {
    $(this).css('background', 'red')
    },
    mouseenter: function () {
    $(this).css('background', 'yellow')
    },
})
$('ul').on('click', 'li', function () {
    $(this).css('background', 'yellow')
})

$('div').off('mouseenter')
$('ul').off('click', 'li')

$('div').one('click', function () { //点击1次以后,就不再有效
    console.log(123)
})
})

自动触发事件

$(function () {
$('div').click(function () {
    alert('123')
})
$('div').click() // 自动触发点击事件
$('input').trigger('focus') //自动获取焦点
$('input').on('focus', function () {
    $(this).val('hao a you')
})
$('input').triggerHandler('focus') //输入框不会高亮 就是不会触发元素的默认行为
})

事件对象

$(function () {
$(document).click(function () {
    console.log('document')
})
$('div').click(function (e) {
    console.log('div')
    e.stopPropagation() // 阻止事件冒泡
})
})

对象拷贝extend

var targetObj = {
    id: 1,
    msg: {
    sex: '男',
    },
}
var obj = {
    id: 1,
    name: 'andy',
    msg: {
    sex: '女',
    },
}
// 1. 浅拷贝把原来对象里面的复杂数据类型地址拷贝给目标对象(修改任何一个对象,都会彼此修改,同步影响)
$.extend(targetObj, obj) 
// obj.msg.sex = '娃娃'
targetObj.msg.sex = '放牛娃'
console.log(targetObj) // {id: 1, msg: {sex: "放牛娃"}, name: "andy"}
console.log(obj) // {id: 1, msg: {sex: "放牛娃"}, name: "andy"}

// 2. 深拷贝把里面的数据完全复制一份给目标对象 如果里面有不冲突的属性,会合并到一起(修改任何一个对象,彼此独立,互不影响)
$.extend(true, targetObj, obj)
// obj.msg.sex = '娃娃'
targetObj.msg.sex = '放牛娃'
console.log(targetObj) // {id: 1,msg: {sex: "放牛娃"},  name: "andy"}
console.log(obj) // {id: 1,msg: {sex: "女"}, name: "andy"

多库共存

1. 如果$ 符号冲突 我们就使用 jQuery
2. 让jquery 释放对$ 控制权 让用自己决定
var suibian = jQuery.noConflict();
suibian.each();

todolist案例 - 画图分析 ↓↓↓

Es5 部分已经完结, 开始介绍Es6 和JS高级的内容知识

JS 高级

面向对象编程

面向对象编程介绍

两大编程思想
1:面向过程
面向过程
面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候再一个一个的依次调用就可以了。

2:面向对象
面向对象是把事务分解成为一个个对象,然后由对象之间分工与合作。

类和对象

1:对象
现实生活中:万物皆对象,对象是一个具体的事物,看得见摸得着的实物。例如,一本书、一辆汽车、一个人
可以是“对象”,一个数据库、一张网页、一个与远程服务器的连接也可以是“对象”。 在 JavaScript 中,对象是一组无序的相关属性和方法的集合,所有的事物都是对象,例如字符串、数值、数组、
函数等。
对象是由属性和方法组成的:
属性:事物的特征,在对象中用属性来表示(常用名词)
方法:事物的行为,在对象中用方法来表示(常用动词)
类 class
在 ES6 中新增加了类的概念,可以使用 class 关键字声明一个类,之后以这个类来实例化对象。
类抽象了对象的公共部分,它泛指某一大类(class)
对象特指某一个,通过类实例化一个具体的对象
面向对象的思维特点: 
1. 抽取(抽象)对象共用的属性和行为组织(封装)成一个类(模板)
2. 对类进行实例化, 获取类的对象

创建类和生成实例:

class Star {
constructor(name, age) {
    //constructor() 方法是类的构造函数(默认方法),用于传递参数,返回实例对象,通过 new 命令生成对象实例时
    // ,自动调用该方法。如果没有显示定义, 类内部会自动给我们创建一个constructor()
    this.name = name
    this.age = age
}
}
var ldh = new Star('刘德华', 18) // Star {name: "刘德华", age: 18}
var zxy = new Star('张学友', 20) // Star {name: "张学友", age: 20}

类中添加共有方法:

class Star {
constructor(name, age) {
    this.name = name
    this.age = age
}
// 我们类里面所有的函数写在constructor外面,不需要写function,多个函数方法之间不需要添加逗号分隔
sayHi(msg) {
    console.log(this.name + msg)
}
sing(song) {
    console.log(song)
}
}
var ldh = new Star('华仔', 18)
ldh.sayHi(':我爱死你们了')
ldh.sayHi('爱你一万年')

类继承extends和super关键字:

1: extends 子类继承父类
class Father {
    constructor(name) {
    this.name = name
    }
    sayHi(hi) {
    console.log(this.name + hi) //大儿子:去上学
    }
}
class Son extends Father {}
var son = new Son('大儿子') // Son {name: "大儿子"}
var father = new Father('爸爸') // Father {name: "爸爸"}
console.log(son)
console.log(father)
son.sayHi(':去上学')
2:super 关键字用于访问和调用对象父类上的函数。可以调用父类的构造函数,也可以调用父类的普通函数
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 father = new Father(2, 4)
var son = new Son(1, 3)
son.sum() // 4
father.sum() // 6
console.log(father) //Father {x: 2, y: 4}
console.log(son) //Son {x: 1, y: 3}

super调用父类普通函数以及继承中属性方法查找原则:
class Father {
sayHi() {
    return '我是爸爸'
}
}
class Son extends Father {
sayHello() {
    console.log(super.sayHi() + ':的儿子') // super 关键字调用父类普通函数
}
}
var son = new Son()
son.sayHello()

super必须放到子类this之前:
class Father {
constructor(x, y) {
    this.x = x
    this.y = y
}
sum() {
    console.log(this.x + this.y) // 8
}
}

class Son extends Father {
constructor(a, b) { // son可以与自己的形参名称,可以跟父的x, y不一样
    super(a, b)// super 相当于sum方法,映射到了这里
    this.a = a
    this.b = b
}
subtract() {
    console.log(this.a - this.b)//son 也可以有自己的方法
}
}
var son = new Son(5, 3) // 2
son.sum()
son.subtract()

类里面this指向问题:

class Star {
constructor(name) {
    this.name = name
    this.btn = document.querySelector('button') //可以直接从页面中获取某个元素,将它直接赋值给属性
    this.btn.onclick = this.sayHi // 所有的属性名前都必须加上this
}
sayHi() {
    console.log(this)
    console.log('that:' + this._that) // that:undefined
}
hello() {
    this._that = this // _that = this 必须加上this, 否则报错
    console.log(this) // Es6中没有变量提升
}
}

var ldh = new Star('华仔')
ldh.hello()

面向对象tab栏切换案例 - 画图分析 ↓↓↓

构造函数和原型对象

利用构造函数创建对象

概述
在典型的 OOP 的语言中(如 Java),都存在类的概念,类就是对象的模板,对象就是类的实例,但在 ES6之前,
JS 中并没用引入类的概念。
ES6, 全称 ECMAScript 6.02015.06 发版。但是目前浏览器的 JavaScript 是 ES5 版本,大多数高版本的浏
览器也支持 ES6,不过只实现了 ES6 的部分特性和功能。
在 ES6之前 ,对象不是基于类创建的,而是用一种称为构建函数的特殊函数来定义对象和它们的特征。

在 JS 中,使用构造函数时要注意以下两点:
1. 构造函数用于创建某一类对象,其首字母要大写
2. 构造函数要和 new 一起使用才有意义

new 在执行时会做四件事情: 
① 在内存中创建一个新的空对象。 
② 让 this 指向这个新的对象。 
③ 执行构造函数里面的代码,给这个新对象添加属性和方法。 
④ 返回这个新对象(所以构造函数里面不需要 return )。

创建对象可以通过以下三种方式:

1. 对象字面量
2. new Object()
3. 自定义构造函数
// 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)
ldh.sing()

实例成员和静态成员:

构造函数
JavaScript 的构造函数中可以添加一些成员,可以在构造函数本身上添加,也可以在构造函数内部的 this 上添
加。通过这两种方式添加的成员,就分别称为静态成员和实例成员。 
静态成员:在构造函数本上添加的成员称为静态成员,只能由构造函数本身来访问
实例成员:在构造函数内部创建的对象成员称为实例成员,只能由实例化的对象来访问

function Star(name) {
this.name = name
this.sayHi = function (hi) {
    console.log(hi)
}
}
var ldh = new Star('华仔')
console.log(ldh.name) //"华仔" 实例成员就是this添加的成员(旧成员)

Star.sex = '男'
console.log(Star.sex) // "男" ,直接在该构造函数上新增的属性,就是静态成员(对象上新增的成员)
console.log(ldh.sex) // undefined 因为是后面添加到构造函数上的属性,实例化对象是访问不到的。

构造函数原型对象prototype:

构造函数原型 prototype

构造函数通过原型分配的函数是所有对象所共享的。
JavaScript 规定,每一个构造函数都有一个 prototype 属性,指向另一个对象。注意这个 prototype 就是一
个对象,这个对象的所有属性和方法,都会被构造函数所拥有。
我们可以把那些不变的方法,直接定义在 prototype 对象上,这样所有对象的实例就可以共享这些方法。
问答?
1. 原型是什么 ? 
一个对象,我们也称为 prototype 为原型对象。
2. 原型的作用是什么 ? 
共享方法。
function Star(name) {
this.name = name
}
Star.prototype.sayHi = function (hi) {
console.log(hi) //因为工作函数中的方法浪费内存,所以一般情况下,构造函数中国只写属性,方法写在外面,直接写在构造函数的prototype上更推荐。
}
var hz = new Star('华仔')
hz.sayHi('大家好,我是华仔')

对象原型__proto__:

对象都会有一个属性 __proto__ 指向构造函数的 prototype 原型对象,之所以我们对象可以使用构造函数 prototype 原型对象的属性和方法,就是因为对象有 __proto__ 原型的存在。
__proto__对象原型和原型对象 prototype 是等价的
__proto__对象原型的意义就在于为对象的查找机制提供一个方向,或者说一条路线,但是它是一个非标准属性,因此实际开发中,不可以使用这个属性,它只是内部指向原型对象 prototype
function Star(name) {
this.name = name
}
Star.prototype.sayHi = function (hi) {
console.log(hi)
}
var hz = new Star('华仔')
hz.sayHi('大家好,我是华仔')
console.log(hz.__proto__ == Star.prototype) // true 

原型constructor构造函数:

constructor 构造函数
对象原型( __proto__)和构造函数(prototype)原型对象里面都有一个属性 constructor 属性 ,constructor 我们称
为构造函数,因为它指回构造函数本身。
constructor 主要用于记录该对象引用于哪个构造函数,它可以让原型对象重新指向原来的构造函数。
一般情况下,对象的方法都在构造函数的原型对象中设置。如果有多个对象的方法,我们可以给原型对象采取对象形式赋
值,但是这样就会覆盖构造函数原型对象原来的内容,这样修改后的原型对象 constructor 就不再指向当前构造函数了。
此时,我们可以在修改后的原型对象中,添加一个 constructor 指向原来的构造函数。
function Star(name) {
this.name = name
}
Star.prototype = {
constructor: Star,
sing: function (song) {
    console.log(song)
},
movie: function () {
    console.log('我是男一号')
},
}
var hz = new Star('华仔')
console.log(Star.prototype) // {constructor: ƒ, sing: ƒ, movie: ƒ}
console.log(hz.__proto__) // {constructor: ƒ, sing: ƒ, movie: ƒ} 指向了同一个构造函数

console.log(Star.prototype.constructor) // ƒ Star(name) {this.name = name}
console.log(hz.__proto__.constructor)  //  ƒ Star(name) {this.name = name} 也指向了同一个构造函数

构造函数实例和原型对象三角关系:

原型链:

每一个实例对象又有一个proto属性,指向的构造函数的原型对象,
构造函数的原型对象也是一个对象,也有proto属性,这样一层一层往上找就形成了原型链。
构造函数实例和原型对象三角关系:
1.构造函数的prototype属性指向了构造函数原型对象
2.实例对象是由构造函数创建的,实例对象的__proto__属性指向了构造函数的原型对象
3.构造函数的原型对象的constructor属性指向了构造函数,实例对象的原型的constructor属性也指向了构造函数

对象成员查找规则:

原型链和成员的查找机制
任何对象都有原型对象,也就是prototype属性,任何原型对象也是一个对象,
该对象就有proto属性,这样一层一层往上找,就形成了一条链,我们称此为原型链;

当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。
如果没有就查找它的原型(也就是 __proto__指向的 prototype 原型对象)。
如果还没有就查找原型对象的原型(Object的原型对象)。
依此类推一直找到 Object 为止(null)。
__proto__对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线。

看原型图:↑↑↑
主要的一条查找直线链查找关系: 
hz.__proto__ == Star.prototype ---->  Star.prototype == Object.prototype ----> Object.peototype ----> null

原型对象this指向:

构造函数中的this 指向我们实例对象.
原型对象里面放的是方法, 这个方法里面的this 指向的是 这个方法的调用者, 也就是这个实例对象
function Star(name) {
this.name = name
}
var that
Star.prototype.sayHi = function () {
console.log('hi')
that = this
}
var hz = new Star('华仔')
hz.sayHi()

console.log(hz === that) //true

利用原型对象扩展内置对象方法:

扩展内置对象
可以通过原型对象,对原来的内置对象进行扩展自定义的方法。比如给数组增加自定义求偶数和的功能。
注意:数组和字符串内置对象不能给原型对象覆盖操作 Array.prototype = {} ,只能是 Array.prototype.xxx = function(){} 的方式。
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()) //6

call方法的作用:
ES6之前并没有给我们提供 extends 继承。我们可以通过构造函数+原型对象模拟实现继承,被称为组合继承。
2.1 call()
调用这个函数, 并且修改函数运行时的 this 指向 
fun.call(thisArg, arg1, arg2, ...)
thisArg :当前调用函数 this 的指向对象
arg1,arg2:传递的其他参数
function fn(x, y) {
console.log(this) //{name: "zs"}
console.log(x + y) //5
}
var obj = {
name: 'zs',
}
fn.call(obj, 2, 3) //可以调用函数,可以改变这个函数的this指向 此时这个函数的this 就指向了obj这个对象

利用父构造函数继承属性:
核心原理: 通过 call() 把父类型的 this 指向子类型的 this ,这样就可以实现子类型继承父类型的属性。
function Father(name) {
this.name = name
}
function Son(name, age) {
Father.call(this, name) //相当于调用了父构造函数,所以子构造函数也继承了name属性。
this.age = age
}
var son = new Son('华仔', 18)
console.log(son) //Son {name: "华仔", age: 18}

利用原型对象继承方法:
借用原型对象继承父类型方法
一般情况下,对象的方法都在构造函数的原型对象中设置,通过构造函数无法继承父类方法。 
核心原理:
① 将子类所共享的方法提取出来,让子类的 prototype 原型对象 = new 父类() 
② 本质:子类原型对象等于是实例化父类,因为父类实例化之后另外开辟空间,就不会影响原来父类原型对象
③ 将子类的 constructor 从新指向子类的构造函数
function Father(name) {
this.name = name
}
Father.prototype.money = function () {
console.log(1000)
}
function Son(name, score) {
Father.call(this, name)
this.score = score
}
Son.prototype = new Father() // 1:子的原型对象先指向Father构造函数
Son.prototype.constructor = Son // 2:把子原型对象的constructor构造器指回Son构造函数
Son.prototype.exam = function () {
//在子原型对象上添加eaxm自己的考试方法
console.log('考试')
}
var son = new Son('华仔', 16, 99)
console.log(son) // Son {name: "华仔", score: 16}
console.log(Father.prototype) //{money: ƒ, constructor: ƒ}
console.log(Son.prototype.constructor)

forEach、some、filter方法及查询商品案例

ES5 中给我们新增了一些方法,可以很方便的操作数组或者字符串,这些方法主要包括:

1:数组方法

迭代(遍历)方法:forEach()
array.forEach(function(currentValue, index, arr))
var arr = [1, 2, 3]
var sum = 0
arr.forEach(function (val, index, array) { // 使用forEach数组求和
console.log(val) // 1, 2, 3 数组里面的每一项
console.log(index) // 0 1 2 数组中每一项的索引
console.log(array) //(3) [1, 2, 3] 数组本身
sum += val
})
console.log(sum) //6
filter()
array.filter(function(currentValue, index, arr))
filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素,主要用于筛选数组
注意它直接返回一个新数组
var arr = [12, 33, 45, 66, 32, 21, 13, 5, 7, 9]
//   用filter返回数组中能被2整除的每一项
var newArr = arr.filter(function (val, index) {
//使用filter过滤数组,并使用新数组接收
return val % 2 === 0
})
console.log(newArr) //(3) [12, 66, 32]
some()
array.some(function(currentValue, index, arr))
some() 方法用于检测数组中的元素是否满足指定条件. 通俗点 查找数组中是否有满足条件的元素
注意它返回值是布尔值, 如果查找到这个元素, 就返回true , 如果查找不到就返回false.
如果找到第一个满足条件的元素,则终止循环. 不在继续查找.
var arr = [1, 2, 3, 5]
使用some找出数组中是否满足判断条件的,返回的是布尔值
var flag = arr.some(function (val) {
    return val >= 2
})
console.log(flag) // true

var arr = ['red', 'pink', 'blue']
var flag1 = arr.some(function (val) {
return val === 'pink'
})
console.log(flag1) // true

查询商品案例

var data = [
{ id: 1, name: '小米', price: 999 },
{ id: 2, name: '荣耀', price: 1299 },
{ id: 3, name: 'oppo', price: 1599 },
{ id: 4, name: '华为', price: 1699 },
{ id: 5, name: '小米', price: 1999 },
]
var tbody = document.querySelector('tbody')
var searchPrice = document.querySelector('.search-price')
var start = document.querySelector('.start')
var end = document.querySelector('.end')
var product = document.querySelector('.product')
var btnSearch = document.querySelector('.search-pro')

//将数据渲染到页面表格中,一般多次操作dom渲染页面数据,都需要封装一个方法,之后需要渲染数据调用该方法即可。
function setDate(data) {
    tbody.innerHTML = ''
    data.forEach(function (val) {
    var tr = document.createElement('tr')
    tr.innerHTML = '<td>'+val.id+'</td><td>'+val.name+'</td><td>'+val.price+'</td>'
    tbody.appendChild(tr)
    })
}
setDate(data)

// 点击查询价格区间,返回对应商品,使用filter过滤用新数组接收,然后调用setDate重新渲染页面数据
searchPrice.addEventListener('click', function () {
var newDate = data.filter(function (value) {
    return value.price >= start.value && value.price <= end.value
})
setDate(newDate)
})

//some只会找出第一个后返回了,必须使用return true结束程序,将找出这个符合条件的放在一个新的数组中,调用setDate重新渲染页面
btnSearch.addEventListener('click', function () {
var arr = []
data.some(function (value) {
    if (value.name === product.value) {
    arr.push(value)
    return true
    }
})
setDate(arr)
})

some和forEach区别

var arr = ['pink', 'red', 'blue']
arr.forEach(function (val) {
    if (val === 'red') {
    console.log('找到了该元素') //找到了该元素
    return true
    }
    console.log('111') // 111 在forEach中,即使return true,它也会将数组中的每一项循环完,性能开销大
})

arr.some(function (val) {
    if (val === 'pink') {
    console.log('找到了该元素') //找到了该元素
    return true // return 在此处发挥了作用,到了这里就return退出程序了,后续代码就不在执行了。
    }
    console.log('222')
})

arr.filter(function (val) {
if (val === 'pink') {
    console.log('找到了该元素')//找到了该元素
    return true
}
console.log('333')//333 filter即使return,程序也不会打断,也会继续向后执行完,和forEach差不多
})

2:字符串方法

trim()
trim() 方法会从一个字符串的两端删除空白字符。
str.trim()
trim() 方法并不影响原字符串本身,它返回的是一个新的字符串。
var str = '  afdsdf  '
console.log(str.trim()) //afdsdf

//   案例:去除用户输入内容的前后空格
var btn = document.querySelector('button')
var div = document.querySelector('div')
var input = document.querySelector('input')
//   将输入框中的内容去除内容赋值给div,如果输入的内容为空,就弹出提示
btn.addEventListener('click', function () {
var iptVal = input.value.trim()
if (iptVal === '') {
    alert('请输入内容')
} else {
    div.innerHTML = iptVal
}
})

3:对象方法

Object.keys()
Object.keys() 方法返回一个所有元素为字符串的数组。
Object.keys(obj)
效果类似 forin

var obj = { id: 1, name: '小米', price: 1299, num: 10 }
var arr = Object.keys(obj) //效果和forEach差不多,但是返回的是一个数组,而forEach不是数组
console.log(arr) //(4) ["id", "name", "price", "num"]

arr.forEach(function (val) {
console.log(val) //"id" "name" "price" "num"
})

函数进阶

函数的定义方式

1. 函数声明方式 function 关键字 (命名函数)
2. 函数表达式 (匿名函数)
3. new Function() 
var fn = new Function('参数1','参数2'..., '函数体')
Function 里面参数都必须是字符串格式
第三种方式执行效率低,也不方便书写,因此较少使用

1. 自定义函数(命名函数)
function fn1() {}
2. 函数表达式 (匿名函数)
var fn2 = function () {}
3. 利用 new Function('参数1','参数2', '函数体');
var fn3 = new Function('a', 'b', 'console.log(a+b)')
4. 所有函数都是 Function 的实例(对象)
fn3(1, 2) //3
5. 函数也属于对象
console.log(fn3 instanceof Object) //true

函数的调用方式:

// 命名函数
function fn1() {
console.log('fn1')
}
fn1()
// 对象中的方法
var obj = {
sayHi: function () {
    console.log('obj.sayHi')
},
}
obj.sayHi()
// 构造函数
function Star() {}
new Star()
// 事件处理函数
btn.onclick = function(){}
// 定时器函数
setInterval(function(){}, 1000)
// 自调用函数
(function(){})()

函数内部的this指向:

function fn1() {
console.log(this) //window
}
fn1()

setInterval(function () {
console.log(this)
}, 1000) //window

(function () {
console.log(this) //window
})()

var o = {
sayHi: function () {
    console.log(this) // o
},
}
o.sayHi()

function Star() {}
Star.prototype.hello = function () {
console.log(this) // hz
}
var hz = new Star()

btn.onclick = function () {
console.log(this)//button
}

call、apply、bind的使用

call方法及其应用

call() 方法调用一个对象。简单理解为调用函数的方式,但是它可以改变函数的 this 指向。
var obj = {
    name: 'zs',
}
function fn1(a, b) {
    console.log(this) //{name: "zs"}
    console.log(a + b) // 5
}
fn1.call(obj, 2, 3)

function Father(name) {
this.name = name
}
function Son(name) {
Father.call(this, name)
console.log(this) //Son {name: "华仔"}
}
var son = new Son('华仔')

apply方法及其应用

// 感觉没有call好用,虽然指向了obj
var obj = {
name: 'zs',
}
function fn1(arr) {
console.log(this) //{name: "zs"}
console.log(arr) //red
}
fn1.apply(obj, ['red'])

// 借助Math.max求数组中最大和最小值
var arr = [1, 3, 5]
var max = Math.max.apply(Math, arr)
var min = Math.min.apply(Math, arr)
console.log(max, min) // 5, 1

bind方法基本使用

1. 不会调用原来的函数   可以改变原来函数内部的this 指向
2. 返回的是原函数改变this之后产生的新函数
3. 如果有的函数我们不需要立即调用,但是又想改变这个函数内部的this指向此时用bind
bind的感觉和call的很像
var obj = {
name: 'zs',
}
function fn1(a, b) {
console.log(this) //{name: "zs"}
console.log(a + b) //3
}
var fn2 = fn1.bind(obj, 1, 2)
fn2()

点击让按钮禁用,2秒以后让按钮恢复回来,通过bind将this指回了btn当前点击的按钮
var btns = document.querySelectorAll('button')
for (var i = 0; i < btns.length; i++) {
btns[i].onclick = function () {
    this.disabled = true
    setTimeout(
    function () {
        this.disabled = false
    }.bind(this),
    1000
    )
}
}

call和apply以及bind总结

相同点: 
都可以改变函数内部的this指向.
区别点: 
1. call 和 apply 会调用函数, 并且改变函数内部this指向.
2. call 和 apply 传递的参数不一样, call 传递参数 aru1, aru2..形式 apply 必须数组形式[arg]
3. bind 不会调用函数, 可以改变函数内部this指向.
主要应用场景: 
1. call 经常做继承. 
2. apply 经常跟数组有关系. 比如借助于数学对象实现数组最大值最小值
3. bind 不调用函数,但是还想改变this指向. 比如改变定时器内部的this指向.

严格模式

JavaScript 除了提供正常模式外,还提供了严格模式(strict mode)。ES5 的严格模式是采用具有限制性
JavaScript 变体的一种方式,即在严格的条件下运行 JS 代码。
严格模式在 IE10 以上版本的浏览器中才会被支持,旧版本浏览器中会被忽略。
严格模式对正常的 JavaScript 语义做了一些更改:
1. 消除了 Javascript 语法的一些不合理、不严谨之处,减少了一些怪异行为。
2. 消除代码运行的一些不安全之处,保证代码运行的安全。
3. 提高编译器效率,增加运行速度。
4. 禁用了在 ECMAScript 的未来版本中可能会定义的一些语法,为未来新版本的 Javascript 做好铺垫。比 如一些保留字如:class, enum, export, extends, import, super 不能做变量名
严格模式对 Javascript 的语法和行为,都做了一些改变。

1. 变量规定

① 在正常模式中,如果一个变量没有声明就赋值,默认是全局变量。严格模式禁止这种用法,变量都必须先用
var 命令声明,然后再使用。 ② 严禁删除已经声明变量。例如,delete x; 语法是错误的。

严格模式下 this 指向问题

① 以前在全局作用域函数中的 this 指向 window 对象。
② 严格模式下全局作用域中函数中的 thisundefined。 ③ 以前构造函数时不加 new也可以 调用,当普通函数,this 指向全局对象
④ 严格模式下,如果 构造函数不加new调用, this 指向的是undefined 如果给他赋值则 会报错 ⑤ new 实例化的构造函数指向创建的对象实例。
⑥ 定时器 this 还是指向 window 。 ⑦ 事件、对象还是指向调用者

函数变化

① 函数不能有重名的参数。 ② 函数必须声明在顶层.新版本的 JavaScript 会引入“块级作用域”( ES6 中已引入)。为了与新版本接轨,
不允许在非函数的代码块内声明函数。

'use strict' //全局开启严格模式

(function(){
    'use strict'// 函数内开启严格模式
})()

function fn(){
    'use strict'// 函数内开启严格模式
}
fn()


'use strict'
num = 10 
console.log(num) // num is not defined  我们的变量名必须先声明再使用

delete num //Delete of an unqualified identifier in strict mode. 我们不能随意删除已经声明好的变量

function fn1() {
    console.log(this) //undefined 严格模式下全局作用域中函数中的 this 是 undefined
}
fn1()

function Star(name) {
    this.name = name
}
var hz = new Star('华仔')
console.log(hz.name) //"华仔"

setTimeout(function () { //定时器 this 还是指向 window 
    console.log(this) // Window {parent: Window, opener: null, top: Window, length: 0, frames: Window, …}
}, 2000)

a = 1
a = 2
function fn3(a, a) {// 严格模式下函数里面的参数不允许有重名
console.log(a + a) //Duplicate parameter name not allowed in this context
}

闭包及应用

高阶函数

高阶函数- 函数可以作为参数传递
function fn1(a, b, callback) {
    console.log(a + b) // 4
    callback && callback()
}
fn1(1, 3, function () {
    console.log('我是回调函数') //我是回调函数
})

$('div').animate(
{
    left: 500,
},
function () {
    $('div').css('backgroundColor', 'blue')
}
)

什么是闭包

闭包(closure)指有权访问另一个函数作用域中变量的函数。 ----- JavaScript 高级程序设计
简单理解就是 ,一个作用域可以访问另外一个函数内部的局部变量
function fn1() {
var num = 10
function fn2() {
    console.log(num) // 10  闭包(closure)指有权访问另一个函数作用域中变量的函数。
}
fn2()
}
fn1()

闭包的作用

闭包(closure)指有权访问另一个函数作用域中变量的函数。
一个作用域可以访问另外一个函数的局部变量 
我们fn 外面的作用域可以访问fn 内部的局部变量
闭包的主要作用: 延伸了变量的作用范围

闭包应用-点击li打印当前索引号
var lis = document.querySelectorAll('li')
// 自调用函数将循环的i传递到形参中,延伸了变量的作用范围,就是闭包
for (var i = 0; i < lis.length; i++) {
;(function (i) {
    // 2: 接收自调用函数的实参
    lis[i].onclick = function () {
    console.log(i) //
    }
})(i) // 1: 实参给自调用函数传递到形参中,此时的i就是循环中的i,
}


闭包应用-3秒钟之后打印li内容
// 闭包应用-3秒钟之后,打印所有li元素的内容
var lis = document.querySelectorAll('li')
for (var i = 0; i < lis.length; i++) {
;(function (i) { // 自调用函数,拿到了循环中的i,传递到了定时器中,此时lis就可以使用i,将每个li的内容给输出来。
    setTimeout(function () {
    console.log(lis[i].innerHTML)
    }, 3000)
})(i)
}
闭包应用-计算打车价格
// 用对象的方式计算打车价格,道路顺畅就是price按照正常计算,拥堵就在原有的total上加上10元
var car = (function () { //用对象.方法的方式来写相对比较简单
var start = 13 // 起步价格 13元,3公里内都是13元
var total = 0 // 结算价格
return { // 将2个方法的结果返回给该对象, 
    price: function (n) {
    if (n <= 3) {
        total = start
    } else {
        total = start + (n - 3) * 5
    }
    return total //正常不拥堵,如果小于3公里就按起步几个结算,反之就是起步价格-3公里,用剩余的公里就是实际的车费
    },
    yd: function (flag) {
    return flag ? total + 10 : total//如果形参是true表示拥堵,返回价格+拥堵费10元,反之就是不拥堵
    },
}
})()

console.log(car.price(5)) // 23
console.log(car.yd(true)) //33  拥堵就在原有的total上加上10元

console.log(car.price(1)) // 13  跑1公里也按起步价格计算
console.log(car.yd(false)) //13 

递归及应用

什么是递归函数

如果一个函数在内部可以调用其本身,那么这个函数就是递归函数。
简单理解:函数内部自己调用自己, 这个函数就是递归函数
递归函数的作用和循环效果一样
由于递归很容易发生“栈溢出”错误(stack overflow),所以必须要加退出条件 returnvar num = 1 //1:计算器要从1开始,而不是0, 相当于 var i =1;
function fn1() {
console.log('我要打印6次') // 6 我要打印6次
if (num === 6) { //3:相当于 i <= num
    return // 要加退出条件,如果满足条件,就退出
}
num++ // 2:计数器累加, 相当于i++
fn1()  // 函数自己调用自己,相当于for的功能,启动了循环
}
fn1()

利用递归求阶乘

function fn(n) {
if (n == 1) { 
    return 1 // 如果是1,就返回1
}
return n * fn(n - 1)// 每次用传进来的实参-1,进行运算。类似于 i--
}
console.log(fn(3)) // 6

利用递归求斐波那契数列

// 1, 1, 2, 3, 5, 8, 13, 21
function fb(n) {
if (n === 1 || n === 2) {
    return 1
}
return fb(n - 1) + fb(n - 2)
}
console.log(fb(3)) // 2
console.log(fb(6)) //8

利用递归遍历数据

var data = [
{
    id: 1,
    name: '家电',
    goods: [
    {
        id: 11,
        name: '冰箱',
        goods: [
        { id: 111, name: '海尔' },
        { id: 112, name: '美的' },
        ],
    },
    {
        id: 12,
        name: '洗衣机',
    },
    ],
},
{ id: 2, name: '服饰' },
]
function getId(json, id) { //接收2个参数, 参数1数据源, 参数2要查询的id号
var obj = {} //新建空对象,用来存储找到的那个对象
json.forEach(function (item) { // 遍历数组中第一层的每个对象,判断对象的id和要查询的id1是否一致
    if (item.id == id) {
    obj = item //如果一致,就把该对象,存起来
    } else if (item.goods && item.goods.length > 0) { //遍历数组中带goods的对象,如果对象中还有goods子对象,就把goods里面的数据也遍历出来
    obj = getId(item.goods, id) // 把goods根据id查询到的对象,放对象中存储起来
    }
})
return obj //将找到的这个对象返回来。
}
console.log(getId(data, 1)) //{id: 1, name: "家电", goods: Array(2)}
console.log(getId(data, 11)) //{id: 11, name: "冰箱", goods: Array(2)}
console.log(getId(data, 111)) //{id: 111, name: "海尔"}
console.log(getId(data, 12)) //{id: 12, name: "洗衣机"}

浅拷贝

// Object.assign(a, b) 把b拷贝给a, 修改b的值,a的值也会被修改,这就是浅拷贝
var obj = {
name: 'zs',
msg: {
    age: 10,
},
}
var obj2 = {}
Object.assign(obj2, obj)
console.log(obj2) // { name: 'zs', msg: { age: 20 } }
obj.msg.age = 20
console.log(obj) //{name: "zs", msg: {age: 20}}

深拷贝

var obj1 = {
name: 'zs',
msg: {
    age: 18,
},
}
var obj2 = {}
//   自己封装函数进行对象深拷贝
function deepCopy(newObj, oldObj) {
for (var k in oldObj) {
    //for in遍历对象,遍历传递过来的旧对象
    var item = oldObj[k] //把属性的值放在item里面进行判断
    if (item instanceof Array) {
    newObj[k] = [] // 如果是数组类型的,就返回一个空数组
    deepCopy(newObj[k], item) // 将b的属性值拷贝给a
    } else if (item instanceof Object) {
    newObj[k] = {} // 如果是对象类型的,就返回一个空对象
    deepCopy(newObj[k], item) // 将b的属性值拷贝给a
    } else {
    newObj[k] = item //其他类型就直接拷贝
    }
}
}
deepCopy(obj2, obj1)
console.log(obj2) //{name: "zs", msg: {age: 18}}
obj1.msg.age = 20 //深拷贝修改b里面的属性,不会影响a里面的属性,相互独立,互不影响的。
console.log(obj1) //{name: "zs", msg: {age: 20}}
console.log(obj2) //{name: "zs", msg: {age: 18}

ES6中的let和const

ES6解构赋值

数组解构赋值

// 数组解构允许我们按照一一对应的关系从数组中提取值 然后将值赋值给变量
let arr = [1, 2, 3]
let [a, b, c, d] = arr // 如果是数组解构,声明变量时,也需要使用数组的形式声明来接收,多出来的变量,值是undefiend
console.log(a) //1
console.log(b) // 2
console.log(c) // 3
console.log(d) // undefined

对象解构

// 对象解构允许我们使用变量的名字匹配对象的属性 匹配成功 将对象属性的值赋值给变量
let obj = { name: 'zs', age: 18 }
let { name, age } = obj //因为解构的是对象类型的,所以声明变量的时候,也是用对象方式声明的
console.log(name) //"zs"
console.log(age) //18

let { name: myName } = obj //{原名:重命名}, 用新的变量名来接收,重命名变量名
console.log(myName) // "zs"

ES6箭头函数

箭头函数

箭头函数是用来简化函数定义语法的
const fn = () => {
	console.log('123')
}
fn() // "123"

在箭头函数中 如果函数体中只有一句代码 并且代码的执行结果就是函数的返回值 函数体大括号可以省略
const sum = (a, b) => a + b
console.log(sum(1, 5)) //6

在箭头函数中 如果形参只有一个 形参外侧的小括号也是可以省略的,但是格式化工具,有自动添加上了!
const fn = (n) => {
	console.log(n)
}
fn(20) //20

箭头函数中的this关键字

箭头函数不绑定this 箭头函数没有自己的this关键字 如果在箭头函数中使用this this关键字将指向箭头函数定义位置中的this
function fn() {
	console.log(this) //{name: "zs", age: 12}
	return () => {
		console.log(this) //{name: "zs", age: 12}
	}
}
const obj = { name: 'zs', age: 12 }
const objFn = fn.call(obj)
objFn()

剩余参数和拓展运算符

剩余参数

// 求任意个数组的和
const sum = (...args) => {
	// 用...解构数组,相当于把数组每项弹开当做形参摆在这里
	let total = 0
	args.forEach((item) => (total += item)) //forEach遍历数组,item就是每项,用total+=每项,将total计算的结果返回来。
	return total
}
// console.log(sum(1, 3)) //4
// console.log(sum(10, 20, 30)) //60

let arr = ['yellow', 'skyblue', 'deeppink']
let [a1, ...a2] = arr //因为是数组类型的,所有声明变量时也是使用数组形式的,变量是一一对应接收的,如果...a2,表示剩余的就给a2来存储。
console.log(a1) //"yellow"
console.log(a2) // (2) ["skyblue", "deeppink"]

扩展运算符

let arr = ['1', '2', '3']
console.log(...arr) // "1 2 3"

// 数组合并的方式一
let arr1 = [1, 2, 3]
let arr2 = [4, 5, 6]
let arr3 = [...arr1, ...arr2]
console.log(arr3) //(6) [1, 2, 3, 4, 5, 6]

// 数组合并的方式二
let arr1 = [1, 2, 3]
let arr2 = [4, 5, 6]
arr1.push(...arr2)
console.log(arr1) //(6) [1, 2, 3, 4, 5, 6]

var divs = document.querySelectorAll('div')
var divArr = [...divs]
console.log(divArr) //(6) [div, div, div, div, div, div]
divArr.push('a')
console.log(divArr) //(7) [div, div, div, div, div, div, "a"]

解构+剩余+拓展

Array实例方法

Array.from方法

使用Array.from将对象转换为数组
var obj = {
	0: 'zs',
	1: 'ls',
	length: 2, // 注意要声明length,且必须和属性个数一致
}
var arr = Array.from(obj) //将对象转换为数组,返回的是一个新的数组,里面存的是属性的值。
console.log(arr) //(2) ["zs", "ls"]

将对象里面的值返回,并乘以倍数。
var obj = {
	0: 2,
	1: 3,
	length: 2, // 注意要声明length,且必须和属性个数一致
}
var arr = Array.from(obj, (item) => item * 3) // item就是每一项属性值,将属性的值相乘
console.log(arr) //(2) [6, 9]

Array实例方法:find

// 数组方式.find,查找出id为2的对象。
var arr = [
	{ id: 1, name: 'zs' },
	{ id: 2, name: 'ls' },
]
let target = arr.find((item) => item.id == 2)
// 因为箭头函数里面只有1句话,所有不需要{},也不需要return,函数体的结果会直接返回给item
console.log(target) //{id: 2, name: "ls"}

Array实例方法:findIndex

// 数组.findIndex查找数组中的项,返回的是布尔值
let arr = [1, 3, 5]
let index = arr.findIndex((item) => item > 3)
console.log(index) // 返回索引2,也就是数组第3项的5,

Array实例方法:includes

// 数组.includes,查询数组是否包含该项,返回的是布尔值。
var arr = ['1', '2', '3']
let result = arr.includes('2')
console.log(result) //true

ES6模板字符串和set数据结构

模板字符串

`模板语法` 可以直接将变量名渲染`${变量名}`,也可以将对象里面的属性名给渲染`${对象.属性名}`,还可以直接调用函数`{fn()}`
可以直接将变量名渲染`${变量名}
let name = `zs`
let sayHi = `Hello, 我的名字叫${name}`
console.log(sayHi) //Hello, 我的名字叫zs

也可以将对象里面的属性名给渲染`${对象.属性名}
let result = {
	name: 'zhangsan',
}
let html = `
	<div>
		<span>${result.name}</span>
	</div>
`
console.log(html) <div><span>zhangsan</span></div>

还可以直接调用函数`{fn()}
const fn = () => {
	return '我是fn函数'
}
let html = `我是模板字符串:${fn()}` //我是模板字符串:我是fn函数
console.log(html)

startsWith方法和endsWith方法

// 查找字符串中是否包含该字符,startsWith endsWith, 返回布尔值。
let str = 'Hello ECMAScript 2015'
let r1 = str.startsWith('Hello') // true
console.log(r1)

let r2 = str.endsWith('2016')
console.log(r2) // false

repeat方法介绍

// 字符串repeat(次数), 会水平重复拷贝5份
console.log('y'.repeat(5)) //"yyyyy"

创建set数据结构

new Set(['a', 'b']) 创建新的数组
const s2 = new Set(['a', 'b'])
console.log(s2.size) //2

...s3 数组解构, 获取数组中的每项

const s3 = ['a', 'b', 'c']
console.log(...s3) //"a b c"
add()往数组中添加项
const s4 = new Set()
s4.add('a').add('b')
console.log(s4) //Set(2) {"a", "b"}

delete删除数组中的项, 返回布尔值

const r1 = s4.delete('b')
console.log(r1) // true

has查询数组中是否存在该项,返回布尔值

const r5 = s4.has('d')
console.log(r5) // false

清空数组

s4.clear()
console.log(s4) //Set(0) {}

forEach遍历数组

const s6 = new Set(['a', 'b', 'c'])
s6.forEach((value) => {
  console.log(value) //"a" "b" "c"
})

下一篇: web前端 - 系统化知识入门系列三: 前后端交互知识巩固