• JS高级(二):继承、数组的一些api、Object.defineProperty()、call、apply、bind


    一、继承

    1.call方法改变this指向

    call方法第一个参数可以改变函数的this指向,后面的参数和正常调用函数传参一样

    let obj = {
        name: 'dj'
    }
    function fn(x,y) {
        console.log('奥里给');
        console.log(this);  //obj
        console.log(x + y)
    }
    fn.call(obj,1,2);  //奥里给 obj 3
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    2.构造函数中模拟类的super实现属性继承

    在ES6之前是没有类的,只有构造函数(其实类就是构造函数,类的本质就是一个函数),那么子构造函数如何继承父构造函数的属性呢?
    答案是借助call方法,改变子构造函数中的this指向。

    //模拟类中的super实现继承
    function Father(name, age) {
        this.name = name;
        this.age = age;
    }
    
    function Son(name, age, score) {
        //调用一下父构造函数,并让它里面的this指向子构造函数
        Father.call(this, name, age);//调用并传参过去
        //相当于给子实例添加name和age属性
    
        this.score = score;  //再添加子构造函数自身的属性
    }
    
    let zzy = new Son('zzy', 18, 100);
    console.log(zzy.name, zzy.age, zzy.score); //zzy 18 100
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    3.构造函数借助原型对象实现方法继承

    还是上面那个案例。父的原型对象上定义一个方法,如果子想要调用这个方法,怎么办?
    答案是利用原型,如果直接Son.prototype = Father.prototype行吗?这是一个浅拷贝,这样的话子和父原型对象地址一样,如果子添加自有方法,父也会添加,不合理。

    但是如果Son.prototype = new Father();就可以,这是一个深拷贝,这样的话子的原型对象就可以通过父的实例指向父的原型对象,解决上面的问题。不过bug是子原型对象的constructor也会指向父,需要手动改为子。

    //2.1继承父构造函数的方法
    	Father.prototype.speak = function () {
    	    console.log('hello world');
    	}
    	//这样不行,如果子添加自有方法,父也会添加,因为他们指向同一个地址
    	// Son.prototype = Father.prototype; 
    	Son.prototype = new Father();  //这样就可以了
    	//但是如果这样的话,Son.prototype.constructor就指向Father了,我们要改回来
    	Son.prototype.constructor = Son;
    
        zzy.speak(); //hello world
        console.log(Son.prototype.constructor);//修改前:Father  后:Son
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在这里插入图片描述

    4.类的本质

    实际上类的本质就是一个函数,ES6的类其实就是ES5构造函数的语法糖。因为构造函数实现继承实在麻烦,而类只用个extends关键字就欧了。

    类也有原型对象,类的实例也有__proto__,所以类tmd就是构造函数。

    二、ES5几个新增方法

    1.数组forEach()

    遍历数组,可以改变原数组(复杂数据类型),返回undefined,这个api中写return无法终止循环。
    同样可以实现遍历的map()不会改变原数组,会生返回一个新数组

    	let arr = [12, 2, 34];
    	arr.forEach(function (el, index, array) {
    	    console.log('每个数组元素' + el);
    	    console.log('每个数组元素的索引' + index);
    	    console.log('数组本身' + array);
    	})
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    什么时候forEach()不能改变原数组?当数组元素为简单数据类型时(操作el不会变,操作arr[index]就会变,奇怪),而复杂数据类型如果修改了里面的属性,原数组就会改变

    //1.简单数据类型,不会改原数组
    let arr = [12, 2, 34];
    arr.forEach(function (el, index, array) {
        console.log('每个数组元素' + el);
        console.log('每个数组元素的索引' + index);
        console.log('数组本身' + array);
        el = el + 100;
        console.log(array,'不变');  //[12, 2, 34]'不变'
        array[index] = array[index] + 100;
        console.log(array,'变了'); //[112, 12, 134]'变了',这样就可以改变原数组,奇怪
    })
    console.log(arr);  //[112, 12, 134]变了
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    这是因为简单数据类型存储在栈中,复杂数据类型在堆中,forEach如果修改复杂数据类型,相当于拿它们的地址来修改里面的属性。换句话说,forEach操作时是对原数组的一个浅拷贝

    //2.复杂数据类型,会改原数组
    let arr2 = [{age:18}, {age:2}, {age:34}];
    arr2.forEach(function (el, index, array) {
        el.age = 20;
    })
    console.log(arr2); //[{age:20}, {age:20}, {age:20}]变了
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    for循环可以改变原数组

    for (let i = 0; i < arr.length; i++) {
        arr[i] = arr[i] +100;
    }
    console.log(arr,'11'); // [112, 102, 134] '11'
    
    • 1
    • 2
    • 3
    • 4

    2.数组filter()

    筛选数组元素,返回一个新数组。
    参数:(每个元素,索引号,数组本身)

    let arr = [23, 53, 4, 3, 12];
    //使用filter筛选小于20的数字
    let newArr = arr.filter(function (el, index, array) {
        // 参数:每个元素,索引号,数组本身
        return el > 20; //把为true的筛出来
    })
    console.log(newArr); //[23,53]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    3.数组some()

    查找数组中有没有满足条件的元素,如果找到第一个,就不再往后执行,这也导致some的效率比其他的更高一些,找到了直接return true就终止循环了。
    参数:(每个元素,索引号,数组本身)
    filter差不多,区别在于some返回的是一个布尔值

    let arr = [23, 53, 4, 3, 12];
    let hasEl = arr.some( (el) => {
        return el === 4;
    })
    console.log(hasEl); //true
    
    • 1
    • 2
    • 3
    • 4
    • 5

    4.字符串trim()

    去除字符串两边的空格(只去首尾),返回一个新的字符串。如果完了去看todolist,里面用过这样的骚操作。

    let str = '  zzy   ';
    let newStr = str.trim();
    console.log(str);
    console.log(newStr);
    
    • 1
    • 2
    • 3
    • 4

    在这里插入图片描述

    三、Object.definProperty()

    这个很重要,vue中双向绑定的原理就是这个
    在这里插入图片描述
    enumerablewritableconfigurable这些属性,只有新增的才会默认为false,对象本来就有的都是true

    let number = 18;
    let person = {
        name: 'zzy',
        sex: '男',
        // age: number
    }
    
    Object.defineProperty(person, 'age', {
        //     value: 18,
               enumerable: true,  //控制属性是否可以被遍历,默认值是false
        //     writable: true,  //控制属性是否可以被修改,默认值是false
        //     configurable: true  //控制属性是否可以被删除,默认值是false
    
        //当有人读取person的age属性时,get函数(getter)就会被调用,且返回值就是age的值
        get() {
            console.log('有人读取age了');
            return number;
        },
    
        //当有人修改person的age属性时,set函数(setter)就会被调用,且返回值就是age的值
        set(val) {
            console.log('有人修改age的值,' + val);
            number = val;
        }
    })
    console.log(person); //{name: 'zzy', sex: '男'}
    console.log(Object.keys(person)); // ['name', 'sex', 'age']
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    四、改变this指向的三个函数

    这三个函数都可以改变函数内部的this指向

    1.call()

    特点:
    1、参数(this指向谁,参数1,参数2…)
    2、立即调用

     let obj = {
         name: 'dj'
     }
     function fn(x, y) {
         console.log(this); 
         console.log(x + y)
     }
     fn.call(obj, 1, 2);  //obj 3
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    2.apply()

    特点:
    1、第二个参数是一个伪数组:( this指向谁,[ ] )
    2、立即调用
    3、数组传过去会把元素取出来展示,每一个形参对应数组的每一项

      function fn2(el1,el2) {
          console.log(this);  //obj
          console.log(el1,el2);  //字符串'zzy' 'ht'
      }
      fn2.apply(obj,['zzy','ht']);  
    
    • 1
    • 2
    • 3
    • 4
    • 5

    应用:利用apply求数组中的最大值(apply传过去的数组会被拆开)

     let arr = [23, 143, 2, 1, 25];
     let max = Math.max.apply(null, arr); //不用改this所以写null,严格模式下写Math
     console.log(max); //143
    
     console.log(Math.max(...arr)); //用这个更简单
    
    • 1
    • 2
    • 3
    • 4
    • 5

    3.bind()

    特点:
    1、参数(this指向谁,参数1,参数2…)
    2、不会立即调用
    3、返回一个改变this指向后的原函数拷贝

    function fn3(x, y) {
        console.log(this);
        console.log(x + y)
    }
    let newFn = fn3.bind(obj, 1, 2);  //不会立即调用
    newFn(); //obj 3
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    bind应用:点击按钮禁用,3秒后取消禁用。bind可以改变函数的this指向,但不立即执行。看完这个,再去对比ES6中的箭头函数,就知道箭头函数有多方便了。

    let btn = document.querySelector('button');
    btn.addEventListener('click', function() {
        this.disabled = true;
        setTimeout(function() {
            this.disabled = false;  //把this从window改成btn
        }.bind(this),3000)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
  • 相关阅读:
    Python大数据之PySpark(八)SparkCore加强
    < Python全景系列-3 > Python控制流程盘点及高级用法、神秘技巧大揭秘!
    输入输出优化
    Opengl之深度测试
    CSS基础——复合选择器
    制作php的composer包
    计算机网络基础之计算机网络组成与分类
    Filter过滤器,责任链设计模式
    MySQL 数据库
    B树和B+树的区别
  • 原文地址:https://blog.csdn.net/weixin_42044763/article/details/127881254