一.JavaScript 面向对象
1.面向对象编程介绍
- 面向过程:按照分析好的 步骤 解决问题。
- 面向过程实例:打开冰箱门,大象装进去,关上冰箱门
- 面向对象:以 对象功能 来划分问题,而不是步骤。
- 面向对象实例:大象对象(进去),冰箱对象(打开、关闭),使用冰箱和大象的功能
- 面向对象的特性: 封装性 、继承性 、多态性
- 面向过程优缺点:性能高(单片机),不易维护复用扩展,是份蛋炒饭
- 面向对象优缺点:易维护复用扩展,低耦合,性能低,是份盖浇饭
2.ES6 中的类和对象
- JavaScript 中,对象 是一组无序的属性(事物特征)和方法(事物行为)的集合,所有的事物都是对象
- 面向对象思维特点:
- 抽取(抽象)对象 共用的属性 和 行为 组织(封装)成一个 类(模板)
- 对 类 进行实例化,获取 对象
- constructor 构造函数:用于传递参数,返回实例对象(通过 new 自动调用)
- 方法之间 不能 逗号分隔,同时不需要添加 function 关键字
class Person { constructor(name,age) { // constructor 构造函数:用于传递参数返回实例对象(通过 new 自动调用) this.name = name; this.age = age; } // 方法之间不能加 逗号 分隔,不需要 function 关键字 say() { console.log(this.name + '你好'); }} // 下面是创建实例 var ldh = new Person('刘德华', 18); // 类必须使用 new 实例化对象 ldh.say()
3.类的继承
- extends 关键字:子类继承父类
- super 关键字:调用父类的 构造函数 / 普通函数
- 继承中的属性或者方法查找原则:就近原则
- 子类在构造函数中使用 super, 必须放到 this 前面 (必须先调用父类构造方法,再使用子类构造方法)
- ES6 中,类没有变量提升,所以必须先定义类,再实例化对象.
- 类里的 共有属性和方法 一定要加 this
- constructor 里的 this 指向 实例对象,方法里的 this 指向这个方法的调用者
class Father { // 创建类 类名后不要加小括号 constructor(surname) { // 类的共有属性放到 constructor 里 this.surname = surname; // 构造函数 this 指向 实例对象,new 生城实例 自动调用构造函数 } saySurname() { return '我的姓是' + this.surname; }} class Son extends Father { // 子类继承父类 extends constructor(surname, fristname) { super(surname); // super:调用父类构造函数 constructor(surname) this.fristname = fristname; //定义子类独有属性 子类使用super, 必须放 this 前面 } sayFristname() { return super.saySurname() + ",我的名字是:" + this.fristname); // super 调用父类方法 }} var damao = new Son('刘', "德华"); // 实例化子类对象 此时类名后记得加小括号 damao.sayFristname(); // 我的姓是刘,我的名字是:德华 // ======================================================================================= // 子类继承父类加法方法 同时 扩展减法方法 constructor(x, y) { super(x, y); this.x = x; this.y = y; } subtract() { console.log(this.x - this.y); }}
4.面向对象 Tab栏切换
- 功能需求:
- 点击 + 号, 可以添加 tab 项和内容项
- 点击 x 号, 可以删除当前的tab项和内容项
- 双击 tab项文字 或者 内容项文字,可以修改文字内容
- 抽象对象(Tab 对象)功能:切换、添加、删除、修改
4.1 面向对象版 tab 栏切换 添加功能
- 点击 + 可以实现添加新的选项卡和内容
- 第一步: 创建新的选项卡li 和 新的内容 section
- 第二步: 把创建的两个元素追加到对应的父元素中.
- 以前的做法:动态创建元素 createElemen,用 innerHTML赋值,用 appendChild 将内容追加到父元素里
- 现在高级做法:利用 insertAdjacentHTML() 可以直接把 字符串元素 添加到父元素中
- appendChild:不支持追加字符串的子元素;insertAdjacentHTML:支持追加字符串的元素
- insertAdjacentHTML(追加的位置,‘要追加的字符串元素’)
- 追加的位置:before / end
- 该方法地址: https://developer.mozilla.org/zh-CN/docs/Web/API/Element/insertAdjacentHTML
4.2 面向对象版 tab 栏切换 删除功能
- 点击 × 可以删除当前的 li 选项卡 和 当前的 section
- x 是没有索引号的,但是它的父亲 li 有索引号
- 动态删除新的 li 和 索引号 时,需要重新获取 x 这个元素. 需要调用 init 方法
4.3 面向对象版 tab 栏切换 编辑功能
- 双击选项卡 li 或者 section 里面的文字,实现修改功能
- 双击事件:ondblclick
- 双击文字,会默认选定文字,此时需要禁止默认行为 双击选中文字
- window.getSelection ? window.getSelection().removeAllRanges() : document.selection.empty();
- 双击文字时,生成一个文本框,当失去焦点 或者 按下回车,就把文本框输入的值给原先元素
var that; class Tab { constructor(id) { // 构造函数中都要添加 this 这里写的是 固定不变的元素 动态添加的不可以写这 that = this; this.main = document.querySelector(id); this.add = this.main.querySelector('.tabadd'); // 添加按钮 this.ul = this.main.querySelector('.fisrstnav ul:first-child'); // li的父元素 this.fsection = this.main.querySelector('.tabscon'); // section 父元素 this.init(); // 初始化 } init() { this.updateNode(); // 重新获取 动态添加的 以及 原来就有的 各种元素 // init 初始化操作让相关的元素绑定事件 this.add.onclick = this.addTab; // 点击后调用 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('.fisrstnav li span:first-child'); // 导航栏标签修改 } // 1. 切换功能 toggleTab() { 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.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(e) { e.stopPropagation(); // 阻止冒泡 防止触发li 的切换点击事件 var index = this.parentNode.index; // 删除按钮的索引号 是 父亲li 的索引号 // 根据索引号删除对应的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(); 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');
二.构造函数和原型
1.基本概念
- ES6,全称 ECMAScript 6.0,ES6之前 ,对象不基于类创建,而是用名为 构建函数 的特殊函数来定义
- 创建对象可以通过以下三种方式:
- 对象字面量
- new Object()
- 自定义构造函数
// 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();
1.1 构造函数、静态成员、实例成员
- 构造函数:用来为对象成员变量赋初始值,它总与 new 一起使用,首字母要大写
- new 在执行时会做四件事:
- 在内存中创建新的空对象
- 让 this 指向新的对象
- 执行构造函数代码,给这个新对象添加属性和方法
- 返回新对象(所以构造函数里不需要 return )
- 成员:构造函数中的 属性和方法,成员可以添加
- 静态成员:在构造函数 本身上添加的 成员称为 静态成员,只能由构造函数本身来访问
- 实例成员:在构造函数 内部用this添加的 成员称为 实例成员,只能由实例化的对象来访问
function Star(uname) { this.uname = uname; // 1.实例成员:构造函数内部通过this添加的成员 uname 是实例成员 }} var ldh = new Star('刘德华', 18); console.log(ldh.uname); // 实例成员 只能通过 实例化的对象来访问 // console.log(Star.uname); // 实例成员 不可以 通过构造函数来访问 Star.sex = '男'; // 2. 静态成员:在构造函数本身上添加的成员 sex 是静态成员 console.log(Star.sex); // 静态成员 只能通过 构造函数访问 // console.log(ldh.sex); // 静态成员 不能通过 对象访问
- 构造函数存在 内存浪费问题:
1.2 prototype 和 __proto__
- 原型对象 prototype:每个构造函数都有 prototype 对象,又名构造函数原型,用于共享方法(公共属性方法)
- 一般情况下,公共属性 定义到 构造函数里, 公共方法 定义到 原型对象身上
- 构造函数 和 原型对象prototype 里的 this 都指向 实例对象
function Star(uname) { this.uname = uname; // 公共属性 定义到 构造函数里 } Star.prototype.sing = function() { // 公共方法 放到 原型对象身上 console.log('我会唱歌'); } var ldh = new Star('刘德华'); var zxy = new Star('张学友'); console.log(ldh.sing === zxy.sing); // true 此时表明 不会给相同的方法新开辟一个空间 ldh.sing(); console.log(ldh.__proto__ === Star.prototype); // true 对象__proto__指向 原型对象 // 方法的查找规则: 首先看ldh 对象身上是否有 sing 方法,如果有就执行 // 没有sing 方法,因为__proto__ 的存在,就去构造函数原型对象prototype身上去查找sing 方法
对象原型 __proto__:每个对象都有,对象可以使用 构造函数原型 prototype 共享的方法,是通过这个实现的 constructor 属性:对象原型( __proto__)和构造函数(prototype)原型对象里面都有,称为构造函数 如果原型对象赋值了一个对象,则必须手动调用 constructor属性 指回原来的构造函数Star.prototype = { // 如果修改了原来的原型对象,给原型对象赋值了一个对象 // 则必须手动调用 constructor属性 指回原来的构造函数 constructor: Star, sing: function() { console.log('我会唱歌'); }, movie: function() { console.log('我会演电影'); }} var ldh = new Star('刘德华'); console.log(Star.prototype.constructor); // 指回原来的构造函数 console.log(ldh.__proto__.constructor); // 指回原来的构造函数
1.3 JavaScript 成员查找机制
- 原型链:
// 1.只要是对象,就有__proto__ 原型, 指向原型对象 prototype console.log(Star.prototype); console.log(Star.prototype.__proto__ === Object.prototype); // true // 2.Star原型对象的__proto__指向 Object.prototype console.log(Object.prototype.__proto__); // null // 3.Object.prototype原型对象的__proto__指向为 null
JavaScript 的成员查找机制:
- 当访问一个对象的属性(包括方法)时,首先查找 对象自身 有没有该属性
- 如果没有就查找它的原型(也就是 __proto__指向的 prototype 原型对象)
- 如果还没有就查找原型对象的原型(Object 原型对象)
- 依此类推一直找到 Object 为止(null)
- __proto__对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线
1.4 扩展内置对象
- 可以通过原型对象,对原来的内置对象进行扩展自定义的方法,比如给数组增加自定义求偶数和的功能
- 数组和字符串内置对象 不能覆盖 原型对象 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; }; // 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());
2.继承
- ES6之前并没有提供 extends 继承,可以通过 构造函数+原型对象 模拟实现继承,称为 组合继承
2.1 call()
- call():调用函数,把父类型的 this 指向子类型的 this,实现子类型继承父类型的属性
function Person(name) { // 父类 this.name = name; } function Student(name, score) { // 子类 Person.call(this, name); // 此时父类的 this 指向子类的 this,同时调用这个函数 this.score = score; } var s1 = new Student('茶茶子',100);
2.2 借用原型对象继承父类方法
- 让子类的 prototype 原型对象 = new 父类(),子类 constructor 重新指向子类的构造函数
- 本质:子类原型对象 = 实例化父类,因为父类实例化之后另外开辟空间,就不会影响原来父类原型对象
// 借用父构造函数继承属性 function Father(uname, age) { // 1. 父构造函数 this.uname = uname; // this 指向父构造函数的对象实例 this.age = age; } Father.prototype.money = function() { console.log(100000); }; function Son(uname, age, score) { // 2 .子构造函数 Father.call(this, uname, age); // this 指向子构造函数的对象实例 this.score = score; } // Son.prototype = Father.prototype; 直接赋值会有问题,修改子原型对象,父原型对象也会变化 Son.prototype = new Father(); // 利用 对象赋值的形式 修改原型对象 Son.prototype.constructor = Son; // 此时要用 constructor 指回原来对象 Son.prototype.exam = function() { // 子构造函数专门的方法 console.log('孩子要考试'); } var son = new Son('刘德华', 18, 100); console.log(son); // 刘德华实例 console.log(Father.prototype); // 父原型函数 console.log(Son.prototype.constructor); // 子构造函数
3.ES5 中的新增方法
3.1 数组方法 forEach()、filter()、some()
- 迭代(遍历)方法:array.forEach(function(每个数组元素, 每个数组元素的索引号, 数组本身))
var arr = [1, 2, 3]; var sum = 0; arr.forEach(function(value, index, array) { // forEach 遍历数组 console.log('每个数组元素' + value); console.log('每个数组元素的索引号' + index); console.log('数组本身' + array); sum += value; }) console.log(sum);
- 筛选数组(返回新数组):array.filter(function(每个数组元素, 每个数组元素的索引号, 数组本身))
var arr = [12, 66, 4, 88, 3, 7]; // filter 筛选数组 var newArr = arr.filter(function(value, index) { // 这个方法会返回新数组 return value % 2 === 0; // 返回值是偶数的数组 }); console.log(newArr);
- 查找数组(返回布尔值,只查到第一个满足条件的):array.some(function(每个数组元素, 每个数组元素的索引号, 数组本身))
var arr1 = ['red', 'pink', 'blue']; var flag1 = arr1.some(function(value) { // 返回布尔值 return value == 'pink'; }); console.log(flag1);
- filter :返回的是一个数组 把所有满足条件的元素返回
- some :返回的是一个布尔值 把查找到第一个满足条件的元素返回
- 关于 return true; 在 forEach 和 filter 里都不会种植迭代遍历,效率低,但是在 some 里可以终止迭代遍历,效率高
- 查询商品案例:
- 把数据渲染到页面中 (forEach)
- 根据价格显示数据 (filter,可能有多个)
- 根据商品名称显示数据(some,只能有一个)
// 利用新增数组方法操作数据 var data = [{ id: 1, pname: '小米', price: 3999 }, { id: 2, pname: 'oppo', price: 999 }, ....]; // 1. 获取相应的元素 var tbody = document.querySelector('tbody'); // 表格身体部分 var search_price = document.querySelector('.search-price'); // 查询价格按钮 var start = document.querySelector('.start'); // 最低价 var end = document.querySelector('.end'); // 最高价 var product = document.querySelector('.product'); // 产品名输入框 var search_pro = document.querySelector('.search-pro'); // 查询产品名按钮 setDate(data); // 2. 把数据渲染到页面中(forEach循环) function setDate(mydata) { tbody.innerHTML = ''; // 先清空原来tbody 里面的数据 mydata.forEach(function(value) { var tr = document.createElement('tr'); // 创建行 tr.innerHTML = '<td>' + value.id + '</td><td>' + value.pname + '</td><td>' + value.price + '</td>'; tbody.appendChild(tr); });} // 3. 根据价格查询商品(filter查询所有符合条件的商品) 点击按钮后触发 search_price.addEventListener('click', function() { var newDate = data.filter(function(value) { return value.price >= start.value && value.price <= end.value; }); setDate(newDate); // 把筛选完之后的对象渲染到页面中 }); // 4. 根据商品名称查找商品(some查询符合条件的唯一商品) 点击按钮后触发 search_pro.addEventListener('click', function() { var arr = []; data.some(function(value) { if (value.pname === product.value) { // 数组元素的pname属性 = 输入的商品名 arr.push(value); // 将数组对象元素追加到空数组中 return true; // 在some 里面 遇到 return true 就是终止遍历 迭代效率更高 }}); setDate(arr); // 把筛选完之后的对象渲染到页面中 })
3.2 字符串方法 str.trim()
- str.trim() :删除字符串两面的空白,返回新的字符串,不会影响字符串本身
- 判断用户输入是否为空(直接清除空格判断):
var input = document.querySelector('input'); var btn = document.querySelector('button'); var div = document.querySelector('div'); btn.onclick = function() { var str = input.value.trim(); // 将输入框的值 去除空格后 存在新变量里 if (str === '') { // 判断这个新变量是否为空 alert('请输入内容'); } else { div.innerHTML = str; // 不为空所以追加 }}
3.3 对象方法 Object.keys()、Object.defineProperty()
- Object.keys(obj) :获取对象自身所有的属性,返回一个所有元素为 字符串的 数组,效果类似 for...in
var obj = { id: 1, pname: '小米', price: 1999, num: 2000 }; var arr = Object.keys(obj); arr.forEach(function(value) { console.log(value); })
- Object.defineProperty(目标对象,需要定义/修改的属性名字,属性特性):定义新属性或修改原有的属性
- 属性特性说明:
- value: 设置属性的值
- writable: 值是否可以重写 true | false(默认)
enumerable: 目标属性是否可以被枚举(遍历)true | false(默认) configurable: 目标属性是否可以被删除 或 再次修改特性 true | false(默认)var obj = { id: 1, pname: '今夜不再', price: 9999 }; // obj.num = 1000; // 1. 以前的对象添加和修改属性的方式 // 2. Object.defineProperty() 定义新属性或修改原有的属性 Object.defineProperty(obj, 'address', { // 这是给 obj对象 添加了一个属性 value: '第五人格茶茶子的庄园', writable: true, // writable:如果值为false 则不允许修改这个属性值 默认false enumerable: true, // enumerable:如果值为false 则不允许遍历 默认false configurable: true // configurable:如果值为false 则不允许删除这属性 默认false }); console.log(obj.address);
三.函数进阶
1.定义和调用函数
1.1 定义方法
- 自定义函数(命名函数)
- 函数表达式(匿名函数)
- 利用 new Function('参数1','参数2', '函数体');
- 所有函数都是 Function 的实例(对象) 函数也属于对象
// 1. 自定义函数(命名函数) function hanshuming() {}; // 2. 函数表达式 (匿名函数) fun是变量名不是函数名 var fun = function() {}; // 3. 利用 new Function('参数1','参数2', '函数体'); var f = new Function('a', 'b', 'console.log(a + b)'); f(1, 2); // 4. 所有函数都是 Function 的实例(对象) 函数也属于对象 console.log(f instanceof Object);
1.2 调用方法
- 普通函数
- 对象内的函数
- 构造函数
- 绑定事件函数
- 定时器函数
- 立即执行函数
// 1. 普通函数方法 this 指向window function fn() { console.log('普通函数的this' + this); } window.fn(); // 可以省略 windows 直接调用方法 // 2. 对象的方法 this指向的是对象 o var o = { sayHi: function() { console.log('对象方法的this:' + this); }} o.sayHi(); // 通过对象调用 // 3. 构造函数 this 指向 ldh 实例对象 原型对象的this 指向的也是 ldh实例对象 function Star() {}; Star.prototype.sing = function() {} var ldh = new Star(); // 构造函数通过 new 调用方法 // 4. 绑定事件函数 this 指向的是函数的调用者 btn按钮对象 var btn = document.querySelector('button'); btn.onclick = function() { // 点击后就会调用函数 console.log('绑定时间函数的this:' + this); }; // 5. 定时器函数 this 指向的也是window window.setTimeout(function() { console.log('定时器的this:' + this); }, 1000); // 时间到了自动调用这个函数 // 6. 立即执行函数 this还是指向window (function() { console.log('立即执行函数的this' + this); // 直接自动调用函数 })();
2.改变函数内部 this 指向
- 这些 this 的指向,调用函数的时候确定,调用方式的不同决定了 this 指向不同,一般指向调用者
改变函数内部指向 this:bind()、call()、apply() 区别点:
- call 和 apply 会调用函数,并且改变函数内部this指向
- call 和 apply 传递的参数不一样,call 传递参数 aru1, aru2..形式 apply 必须数组形式[arg]
- bind 不会调用函数,可以改变函数内部 this指向
- 主要应用场景:
- call 经常做继承
- apply 经常跟数组有关系,比如借助于数学对象实现数组最大值最小值
- bind 不调用函数,但是还想改变this指向,比如改变定时器内部的this指向
2.1 call():继承相关
// call 可以调用函数,可以改变函数内的this 指向,主要作用可以实现继承 function Father(uname) { this.uname = uname; } function Son(uname) { Father.call(this, uname); } var son = new Son('刘德华'); console.log(son);
2.2 apply():数组相关
// apply 参数必须是数组(伪数组),主要应用 利用apply 借助于数学内置对象求数组最大值 var arr = [1, 66, 3, 99, 4]; var max = Math.max.apply(Math, arr); var min = Math.min.apply(Math, arr); console.log(max, min);
2.3 bind():绑定相关
// 1. 不会调用原来的函数 可以改变原来函数内部的this 指向 // 2. 如果有的函数不需要立即调用,但是又想改变函数内部的this指向 此时用 bind 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; // 定时器原本的this指向windows,windows没有禁用属性 }.bind(this), 2000); // 修改绑定后this指的是定时器外面的 btns }}
3.严格模式
- 严格模式在 IE10 以上版本的浏览器中才会被支持,旧版本浏览器中会被忽略
- 严格模式分为 为脚本开启严格模式(整个脚本) 和 为函数开启严格模式(个别函数) 两种情况
- "use strict";
<!-- 为整个脚本(script标签)开启严格模式 --> <script> 'use strict'; </script> <script> (function() { 'use strict'; })(); </script> <!-- 为某个函数开启严格模式 --> <script> function fn() { // 此时只是给fn函数开启严格模式 'use strict'; // 下面的代码按照严格模式执行 } function fun() { // 里面的还是按照普通模式执行 } </script>
- 严格模式中的一些变化:
- 变量规定:禁止没声明就赋值,禁止删除已经声明的变量
- 函数规定:不能有重名参数,函数必须声明在顶层
- this指向:以前全局作用域函数中的 this 指向 Windows,严格模式全局作用域函数中的 this 是 undefined
- 构造函数this:以前构造函数不写 new,会指向全局对象,严格模式中不加new,会 undefined,若赋值就报错
- 和以前没区别的:new 实例化的构造函数指向对象实例,定时器this 指向Windows,事件对象this 指向调用者
4.高阶函数
- 高阶函数:接收函数作为参数(回调函数) 或 将函数作为返回值输出
// 接收函数作为参数(回调函数) function fn(callback){ callback&&callback(); // 如果有回调函数就直接调用 } fn(function(){alert('hi')} // 接收函数作为返回值 function fn(){ return function() {} } fn();
5.闭包
- 变量作用域:函数内可以使用全局变量,函数外不可以使用局部变量,函数执行完毕后本作用域内的局部变量会销毁
闭包(closure):有权访问 另一个函数作用域中的 变量的函数,用于延伸变量的作用范围 在 chrome 中调试闭包:
- 打开浏览器,按 F12 键启动 chrome 调试工具
- 设置断点,找到 Scope(作用域) 选项
- 重新刷新页面,会进入断点调试,Scope 里面会有两个参数(global 全局作用域、local 局部作用域)
- 执行到 fn2() 时,Scope 里面会多一个 Closure 参数 ,这就表明产生了闭包
- 怎么能在 fn() 函数外面访问 fn() 中的局部变量 num 呢 ?
function fn() { var num = 10; return function { console.log(num); // 10 }} var f = fn(); f()
- 循环注册点击事件
<ul class="nav"> <li>小可爱</li> ... </ul> // 闭包应用-点击li 输出 当前li 索引号 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);}
- 循环中的 setTimeout()
<ul class="nav"> <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);}
- 计算打车价格
// 闭包应用-计算打车价格 // 打车起步价13(3公里内),之后每多一公里增加 5块钱.用户输入公里数就可以计算打车价格 // 如果有拥堵情况,总价格多收取10块钱拥堵费 var car = (function() { var start = 13; // 起步价 局部变量 var total = 0; // 总价 局部变量 return { 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
6.递归
- 由于递归很容易发生“栈溢出”错误(stack overflow),所以 必须要加退出条件 return
- 求 1 * 2 *3 ... * n 阶乘
function fn(n) { if (n == 1) { return 1; } return n * fn(n - 1); }
求斐波那契数列 (兔子序列)// 利用递归函数求斐波那契数列(兔子序列) 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); }
- 根据id 返回对应的数据对象
var data = [{ id: 1, name: '家电', goods: [{ id: 11, gname: '冰箱', goods: [{ id: 111, gname: '海尔' }, { id: 112, gname: '美的' }, ] }, { id: 12, gname: '洗衣机' }] }, { id: 2, name: '服饰' }]; // 1. 利用 forEach 遍历每一个对象 function getID(json, id) { // json数据集 id是用户输入的id var o = {}; json.forEach(function(item) { // item表示数组元素 if (item.id == id) { // 如果数组元素的id = 用户输入的id o = item; // 把满足条件的对象存到 对象o 中 // 2. 想要得里层的数据 11 12 可以利用递归函数 // 里面有goods这个数组 并且 数组的长度不为 0 } else if (item.goods && item.goods.length > 0) { o = getID(item.goods, id); } }); return o;} console.log(getID(data, 112));
7.深拷贝、浅拷贝
- 浅拷贝(Object.assign()):只是拷贝一层,更深层次对象级别的只拷贝引用(对象地址)
- 深拷贝(自己封装函数):拷贝多层,每一级别的数据都会拷贝.
var obj = { id: 1, name: 'andy', msg: { age: 18 }, color: ['pink', 'red'] }; var o = {}; function deepCopy(newobj, oldobj) { // 深拷贝封装函数 for (var k in oldobj) { var item = oldobj[k]; // 1. 获取属性值 oldobj[k] if (item instanceof Array) { // 2. 判断这个值是否是数组 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('--------------'); Object.assign(o, obj); // 浅拷贝
四.正则表达式
1.创建使用正则表达式
- 通过调用 RegExp 对象的构造函数创建 :var 变量名 = new RegExp(/表达式/);
- 通过字面量创建 :var 变量名 = /表达式/;
- 写好的正则表达式.test(测试文本)
- 正则测试工具: http://tool.oschina.net/regex
2.特殊字符
- 括号总结及在线测试:https://c.runoob.com/
- 大括号:量词符. 里面表示重复次数
- 中括号:字符集合 匹配方括号中的任意字符.
- 小括号:表示优先级
var rg = /abc/; // 包含 abc 整体字符串 console.log(rg.test('abcd')); // true var reg = /^abc/; // 以 abc 整体开头 console.log(reg.test('abcd')); // true console.log(reg.test('aabcd')); // false var reg1 = /^abc$/; // 精确匹配 必须是 abc 字符串 console.log(reg1.test('abcd')); // false var reg2 = /[abc]/; // 包含有a 或者 包含有b 或者包含有c var reg3 = /^[a-z]$/; // 只有是a-z 任一字母 var reg4 = /^[^a-zA-Z0-9_-]$/; // 中括号里面有^ 表示取反 任意一个值都不是 var reg5 = /^[a-zA-Z0-9_-]{6,16}$/; // 标准用户名 var reg6 = /^abc{3}$/; // 让c重复三次 abccc var reg7 = /^(abc){3}$/; // 让abc整体重复三次 abcabcabc var reg = /^\d{3,4}-\d{7,8}$/; // 座机号 0631-5129136 010-12345678
- 常见表单验证:
- 手机号码: /^1[3|4|5|7|8][0-9]{9}$/
- QQ: [1-9][0-9]{4,} (腾讯QQ号从10000开始)
- 昵称是中文: ^[\u4e00-\u9fa5]{2,8}$
window.onload = function() { var regtel = /^1[3|4|5|7|8]\d{9}$/; // 手机号码的正则表达式 var regqq = /^[1-9]\d{4,}$/; // qq号正则表达式 var regnc = /^[\u4e00-\u9fa5]{2,8}$/; // 昵称正则表达式(中文) var regmsg = /^\d{6}$/; // 短信验证码正则表达式 var regpwd = /^[a-zA-Z0-9_-]{6,16}$/; // 密码正则表达式 var tel = document.querySelector('#tel'); var qq = document.querySelector('#qq'); var nc = document.querySelector('#nc'); var msg = document.querySelector('#msg'); var pwd = document.querySelector('#pwd'); regexp(tel, regtel); // 手机号码 regexp(qq, regqq); // qq号码 regexp(nc, regnc); // 昵称 regexp(msg, regmsg); // 短信验证 regexp(pwd, regpwd); // 密码框 // 表单验证的函数 function regexp(ele, reg) { ele.onblur = function() { // 元素失去焦点后 开始和正则表达式作比较 if (reg.test(this.value)) { this.nextElementSibling.className = 'success'; this.nextElementSibling.innerHTML = '<i class="success_icon"></i> 恭喜您输入正确'; } else { this.nextElementSibling.className = 'error'; this.nextElementSibling.innerHTML = '<i class="error_icon"></i> 格式不正确,请从新输入 '; }}};}
4.正则表达式中的替换
- stringObject.replace(被替换的字符串,替换为的字符串)
- /表达式/[switch]:switch(也称为修饰符) 按照什么样的模式来匹配. 有三种值:
- g:全局匹配
- i:忽略大小写
- gi:全局匹配 + 忽略大小写
- 敏感词过滤:
var text = document.querySelector('textarea'); var btn = document.querySelector('button'); var div = document.querySelector('div'); btn.onclick = function() { div.innerHTML = text.value.replace(/激情|gay/g, '**'); }
五.ES6
1.ES6 简介
- ES 的全称是 ECMAScript , 它是由 ECMA 国际标准化组织,制定的 一项脚本语言的标准化规范
- ES6 实际上是一个泛指,泛指 ES2015 及后续的版本
2.ES6 的新增语法
2.1 Let(声明变量)
- let 声明的变量只在所处于的块级有效,不存在变量提升,会有暂时性死区
if (true) { // let 声明的变量只在所处于的块级有效 let a = 10; } console.log(a) // a is not defined console.log(b); // a is not defined let b = 20; // 不存在变量提升 var tmp = 123; if (true) { tmp = 'abc'; let tmp; // 暂时性死区 }
- 使用 let 声明的变量才具有块级作用域,使用 var 声明的变量不具备块级作用域
2.2 const(声明常量)
- const 具有块级作用域,用于声明常量,常量就是值(内存地址)不能变化的量
if (true) { const a = 10; } console.log(a) // a is not defined 具有块级作用域 const PI; // Missing initializer in const declaration 常量必须赋初值,不赋值会报错 const PI = 3.14; PI = 100; // Assignment to constant variable. 常量赋值后不可以被修改 const ary = [100, 200]; ary[0] = 'a'; // 数组可以用这种方式修改值 ary[1] = 'b'; console.log(ary); // ['a', 'b']; ary = ['a', 'b']; // Assignment to constant variable. 数组不可以用这种方式修改值
- 使用 var 声明的变量,其作用域为该语句所在的函数内,且存在变量提升现象
- 使用 let 声明的变量,其作用域为该语句所在的代码块内,不存在变量提升
- 使用 const 声明的是常量,在后面出现的代码中不能再修改该常量的值
2.3 解构赋值
// 数组解构:允许按照 一一对应的关系 从数组中提取值 然后将值赋值给变量 let ary = [1,2,3]; let [a, b, c, d, e] = ary; // 对象解构:允许 使用变量的名字 匹配 对象的属性 匹配成功 将对象属性的值赋值给变量 let person = { name: 'zhangsan', age: 20 }; let { name, age } = person; console.log(name); // 'zhangsan' let {name: myName, age: myAge} = person; // myName myAge 属于别名 console.log(myName); // 'zhangsan'
2.4 箭头函数
- 箭头函数中 如果函数体中只有一句代码 且执行结果是函数的返回值 函数体大括号可以省略
- 箭头函数中 如果形参只有一个 形参外侧的小括号也是可以省略的
- 箭头函数没有自己的this关键字 若有 则将指向 箭头函数定义位置中的this(经典面试)
// 在箭头函数中 如果函数体中只有一句代码 且执行结果是函数的返回值 函数体大括号可以省略 // const sum = (n1, n2) => n1 + n2; // 在箭头函数中 如果形参只有一个 形参外侧的小括号也是可以省略的 // const fn = v => { alert(v); }
2.5 剩余参数
let students = ['wangwu', 'zhangsan', 'lisi']; let [s1, ...s2] = students; console.log(s1); // 'wangwu' console.log(s2); // ['zhangsan', 'lisi']
3.ES6 的内置对象扩展
3.1 Array 的扩展方法
- 扩展运算符...:将 数组或者对象 转为 用逗号分隔的参数序列
- 合并数组
- 将类数组或可遍历对象转换为真正的数组
let ary = [1, 2, 3]; console.log(...ary); // 1 2 3 将数组转换为字符串 // 合并数组 方法一 let ary1 = [1, 2, 3]; let ary2 = [3, 4, 5]; let ary3 = [...ary1, ...ary2]; // [1,2,3,4,5,6] // 合并数组 方法二 ary1.push(...ary2); // 将类数组或可遍历对象转换为真正的数组 let oDivs = document.getElementsByTagName('div'); oDivs = [...oDivs];
- 构造函数方法 Array.from():可以接受两个参数,类似于数组的map方法,对每个元素进行处理并返回的数组
let arrayLike = { "0": 1, "1": 2, "length": 2 } let newAry = Array.from(aryLike, item => item *2) // [2,4]
- find() :找出第一个符合条件的数组成员,没有就返回 undefined
let ary = [{ id: 1, name: '张三‘ }, { id: 2, name: '李四‘ }]; let target = ary.find((item, index) => item.id == 2);
- findIndex() :找出第一个符合条件的数组成员的位置,如果没有找到返回-1
let ary = [1, 5, 10, 15]; let index = ary.findIndex((value, index) => value > 9); console.log(index); // 2 这是索引号
- includes():表示某个数组是否包含给定的值,返回布尔值
[1, 2, 3].includes(2) // true [1, 2, 3].includes(4) // false
3.2 String 的扩展方法
- 模板字符串${}:可以解析变量,可以换行,可以调用函数
let name = '张三'; let sayHello = `hello,my name is ${name}`; // hello, my name is zhangsan 解析变量 let result = { name: 'zhangsan', age: 20, sex: '男' } let html = ` <div> <span>${result.name}</span> // 实现换行 <span>${result.age}</span> <span>${result.sex}</span> </div> `; const sayHello = function () { return '哈哈哈哈 追不到我吧 我就是这么强大'; }; let greet = `${sayHello()} 23333'; // 调用函数 console.log(greet); // 哈哈哈哈 追不到我吧 我就是这么强大 23333
- startsWith():表示参数字符串是否在原字符串的头部,返回布尔值
- endsWith():表示参数字符串是否在原字符串的尾部,返回布尔值
let str = 'Hello world!'; str.startsWith('Hello') // true str.endsWith('!') // true
- repeat() :表示将原字符串重复n次,返回一个新字符串
'x'.repeat(3) // "xxx" 'hello'.repeat(2) // "hellohello"
3.3 Set 数据结构
- 数据结构 Set:它类似于数组,但成员没有重复值
const s = new Set(); // Set本身是一个构造函数,用来生成 Set 数据结构 const set = new Set([7, 8, 4]); // Set函数可以接受一个数组作为参数,用来初始化 s.add(1).add(2).add(3); // 向 set 结构中添加值 s.delete(2) // 删除 set 结构中的2值 s.has(1) // 表示 set 结构中是否有1这个值 返回布尔值 s.clear() // 清除 set 结构中的所有值 s.forEach(value => console.log(value)) // 遍历 没有返回值