▊ 介绍
面向过程(POP,Process-oriented programming):代码量小,性能高,适合与硬件联系(比如单片机);但不易维护
面向对象(OOP ,Object-oriented programming):易维护,易扩展,低耦合,三大特性;性能没有前者高,代码量大
面相对象三大特性:封装性,继承性,多态性
▊ 类与继承
// 声明类(属性+方法)
class Loli {
constructor(uname, age) { // 构造函数`constructor()` 在实例化对象时候自动调用;如果不显式写出也会自动生成
this.uname = uname;
this.age = age;
}
getAge(){
alert(this.age);
}
}
var chino = new Loli('chino', 12); // 实例化对象
// 继承与super关键字(在java中有详细的机制解析,这里仅仅提一下语法)
class Son extends Father {
constructor(x, y) { // 调用父类的构造器进行构造
super(x, y);
}
fun(){ // 使用super关键字拓展继承来的方法
super();
alert('suki');
}
}
ES6中类和对象的几个注意点:
▊ 一个"面相对象"的导航窗口
<div class="tabsbox" id="tab">
<nav class="nav">
<ul>
<li class="li-active"><span>测试1span><span class="del">×span>li>
<li><span>测试2span><span class="del">×span>li>
<li><span>测试3span><span class="del">×span>li>
ul>
<div class="tabadd">
<span>+span>
div>
nav>
<div class="con">
<section class="con-active">窗口1section>
<section>窗口2section>
<section>窗口3section>
div>
div>
var that; //这个全局变量指向整个tab对象(这很有必要,this指针不一定是指向tab的,而我们经常要使用tab对象)
class Tab {
constructor(id) {
// 获取元素写在构造器里,初始化绑定事件(init)也写在构造器里!
that = this; // 把这里的this指的对象存储起来(整个tab对象)
this.main = document.querySelector(id);
this.lis = this.main.querySelectorAll('li');
this.sections = this.main.querySelectorAll('section');
this.add = this.main.querySelector('.tabadd');
this.remove = this.main.querySelectorAll('.del');
this.ul = this.main.querySelector('.nav ul');
this.con = this.main.querySelector('.con');
this.spans = this.main.querySelectorAll('.nav li span:first-child');
this.init(); // new的时候立即初始化
}
updateNode() {
// 因为我们是在初始化时获取所有节点的,但有几个元素节点会动态的创建或删除,
// 因此把它们单独拿出来,加载初始化上,每次动态变化,就初始化一次
this.lis = this.main.querySelectorAll('li');
this.sections = this.main.querySelectorAll('section');
this.remove = this.main.querySelectorAll('.del');
this.spans = this.main.querySelectorAll('.nav li span:first-child');
}
init() {
this.updateNode();
// 初始化,让相关的元素绑定事件
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; // toggleTab中的this就是li
this.remove[i].onclick = this.removeTab; // removeTab中的this也是li
this.spans[i].ondblclick = this.editTab; // editTab中的this是span
this.sections[i].ondblclick = this.editTab; //看调用者,此时的editTab中的this是section
}
}
// 1.切换功能
toggleTab() {
that.clearClass();
this.className = 'li-active'; //这里的this是指li (this指向该方法的调用者)
that.sections[this.index].className = 'con-active';
}
clearClass() { // 把排他思想(干掉所有人,留下我自己)封装一下,多次用到
for (var i = 0; i < this.lis.length; i++) { //这里的this应该是tab对象,因此调用它要是用that
this.lis[i].className = '';
this.sections[i].className = '';
}
}
// 2.添加功能
addTab() {
that.clearClass();
// 除了document.createElement然后父元素appendChild/insertBefore之外,当这个加入的元素内部比较复杂时,还可以:
// 新建一个字符串,存储加入元素的完整HTML,然后通过insertAdjacentHTML加到父元素的某个位置上
var li = '测试add× ';
var section = '窗口add ';
that.ul.insertAdjacentHTML('beforeend', li);
that.con.insertAdjacentHTML('beforeend', section);
that.init(); // 每次动态更新节点,都要初始化一次(获取元素+绑定事件)
}
// 3.删除功能
removeTab(e) {
e.stopPropagation(); // 阻止冒泡(只让叉号被点击就可以,父盒子li不触发点击效果)
var index = this.parentNode.index;
that.lis[index].remove();
that.sections[index].remove();
that.init();
// 优化1:当删除的li不是处于选定状态时,选定状态的那个li及内容仍然保持选定
if (document.querySelector('.li-active')) return; //此时那个li已经被删了,如果还有.li-active存在,就不用再去管,直接return
// 优化2:当我们删除某个选定状态的li时',它的前一个li处于选中状态(包括内容)(思路极其简单:模拟一次点击就可以了)
index--;
that.lis[index] && that.lis[index].click(); // 手动调用点击事件(因为index有可能被减成-1,因此利用了一次巧妙的逻辑短路)
}
// 4.修改功能
editTab() {
// 核心思路:双击后,生成一个文本框,当失去焦点或者按下回车,把文本框的内容给原先的元素
var content = this.innerHTML;
window.getSelection ? window.getSelection().removeAllRanges() : document.selection.empty(); // 禁止双击选中文字
this.innerHTML = ''; // 内加入了一个input孩子
var input = this.children[0];
input.value = content;
input.select(); // 文本框内原先的内容被默认选中
input.onblur = function() {
this.parentNode.innerHTML = this.value;
}
input.onkeyup = function(e) {
if (e.keyCode === 13) { // 键盘事件
this.blur(); // 当和另一个事件效果一样时,完全可以手动调用另一个事件
}
}
}
}
var tab = new Tab('#tab');
▊ 原型
Dog.prototype.sing = function(){} // 通过原型声明方法(而不是在构造函数中声明)
Dog.prototype = { // 原型声明多个方法(通过对象的键值对实现)
constructor: Dog;
bark: function(){};
sleep: function(){};
}
Array.prototype.sum = function() { // 通过原型扩展内置对象的方法(★☆★典型用法)
var sum = 0;
for (var i = 0; i < this.length; i++) {
sum += this[i]; // 依据是 this指向实例对象
}
return sum;
}
var arr = [1, 2, 3];
console.log(arr.sum()); // 打印6
prototype
)分配的函数是对象所共享的__proto__
指向它,所以实例化对象才能使用这个方法。dog.__proto__ === Dog.prototype
__proto__
不断向上找,直到Object.prototype.__proto__
,即null。这也是就近原则的本质
▊ 继承
// 关于call()
fun.call(); // 当做调用函数的另一种方式
fun.call(myObject); // 这就是call的强大之处,可以指定this的指向(本来指向的是函数的调用者(即window),现在改变为对象myObject)
fun.call(myObject, 1, 2); // 后面正常传入参数
在ES6之前没有extends,是如何实现继承的呢?
☀ 经典继承:通过call()把父类型的this指向子类型的this,这样调用者就改变成了子类(父类的方法,调用者却是子类)
function Father(uname, age) {
this.uname = uname;
this.age = age;
}
function Son(uname, age, score) {
Father.call(this, uname, age); // 关键点(此时的this是子类,通过call实现了让子类作为调用者调用了父类的构造函数)
this.score = score;
}
☀ 原型链继承:绝对不是让子类的原型对象指向父类的原型对象(会使得父类受到子类的影响),而是使子类的原型对象指向父类的实例对象;另外,还是那句话只要改动了原型对象,就不要忘了把原型对象的constructor手动指回来
Son.prototype = new Father();
Son.prototype.constructor = Son;
☀ 组合继承(原型 + 借用构造函数):
// 下面是最为推荐的写法(相当于把Father构造函数的属性先清空了)
funtion F(){};
F.prototype = Father.prototype;
Son.prototype = new F();
Son.prototype.constructor = Son;
▊ ES6新增数组方法
forEach 迭代
var arr = [1, 3, 19, 233, 15];
arr.forEach(function(value, index, array) {
console.log(value);
console.log(index);
console.log(array);
})
filter 筛选
var newArr = arr.filter(function(value, index, array) {
return value > 10; // 返回一个新数组[19, 233, 15]
});
some / every 判断
var flag1 = arr.some(function(value, index, array) {
return value == 233; // true
});
var flag2 = arr.every(function(value, index, array) {
return value > 10; // false
})
注:
function
;function有三个参数value, index, array
,分别是迭代过程中此时的值,此时的下标,原数组return
后写筛选条件或判断条件,返回一个布尔值
▊ ES6新增对象方法
Object.keys(obj);
Object.values(obj)
Object.defineProperty(obj, 'id', {
value: 12, // 设置属性的值(有则修改,无则添加)
});
Object.defineProperty(obj, 'id', {
writable: false, // 目标属性的值是否可以被重写(有点主键的意味)
enumerable: false, // 目标属性是否可以被枚举,也就是被遍历出来
configurable: false, // 目标属性是否可以被删除,以及是否可以再次修改属性的特性(第三个参数)
});
// 注意:这三个属性都默认为false,也就是说,只要写了defineProperty,这几个属性都被置false
// 这样去理解:defineProperty不仅可以修改属性值,还可以修改属性的【特性】
☀ Loli & JS
♫ Suki