JavaScript 网页编程(五)——JavaScript 高级
一.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 { // 子类继承父类 extendsconstructor(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;};// 按下回车也可以把文本框里面的值给spaninput.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__ 原型, 指向原型对象 prototypeconsole.log(Star.prototype);console.log(Star.prototype.__proto__ === Object.prototype); // true// 2.Star原型对象的__proto__指向 Object.prototypeconsole.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 指向windowfunction fn() {console.log('普通函数的this' + this); }window.fn(); // 可以省略 windows 直接调用方法// 2. 对象的方法 this指向的是对象 ovar 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 指向的也是windowwindow.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指向 此时用 bindvar 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)); // 23console.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是用户输入的idvar 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')); // truevar reg = /^abc/; // 以 abc 整体开头console.log(reg.test('abcd')); // trueconsole.log(reg.test('aabcd')); // falsevar reg1 = /^abc$/; // 精确匹配 必须是 abc 字符串console.log(reg1.test('abcd')); // falsevar 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重复三次 abcccvar reg7 = /^(abc){3}$/; // 让abc整体重复三次 abcabcabcvar 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 definedconsole.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)) // 遍历 没有返回值
JavaScript 网页编程(五)——JavaScript 高级相关推荐
- JavaScript网页制作--五秒后自动跳转页面
JavaScript网页制作–五秒后自动跳转页面 通常在浏览一些网站时,会出现网页不存在的情况,网页不存在之后,有些网站会在5秒后自动跳转到一个新的网页,比如网站的首页.可以利用定时器和locatio ...
- JavaScript 网页编程(一)——JavaScript 基础语法
目录 一.计算机编程基础 二.初识 JavaScript 三.JavaScript 变量 四.JavaScript 数据类型 五.标识符.关键字.保留字 六.JavaScript 运算符(操作符) 七 ...
- 阶段三 JavaScript网页编程---js基础语法
系列文章目录 一:计算机基础和JavaScript介绍 二:JavaScript变量 三:JavaScript数据类型 四:JavaScript操作符 五:JavaScript流程控制及案例 六:Ja ...
- JavaScript网页编程_API和WebAPI
Web API 和 API 的区别 API的概念 API(Application Programming Interface,应用程序编程接口)是一些预先定义的函数,目的是提供应用程序与开发人员基于某 ...
- JavaScript 网页编程(三)——Web API(BOM)
一.BOM 简介 BOM(Browser Object Model)浏览器对象模型:提供与浏览器窗口进行交互的对象 JavaScript 语法标准化是 ECMA,DOM 标准化是 W3C BOM 是 ...
- JavaScript 网页编程(六)—— ES6重点概念 + 开发基本规范
目录 1.前端开发步骤 2.SVN 使用规范 3.文件命名规范 4.HTML 规范 5.CSS 规范 6.jQuery 规范 7.isNaN.isFinite 8.ES6 规范 9.ES6 习题 9. ...
- JavaScript DOM 编程艺术 --- JavaScript语法
二. JavaScript语法目录 2.1 语法 javaScript代码要通过HTML/XHTML文档才能执行.可以有两种方式完成这一点,第一种是将JavaScript代码放到文档<head ...
- JavaScript学习总结(五)——Javascript中==和===的区别
一.JavaScript"=="的作用 当==两边的内容是字符串时,则比较字符串的内容是否相等. 当==两边的内容是数字时,则比较数字的大小是否相等. 当==两边的内容是对象或者是 ...
- javascript高级编程教程,javascript基础入门案例
谁有比较好的javascript视频教程 李炎恢的javascript教程,在verycd上可以下载. 结合<javascript高级程序设计>学习,应该会比较好,他这个教程就是参考了&l ...
最新文章
- oracle rman imp exp,Oracle-client支持exp|imp|rman
- 消费者版 Vive Trackers 正式发布,只会与 Steam 1.0 基站适配
- Qt控制台工程不能调试问题
- Dubbo架构的特点
- MPLS TE基本配置-OSPF
- python 字典查询比列表快_Python字典vs列表,哪个更快?
- 2016/06/22 中色启动筹码分析作业
- qunit 前端脚本测试用例
- 哈利波特与魔杖的故事(洛谷P4613题题解,Java语言描述)
- 5.过滤器作为模板——1D 相关、Matlab互相关实战_1
- C++中一个类禁止继承好麻烦
- JavaFx实现(2)-随机图形绘制
- chrome-推荐13个插件
- 帝国cms 自动生成html,帝国CMS静态生成为一行代码教程
- ESP定律脱压缩壳aspack
- 忘记密码(找回密码)代码实现
- Arcgis实验一 空间数据数字化、投影变换与仿射变换
- VDI 虚拟桌面基础架构(VDI,Virtual Desktop Infrastructure)
- Python机器学习及实践——基础篇9(SVM回归)
- 语音合成vocoder(一) 概况