【JavaScript高级】面向对象,原型,ES6新增方法

 

介绍
 
面向过程(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中类和对象的几个注意点:

  1. 类没有变量提升,因此必须先定义类才能实例化对象
  2. 在类内部使用属性和方法时,一定别忘了加上this,表明属性或方法的所有者
  3. this的指向:this指向的是调用它的对象(对于构造函数来说,调用者就是正在构造的对象)

 

一个"面相对象"的导航窗口

【JavaScript高级】面向对象,原型,ES6新增方法_第1张图片

<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
    
    1. ES6之前JavaScript没有引入类(class),在此之前用原型来模拟类
    2. JS中同样有"静态"的概念——分为实例成员静态成员(和高级语言一样,分别是对象层面和层面)
    3. 如果方法放在构造函数中,那么创建出的多个对象会各自拥有一个该方法(本质键值对),大量重复会浪费空间。而且对于某些方法来说,这没必要
    4. 原型(prototype)分配的函数是对象所共享的
    5. prototype实际上也是一个对象。实例化对象通过__proto__指向它,所以实例化对象才能使用这个方法。
    6. dog.__proto__ === Dog.prototype
    7. 一般来说,我们把公共的属性放在构造函数里,公共的方法放到原型对象
    8. 这个原型对象有一个constructor属性,它指回原来的构造函数。像最上方的第二种方法那样给prototype赋值(值为对象),会导致constructor指向了该对象,因此必须手动指回去
    9. 原型链:当访问一个对象的属性和方法时,先查找该对象本身有没有该属性/方法;如果没有,就用__proto__不断向上找,直到Object.prototype.__proto__,即null。这也是就近原则的本质
    10. 无论是构造函数,还是原型对象this指针都指向实例对象

    【JavaScript高级】面向对象,原型,ES6新增方法_第2张图片

     

    继承

    // 关于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		
    })
    

    注:

    1. 参数都是function;function有三个参数value, index, array,分别是迭代过程中此时的值此时的下标原数组
    2. 上面的三个参数不是必须的,可以用什么写什么
    3. function的return后写筛选条件判断条件,返回一个布尔值
    4. some中的return为true时可以终止迭代,效率很高;其他的(forEach, filter, every)都不能被return true终止。根据每个方法的功能去理解,很简单

     
     

    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

    你可能感兴趣的:(JavaScript)