面向对象编程介绍
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');
console.log(this.ul);
// section 父元素
this.fsection = this.main.querySelector('.tabscon');
this.init();
}
init() {
this.updateNode();
// init 初始化操作让相关的元素绑定事件
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('.fisrstnav li span:first-child');
}
// 1. 切换功能
toggleTab() {
// 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.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;
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');
类和对象
// 1:对象
// 现实生活中:万物皆对象,对象是一个具体的事物,看得见摸得着的实物。例如,一本书、一辆汽车、一个人
// 可以是“对象”,一个数据库、一张网页、一个与远程服务器的连接也可以是“对象”。 在 JavaScript 中,对象是一组无序的相关属性和方法的集合,所有的事物都是对象,例如字符串、数值、数组、
// 函数等。
// 对象是由属性和方法组成的:
// 属性:事物的特征,在对象中用属性来表示(常用名词)
// 方法:事物的行为,在对象中用方法来表示(常用动词)
// 类 class
// 在 ES6 中新增加了类的概念,可以使用 class 关键字声明一个类,之后以这个类来实例化对象。
// 类抽象了对象的公共部分,它泛指某一大类(class)
// 对象特指某一个,通过类实例化一个具体的对象
// 面向对象的思维特点:
// 1. 抽取(抽象)对象共用的属性和行为组织(封装)成一个类(模板)
// 2. 对类进行实例化, 获取类的对象
// 创建类和生成实例:
class Star {
constructor(uname, age) {
this.uname = uname;
this.age = age;
}
}
var mayun = new Star('马云', 66); //Object { uname: "马云", age: 66 }
var yyx = new Star('尤雨溪', 34); //Object { uname: "尤雨溪", age: 34 }
console.log(mayun);
console.log(yyx);
// 类中添加共有方法:
class Boss {
constructor(name, money) {
this.name = name;
this.money = money;
}
// 共有方法
sing(song) {
console.log(song);
}
}
var mayun = new Boss('马云', '1500亿身家');
console.log(mayun); // Object { name: "马云", money: "1500亿身家" }
mayun.sing('假如云知道'); // 假如云知道
// 类继承extends和super关键字:
class Boss {
constructor(name, money) {
this.name = name;
this.money = money;
}
sing(song) {
console.log(song);
}
}
class Fuerdai extends Boss {}
var wsc = new Fuerdai('王思聪', '好多money');
console.log(wsc);
wsc.sing('KTV是我家'); // KTV是我家
// super 关键字用于访问和调用对象父类上的函数。可以调用父类的构造函数,也可以调用父类的普通函数
class Father {
constructor(name, money) {
this.name = name;
this.money = money;
}
work(msg) {
console.log(msg);
}
}
class Son extends Father {
constructor(name, money) {
super(name, money);
}
}
var xiaoming = new Son('小明', '兜里9块9');
xiaoming.work('放牛');
// 类里面this指向问题:指向调用者
class Work {
constructor(name, age) {
this.name = name;
this.age = age;
}
happy() {
console.log(this); //Object { name: "打工人", age: "16未成年" }
}
}
var wxb = new Work('打工人', '16未成年');
console.log(wxb); //Object { name: "打工人", age: "16未成年" }
wxb.happy();
构造函数原型 prototype
// 原型constructor构造函数:
// constructor 构造函数
// 对象原型( __proto__)和构造函数(prototype)原型对象里面都有一个属性 constructor 属性 ,constructor 我们称
// 为构造函数,因为它指回构造函数本身。
// constructor 主要用于记录该对象引用于哪个构造函数,它可以让原型对象重新指向原来的构造函数。
// 一般情况下,对象的方法都在构造函数的原型对象中设置。如果有多个对象的方法,我们可以给原型对象采取对象形式赋
// 值,但是这样就会覆盖构造函数原型对象原来的内容,这样修改后的原型对象 constructor 就不再指向当前构造函数了。
// 此时,我们可以在修改后的原型对象中,添加一个 constructor 指向原来的构造函数。
function Boss(name) {
this.name = name;
}
Boss.prototype = {
constructor: Boss,
work: function (money) {
console.log(money);
},
};
var mayun = new Boss('马云');
mayun.work('1500亿身家'); //1500亿身家
// console.log(Boss.prototype === mayun.__proto__); // true
// console.log(Boss.prototype.constructor === mayun.__proto__.constructor); // true
console.log(Boss);
console.log(mayun);
forEach、some、filter方法
// 1:数组方法
// 迭代(遍历)方法:forEach()
array.forEach(function(currentValue, index, arr))
var arr = [1, 2, 3, 4];
var sum = 0;
arr.forEach(function (val, index, arr) {
// console.log(val); // 1 2 3 4
// console.log(index); // 0 1 2 3
// console.log(arr); // Array(4) [ 1, 2, 3, 4 ]
sum += val; //10
});
console.log(sum);
// array.filter(function(currentValue, index, arr))
// filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素,
// 主要用于筛选数组,注意它直接返回一个新数组
// 用filter返回数组中能被2整除的每一项
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
var newArr = arr.filter(function (val, index) {
return val % 2 === 0;
});
console.log(newArr); //Array(5) [ 2, 4, 6, 8, 10 ]
// some()
// array.some(function(currentValue, index, arr))
// some() 方法用于检测数组中的元素是否满足指定条件. 通俗点 查找数组中是否有满足条件的元素
// 注意它返回值是布尔值, 如果查找到这个元素, 就返回true , 如果查找不到就返回false.
// 如果找到第一个满足条件的元素,则终止循环. 不在继续查找.
// 使用some查找数组中,是否有可以被2整除的数据,有返回布尔值true
var arr = [1, 2, 3, 4, 5];
var flag = arr.some(function (val, i) {
// console.log(val); // 1 2 3 4 5
// console.log(i); // 0 1 2 3 4
return val % 2 === 0;
});
console.log(flag); // true
// forEach和filter性能开销大,some性能开销小些
// 在forEach中,即使return true,它也会将数组中的每一项循环完,性能开销大。
// filter,filter即使return,程序也不会打断,也会继续向后执行完,和forEach差不多。
// some,return 在此处发挥了作用,到了这里就return退出程序了,后续代码就不在执行了。
call、apply、bind的使用
// call、apply、bind的使用
// call方法及其应用
// call() 方法调用一个对象。简单理解为调用函数的方式,但是它可以改变函数的 this 指向。
// ------------------------- call ------------------------
// 普通函数
var obj = {
name: 'zs',
};
function fn1(a, b) {
console.log(this);
console.log(a + b);
}
fn1.call(obj, 1, 5); // 函数使用call将this指向了obj对象,
// 构造函数
function Boss(name) {
this.name = name;
}
function Fuerdai(name) {
Boss.call(this, name); //将this指向了父构造函数
console.log(this); //Object { name: "王思聪" }
}
var wsc = new Fuerdai('王思聪');
// bind方法基本使用
// 1. 不会调用原来的函数 可以改变原来函数内部的this 指向
// 2. 返回的是原函数改变this之后产生的新函数
// 3. 如果有的函数我们不需要立即调用,但是又想改变这个函数内部的this指向此时用bind
// bind的感觉和call的很像
// ------------------------- bind ------------------------
var obj = {
name: 'zs',
};
function fn2(a, b) {
console.log(this);
console.log(a + b);
}
var fn = fn2.bind(obj, 2, 3); // callhi直接调用函数,apply需要用新变量接收
fn();
// 点击让按钮禁用,2秒以后让按钮恢复回来,通过bind将this指回了btn当前点击的按钮
var btn = document.querySelector('.btn');
btn.onclick = function () {
this.disabled = true;
setTimeout(
function () {
this.disabled = false;
}.bind(this), //定时器的this指向的是window,使用bind将this指回了按钮
2000
);
};
// ------------------------- apply ------------------------
// apply方法及其应用
// 感觉没有call好用,虽然指向了obj
var obj = {
name: 'zs',
};
function fn2(arr) {
console.log(this); //Object { name: "zs" }
console.log(arr); //red
}
fn2.apply(obj, ['red']); // 函数使用apply将this指向了obj对象,
// 借助Math.max求数组中最大和最小值
var arr = [1, 3, 7, 9, 23, 8, 5];
var max = Math.max.apply(Math, arr);
var min = Math.min.apply(Math, arr);
console.log(max); //23
console.log(min); //1
// 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指向.
闭包-递归及应用
// 闭包(closure)指有权访问另一个函数作用域中变量的函数。
// 一个作用域可以访问另外一个函数的局部变量
// 我们fn 外面的作用域可以访问fn 内部的局部变量
// 闭包的主要作用: 延伸了变量的作用范围
// 闭包应用-点击li打印当前索引号
var lis = document.querySelectorAll('li');
for (var i = 0; i < lis.length; i++) {
(function (i) {
lis[i].onclick = function () {
console.log(i); // 0 1 2
};
})(i); // 使用自调用函数,将外层循环的i,通过形参的方式传递到内部的点击事件中,此时产生了闭包
}
// 闭包应用-3秒钟之后,打印所有li元素的内容
var lis = document.querySelectorAll('li');
for (var i = 0; i < lis.length; i++) {
(function (i) {
setTimeout(function () {
console.log(lis[i].innerHTML); // 第1个li 第2个li 第3个li
}, 3000);
})(i); // 循环之中,同上一致,使用自调用函数,将i当做形参传给内部的函数使用,产生了闭包
}
// 闭包应用-计算打车价格
// 用对象的方式计算打车价格,道路顺畅就是price按照正常计算,拥堵就在原有的total上加上10元
var car = (function () {
// 使用自调用函数,在return中将2个价格计算并返回
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
// 什么是递归函数
// 如果一个函数在内部可以调用其本身,那么这个函数就是递归函数。
// 简单理解:函数内部自己调用自己, 这个函数就是递归函数
// 递归函数的作用和循环效果一样
// 由于递归很容易发生“栈溢出”错误(stack overflow),所以必须要加退出条件 return。
var num = 1;
function fn1() {
console.log('我要打印6次'); // 我要打印6次 * 6
if (num === 6) {
return; // 满足条件后,一定要避免死循环,要加上return退出。
}
num++;
fn1(); // 1 函数自己调用自己
}
fn1();
浅拷贝 - 深拷贝
// 1:Object.assign(target, ...soureces)
// ------------------- Object.assign -------------------
// 参数:target:目标对象。 sources:任意多个源对象。 返回值:目标对象会被返回。
// Object.assign是ES6的新函数。Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,
// 然后返回目标对象。但是 Object.assign() 进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身。
var obj1 = {
test1: {
test2: 'hello',
},
};
var obj2 = Object.assign({}, obj1);
obj2.test1.test2 = 'nihao'; //修改b的属性,会影响a的属性
console.log(obj2); //test1: Object { test2: "nihao" }
console.log(obj1); //test1: Object { test2: "nihao" }
// 2:jquery 有提供一个$.extend可以用来做 Deep Copy。
// ------------------- $.extend -------------------
var obj1 = {
test1: {
test2: 'hello',
},
};
var obj2 = $.extend(true, {}, obj1);
obj2.test1.test2 = 'nihao'; // //修改b的属性,不会影响a的属性
console.log(obj2); //test1: Object { test2: "nihao" }
console.log(obj1); //test1: Object { test2: "hello" }
// 还有一些其它的第三方函数库有深拷贝function,如lodash。
// ------------------- JS数据类型 -------------------
// 基本数据类型主要是:undefined,boolean,number,string,null。
// 基本类型就是值类型, 存放在栈(stack)内存中的简单数据段,数据大小确定,内存空间大小可以分配
// 引用类型 Object Array
// 引用类型, 存放在堆(heap)内存中的对象,变量实际保存的是一个指针,这个指针指向另一个位置
// ------------------- 浅拷贝 -------------------
// 概念: 对于字符串类型,浅拷贝是对值的复制,对于对象来说,浅拷贝是对对象地址的复制, 也就是拷贝的结果是两个对象指向同一个地址
// ------------------- 深拷贝 -------------------
// 概念: 深拷贝开辟一个新的栈,两个对象对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性
// 深拷贝是对对象以及对象的所有子对象进行拷贝。
// ------------------- 总结: -------------------
// 其实深拷贝和浅拷贝的主要区别就是其在内存中的存储类型不同。
// 浅拷贝是复制,两个对象指向同一个地址
// 深拷贝是栈中新开地址,两个对象指向不同的地址
解构赋值
// ------------------- 数组解构赋值 -------------------
// 数组解构允许我们按照一一对应的关系从数组中提取值 然后将值赋值给变量
// 如果是数组解构,声明变量时,也需要使用数组的形式声明来接收,多出来的变量,值是undefiend
let arr = [1, 2, 3];
let [a, b, c] = arr;
console.log(a); // 1
console.log(b); // 2
console.log(c); // 3
console.log(d); // undefined
// ------------------- 对象解构 -------------------
// 对象解构允许我们使用变量的名字匹配对象的属性 匹配成功 将对象属性的值赋值给变量
//因为解构的是对象类型的,所以声明变量的时候,也是用对象方式声明的
let obj = { name: 'wsc', age: 33 };
let { name, age } = obj;
console.log(name); // wsc
console.log(age); // 33
// 解构后重命名, 和{res:data}是一样的
// ------------------- 解构后重命名 -------------------
let { name: uname } = obj;
console.log(uname); // wsc
箭头函数 =>
// 箭头函数是用来简化函数定义语法的
// ()括号就代替function, =>就代表这是箭头函数
// 1:传统函数
const fn = function () {
console.log('fn1');
};
fn();
// 2:箭头函数
const fn = () => {
console.log('fn2');
};
fn;
// 在箭头函数中 如果函数体中只有一句代码 并且代码的执行结果就是函数的返回值 函数体{}大括号可以省略
const sum = (a, b) => a + b;
console.log(sum(1, 3)); //4
// 箭头函数不绑定this 箭头函数没有自己的this关键字 如果在箭头函数中使用this
// this关键字将指向箭头函数定义位置中的this
const fn = () => {
console.log(this); // window
};
fn();
// fn.call(obj), 函数使用call将this指向了obj对象
const obj = { name: 'zs' };
function fn() {
console.log(this); //Object { name: "zs" }
return () => {
console.log(this); //Object { name: "zs" }
};
}
const objFn = fn.call(obj);
objFn();
剩余参数和拓展运算符 (展开运算符)
// 剩余参数和拓展运算符 (展开运算符)
// --------------------------- 剩余参数 --------------------------
// 求任意个数组的和
const sum = (...arr) => {
let total = 0;
arr.forEach((item) => {
total += item;
});
return total;
};
console.log(sum(1, 2, 3));
// --------------------------- 接收数组剩下的参数 --------------------------
let arr = ['apple', 'orange', 'banana'];
let [a, ...b] = arr;
console.log(a); //apple
console.log(b); //Array [ "orange", "banana" ]
// --------------------------- 数组合并的方式一 --------------------------
let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];
let arr3 = [...arr1, ...arr2];
console.log(arr3); // Array(6) [ 1, 2, 3, 4, 5, 6 ]
// --------------------------- 数组合并的方式二 --------------------------
let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];
let arr3 = arr1.concat(...arr2);
console.log(arr3); //Array(6) [ 1, 2, 3, 4, 5, 6 ]
Array实例方法
// ------------------------- Array.from方法 -------------------------
// 使用Array.from将对象转换为数组
var obj = {
// 注意对象中的属性的格式
0: 'zs',
1: 18,
length: 2,
};
let arr = Array.from(obj);
console.log(arr); //Array [ "zs", 18 ]
// ------------------------- Array实例方法:find -------------------------
// find,查找出id为2的对象
var arr = [
{ id: 1, name: 'zs' },
{ id: 2, name: 'ww' },
];
let obj = arr.find((item) => item.id === 2);
console.log(obj); //Object { id: 2, name: "ww" }
// Array实例方法:findIndex
// 数组.findIndex查找数组中的项,返回的是该项的索引
let arr = [1, 3, 5, 7, 9];
let res = arr.findIndex((item) => item > 3);
console.log(res); // 2
// ------------------------- Array实例方法:includes -------------------------
// 数组.includes,查询数组是否包含该项,返回的是布尔值
let arr = ['a', 'b', 'c'];
let res = arr.includes('c');
console.log(res); // true
ES6模板字符串和 set数据结构
// `模板语法` 可以直接将变量名渲染`${变量名}`,也可以将对象里面的属性名给渲染`${对象.属性名}`,
// 还可以直接调用函数`{fn()}`
// 可以直接将变量名渲染`${变量名}
// --------------------------------- ${变量名} --------------------------------
let name = 'zs';
let myName = `hello my name is ${name}`;
console.log(myName); // hello my name is zs
// // ----------------------------- ${fn()} -----------------------------
const fn = (a, b) => a + b;
let res = `函数的结果是 ${fn(1, 3)}`;
console.log(res); //函数的结果是 4
// // 也可以将对象里面的属性名给渲染`${对象.属性名}
// // ---------------------------- ${对象.属性名} -----------------------------
let obj = {
name: 'zs',
age: 16,
};
let objName = `she name is ${obj.name}`;
console.log(objName); //she name is zs
// ---------------------------- add()往数组中添加项 ----------------------------
const set = new Set();
set.add('a').add('b');
console.log(set);
// ------------------------ adelete删除数组中的项, 返回布尔值 ------------------------
console.log(set.delete('b')); // true
console.log(set); //Set [ "a" ]
// ------------------------ has查询数组中是否存在该项,返回布尔值 -----------------------
console.log(set.has('a')); // true
// -------------------------------- 清空数组 ---------------------------------------
set.clear();
console.log(set); //Set []
// ------------------------------- forEach遍历数组 --------------------------------
var arr = ['a', 'b', 'c'];
let arr1 = arr.forEach((item) => {
console.log(item); // a b c
});