Js高级笔记

180 阅读15分钟

Js高级笔记

箭头函数

优点

(1)简洁的语法

(2)隐式返回,如 下面的代码可以去掉return,代码移到一行,减少代码量numbers.map((number)=>number*2)

(3)解决了this的指向问题,原生的写法this指向的是调用者,箭头函数this绑定的是定义时的那个对象。如果有对象嵌套的情况,则this绑定到最近的一层对象上

(4) 箭头函数没有 prototype 属性,不能进行 new 实例化,亦不能通过 call、apply 等绑定 this;

(5) 在定义类的方法时,箭头函数不需要在 constructor 中绑定 this。

缺点

(1)做为构造函数的时候不能使用箭头函数

(2)真正需要this的时候如给元素绑定click事件的 时候,执行的回调函数不能使用箭头函数。

(3)我们需要使用arguments对象的时候不能使箭头函数。箭头函数中没有arguments对象。

(4)对象的方法也不可以使用箭头函数

箭头函数是匿名函数,一般做为参数传递

  1. 没有形参、没有返回值

    const func = () => {
                console.log('执行业务');
            };
    
  2. 没有形参、没有返回值、业务只有一行代码 花括号都可以省略

    如果没有参数,()也不能省略

    const func = () => console.log('执行业务');
    
  3. 只有一个形参、没有返回值、业务只有一行代码(括号能省略)

    1.const func = num => console.log(num + 1); 
    
    2.const func = (num) => console.log(num + 1);
    
  4. 两个或者多个参数(括号不能省略)、没有返回值、业务只有一行代码

    const func = (a, b) => console.log(a + b);
    
  5. 没有形参,有返回值 业务两行代码

    const func = () => {
          let a = 100;
          return a + 100;
    };
    
  6. 没有形参、有返回值,业务一行代码

    const func7 = () => {
         return 100 + 200;
    };
    
  7. 没有形参、有返回值,业务一行代码 等价上述6的写法

     const func7 = () => 100 + 200; // 相等于 return 100+200
    
  8. 例子

     const button = document.querySelector('button');
            button.addEventListener('click', () => {
                console.log(123321);
            });
    

面向对象

面向过程和面向对象

面向过程

概述:

​ 面向过程就是分析出实现需求所需要的步骤,通过函数一步步实现这些步骤,然后依次调用 。

  • 优点:性能比面向对象好,因为类调用时需要实例化,开销比较大,比较消耗资源。 缺点:不易维护、不易复用、不易扩展。

面向对象

概述:
  1. 面向对象是把整个需求按照特点、功能划分,将这些存在共性的部分封装成对象,创建了对象不是为了完成某一个步骤,而是描述某个事物在解决问题的步骤中的行为。
  2. 一种编程行业通用的写项目级的代码的思维,引导我们如何编写高质量的代码,万物皆对象 -看待事物的角度,(属性:数据,行为:动作(方法))。代码特点是封装和继承
  • 优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统 更加灵活、更加易于维护 。
  • 缺点:性能比面向过程差 。

字面量-创建对象

const obj = {}

工厂函数

无法实现继承

function createPerson(name) {
return {name:name}
}

构造函数

  1. 构造函数的首字母大写 - 行内编码规范
  2. 构造函数想要解决 性能问题
  3. 一定会把方法-函数写在构造函数的外面
  4. 再通过this.say 指向外部的函数

工作原理

  1. 开辟空间
  2. 将新的创建的对象指向构造函数中的this
  3. 为对象赋值
  4. 将创建好的对象的地址返回
function say() {
            console.log('这个是say方法');
        }

        function CreatePerson(name) {
            this.name = name; // 创建了name属性
            // this.say = function () {
            //   console.log('这个是say方法');
            // };
            this.say = say; // say函数引用类型, 构造函数中的say 其实和外面的say内存地址一致的  同一个say方法
        }

        const obj1 = new CreatePerson('悟空');
        const obj2 = new CreatePerson('八戒');
        // obj1.say();
        // obj2.say();
        // 两个say的判断比较 是false 说明 两个say是在不同的内存空间上
        // 两个say 占用了两个内存空间
        // console.log(obj1.say === obj2.say); // false
        console.log(obj1.say === obj2.say); // true

        // 对于基本类型来说,=  就是复制了一份值
        // let num=100;
        // let num2=num;// 复制值  num和 num2占两给内存 各自不影响
        // num2=1000;
        // console.log(num);

        // 对于引用类似 =  其实把地址拷贝了一份给新的对象  两个数据 公用一份数据
        // let person1 = { name: '悟空' }; // person1 指向了 一个地址 0x1111 存放悟空
        // let person2 = person1; // person2也指向了person1的地址 0x1111  person1和person2 通用一个数据
        // 修改了person2 person1也会发生改变
        // person2.name = '八戒';
        // console.log(person1);

        // person2 和person1 共同指向了同一个地址
        // console.log(person1 === person2); // true

        // let o={};// 开辟了一个内存
        // let o2={};// 开辟了另外一个内存
        // console.log(o===o2);// false 两个不同的内存地址

构造函数的全局污染

// 构造函数的 方法 都会通过类似的这种方式 来实现 多个实例的方法共享
        function say() {
            console.log('你好');
        }

        function CreatePerson(name) {
            this.nickname = name;
            this.say = say;
        }
        // function say() {
        //   console.log("学生你好");
        // }
        // function createStudent(params) {
        // }

        const obj1 = new CreatePerson('悟空');
        const obj2 = new CreatePerson('八戒');
        console.log(obj1);
        console.log(obj2);

        console.log(obj1.say === obj2.say); // true  这个代码是合理的 优化过
        console.log(obj1.say === obj2.say); // false 不够好 性能不够好 两个say占据了两个内存

        // 函数函数 方法提取出去 这套代码
        // 优点: 方便代码维护、也解决了性能 obj1.say === obj2.say
        // 缺点:  代码不够优雅 污染了全局变量 以后不能写 say方法,很容易就覆盖()

构造函数的弊端

  1. 同一个 同样的方法会占据两份内存

    解决:

    1. 提取同一个同样的方法

      但提取同一个同样的方法虽然解决了浪费内存的弊端,又造成了 污染全局变量 的问题

      解决:

      原型上存放函数

原型对象

在构造函数的原型上存放函数

优点

  1. 解决了同一个 say 浪费 内存的问题
  2. 解决了污染全局变量的问题

解释

  • 原型的单词是 prototype, 原型的这个名字是行业内共同认可的名字。
  • 原型本质是一个对象,理解为 JavaScript 自动帮我们添加的
  • 原型是 JavaScript 自动帮我们在定义构造函数的时候添加的
  • 所有构造函数的实例,共享一个原型
  • 原型上一般是挂载函数

原型链继承

用代码的能力实现 面向对象的特性 封装继承

    function createStudent(name, age) {
      this.name = name;
      this.age = age;
    }
    // 将刚才的全局函数say 直接挂载到 构造函数的原型上 即可
    // prototype 是个对象 每一个构造函数都会内置有的. 我们称之为原型
    createStudent.prototype.say = function () {
      console.log(this.name);
    }

    const obj = new createStudent("悟能", 83);
    const obj1 = new createStudent("悟能1", 84);

    console.log(obj.say === obj1.say); // true
       // 创建图片
       function MyImg(src) {
           const img = document.createElement('img');
           img.src = src;
           document.body.appendChild(img);
           this.dom = img;
       }

       MyImg.prototype.scale = function() {
           this.dom.classList.add("scale");
       }

       const imgModel1 = new MyImg('./images/gotop.png');
       const btn1 = document.querySelector(".btn1");

       btn1.addEventListener("click", function() {
           imgModel1.scale();
       })

      // 复杂业务 面向对象技术需要(构造函数、this、原型对象)
    

        //   封装 代码 实现以下的功能
        //   1 父亲   Element
        //     1 属性 dom   this.dom
        //     2 行为 实例.append(父元素标签的选择器)
        //   2 儿子1  ElementDouble  div
        //     1 继承父亲 Element  
        //       属性 dom
        //       行为 append
        //   3 儿子2  ElementSingle  img 
        //       属性 dom
        //       行为 append

        //   4 当执行以下代码时 出现对应效果
        //     1 const divModel = new ElementDouble("div","div内的文字")
        //     2 divModel.append("body") ;  body标签可以出现 一个 div 

        //     1 const imgModel = new ElementSingle("img","图片地址")
        //     2 imgModel.append("body");  body标签可以出现 一个图片 

        //  父亲
        function Element(tagName) {
            const dom = document.createElement(tagName);
            this.dom = dom;
        }
        //  父亲
        Element.prototype.append = function(parentSelector) {
            document.querySelector(parentSelector).appendChild(this.dom);
        };

        // 儿子1
        function ElementDouble(tagName, content) {
            Element.call(this, tagName); // 继承 - 父亲的属性
            this.dom.innerText = content;
        }
        // 去继承父亲的行为
        ElementDouble.prototype.append = Element.prototype.append;

        // 儿子2
        function ElementSingle(tagName, src) {
            Element.call(this, tagName);
            this.dom.src = src;
        }
        ElementSingle.prototype.append = Element.prototype.append;
        const divModel = new ElementDouble('div', '这个是div');
        divModel.append('body');

        const imgModel = new ElementSingle('img', './images/b_01.jpg');
        imgModel.append('div');

        // 如果代码重复实现了 很有可能就是我们要封装的时候
        // 以前的封装 仅仅是封装一个小小的函数而已
        // 现在的封装, 面相对象的思维来封装
        // 封装 属性  父亲
        // 封装 方法  父亲

        // 两个儿子的代码 有重复部分
        // 创建标签重复 this.dom=dom  append方法也重复

        // 儿子1 有要什么功能
        // 先复用父亲的属性和方法

使用结论

  1. 普通的属性写在构造函数内
  2. 行为方法写在原型上prototype

Es6 Class

es6的class 的出现 基本上可以替代了es5的构造函数和原型,使之代码结构上更加简洁。

关键字

  1. class
  2. 属性
  3. 方法
  4. 继承 extends
  5. 构造函数 constructor
  6. 方法重写 override:子类方法覆盖父类,super.父类方法()
  7. 父类的构造函数 super :子类有构造方法且使用this前,必须使用super()
// 方法 constructor 在es6 构造函数
// constructor 会在 new Person的时候触发
        class Person {
            constructor(name) {
                this.name = name;
            }
            say() {
                console.log(this.name + '你好');
            }
        }
        const student = new Person('霉霉');
        student.say()
class Person {
            // 性能最好 推荐方式!! 
            say1() {
                console.log('say1');
            }
            say2 = function() {
                console.log('s2');
            };
            say3 = () => {
                console.log('s3');
            };
        }
        const p1 = new Person();
        const p2 = new Person();

        console.log(p1.say1 === p2.say1); // true p1 和 p2 say1方法是同一个-节省内存
        console.log(p1.say2 === p2.say2); // false  p1 和 p2 say2方法不是同一个-性能不好
        console.log(p1.say3 === p2.say3); // false  p1 和 p2 say3方法不是同一个-性能不好
// 使用es6的class 实现以下功能

        // 1 父类 Element 
        //   1 属性 this.dom
        //   2 方法 append

        // 2 定义两个子类 都要继承父亲的属性和方法
        //   1 ElementDouble
        //   2 ElementSingle 

        // 3 然后 通过编写代码 把以下程序运行起来

        // 父亲
        class Element {
            constructor(tagName) {
                const dom = document.createElement(tagName)
                this.dom = dom;
            }
            append(parentSelector) {
                document.querySelector(parentSelector).appendChild(this.dom);
            }
        }

        // 儿子1
        // 继承父亲的方法
        class ElementDouble extends Element {
            constructor(tagName, content) {
                super(tagName);
                this.dom.innerText = content
            }
        }
        // 儿子2
        class ElementSingle extends Element {
            constructor(tagName, src) {
                super(tagName);
                this.dom.src = src
            }
        }
        const divModel = new ElementDouble('div', '这个是div');
        divModel.append('body');

        const imgModel = new ElementSingle('img', './images/b_01.jpg');
        imgModel.append('div');

Es6和箭头函数

// es6 属性的定义 写法有两种
        // 1 直接在构造函数内  constructor  this.name=name
        // 2 可以写在 大括号内

        // 3 方法 三种写法

        class Person {
            // color = 'yellow';
            // height = 180;
            // weight = 200;
            constructor(name) {
                    this.name = name;
                    // this.color = 'yellow';
                    // this.height = 180;
                    // this.weight = 200;
                }
                // 写法一
                // say() {
                //   console.log('say 方法被调用了 ' + this.name);
                // }
                // 写法二
                // say = function () {
                //   console.log('say 方法被调用了 ' + this.name);
                // };
                // // 写法三
            say = () => {
                // 箭头函数中的this , 绝大部分指向  window
                // 例外 用在 es6的class 充当 方法  this 指向 p1 实例 
                console.log('say 方法被调用了 ' + this.name);
            };
        }
        const p1 = new Person('悟空');
        p1.say();
        // console.log(p1);

Class继承

class Person {
            constructor(name) {
                this.name = name;
            }
            say() {
                console.log('say方法我调用啦 ' + this.name);
            }
            fly() {
                console.log('父亲的起飞');
            }
        }

        // 表示学生要继承父亲
        // extends 直接就实现了继承父亲的方法
        class Student extends Person {
            //
            constructor(name, color) {
                    super(name); // 父亲的构造函数 =es5   Person.call(this,name);
                    this.color = color;
                }
                // fly(){
                //   console.log("儿子 起飞");
                // }
                // fly = null; // 用儿子的新的null 去覆盖父亲的fly没有父亲的fly
        }

        const s1 = new Student('学生', 'red');
        s1.say();
        s1.fly();
        /* 
        Must call super constructor in derived class before accessing 'this' or returning from derived constructor
        1 如果你写了 extends 而且还写了 constructor 那你必须要在 constructor 调用了方法 super();
        2 如果你只写了 extends 但是你没有写constructor 不需要管super 

        继承要继承父亲的属性和父亲的行为
        1 只实现了继承父亲的行为  还没有实现继承父亲的属性 (super 表示可以继承父亲的属性)
      

         */

函数参数默认值

  1. 定义函数的同时,可以给形参一个默认值
  2. 形参有值输出值,没有值默认值
// es6 函数参数默认值
// 你好 默认值
        function show(msg = '你好', str = "你我都好") {
            console.log(msg, str);
        }

        show(); // 没有传递参数  输出默认值你好
        show('大家好'); // 输出 大家好
        show('大家好', "世界美好"); // 输出 大家好

对象简写

  1. 如果变量的名字和属性的名字 一致的话,对象可以简写
const obj = {
// 属性名 和 属性值
// username: 123,
 };
const username = 123;
const color = 'red';
const say = function() {};

function show() {}


const obj = {
      username, // username:username
      color, // color : color
      say,
      show,
      height: 100,
};

      obj.height = 200;
        // 对象中方法的简写
        const person = {
            show: function() {
                console.log("show");
            }, // 常规写法
            // es6 关于 方法简写
            say() {
                console.log("say");
            } 
        }
        person.show();
        person.say();

        // 小结:
        // 1 变量名如果和你的对象的属性名一致 可以简写
          let username='悟空'
          const obj = { username }

        // 2 对象的方法 简写
         const obj ={
               say(){  // 简写的方法
          }
       }

解构

  1. 提供更加方便获取数组中元素或者对象中属性的写法

数组解构

获取数组中的元素
   const [a, b, c, d] = [1, 2, 3, 4];
   console.log(a, b, c, d);// 1,2,3,4
元素交互顺序
  let a = 1111;
  let b = 2222;
  [b, a] = [a, b];
  console.log(a, b); // 2222 1111

对象解构

获取对象中的属性(重点)
// 以前是 声明两个变量 来获取obj的两个属性
 const username = obj.username;
 const height = obj.height;

// 现在用对象解构
const { username, height } = obj;
console.log(username, height);

解构加默认值

// 解构 + 默认值
const arr = [1,100];
const [a,b ] = arr; a = 1 b = undefined
const [a, b = 2] = arr;
console.log(a, b); // a =1  b = 2

// b = 2 默认值 (你没有,就使用我,你有,使用你的)
const [a, b = 2] = arr;
console.log(a,b);



const obj = {
            username: 100,
            height: 500,
        };

const {
        username,
        height = 200
      } = obj;
   console.log(username, height);

小结:

  1. 解构对象

    ​ const { username,height } = {username:"悟空",height:200}

  2. 解构数组

    在右边找不到对应的数据 就使用默认值

     const [a,b]=[100,200];
    //c 在右边找不到对应的数据 c 就使用默认值 300 
    const [a,b,c=300]=[100,200];
    
  3. 解构 + 默认值

    //如果右边的对象中没有height 属性 那么 height变量 =  1000 
    const  { username,height } = {username:"悟空",height:200}
    const  { username,height=1000 } = {username:"悟空",height:200}
    

拓展运算符和剩余运算符

拓展运算符

通过 ...符号来获取剩下的参数

把数组或者类数组对象展开成一些列用逗号隔开的值

函数内获取
    function show(a, ...all) {		// 只能放最后
      console.log(a);
      console.log(all);
    }


    show(1);// 1 []
    show(1, 2, 3);// 1 [2,3]
数组内展开
function test(a, b, c) {
            console.log(a);
            console.log(b);
            console.log(c);
        }

  let arr = [1, 2, 3];
  test(...arr);   //1 2 3
  // 将一个数组插入到另一个数据中
        let arr1 = [1, 2, 3];
        let arr2 = [...arr1, 4, 5, 6];
        console.log(arr2);   // [1, 2, 3, 4, 5, 6]

   // 在数组的后面 新增一个 元素 'd'
   const newArr = [...arr,'d'];

   //在数组前面 新增一个属性 A
        console.log(newArr);
        const newArr = ['A', ...arr];
        console.log(newArr);
        arr.push 
        arr.unshift    
  // splice 来实现数组中间插入元素

//获取数组中的最大值
function getMax(...args) {
            let max = args[0];
            args.forEach((value) => {
                if (value > max) {
                    max = value
                }
            });
            console.log(max);
        }
getMax(12, 34, 100, 98, 56, 55)
对象内获取
    const obj = {
      name:"悟空",
      skill:"72变",
      say() {}
    }

    const {name,...others} = obj;
    console.log(name); // 悟空
    console.log(others); // {skill: "72变", say: ƒ}
对象内展开
// 对象是引用类型   写 = 相当于将obj的地址 给了一份 newObj  两个变量指向的地址同样的 同一个对象
   const newObj = obj;
   newObj.color = 'yellow';
   console.log("新的对象",newObj);
   console.log("旧的对象",obj);

// 建议这么做,可以互相影响
   const newObj = {...obj,
         color: 'yellow'
   }; // 给newObj 开辟新的内存空间
        //const newObj = { username:"悟空",height:20}; // 给newObj 开辟新的内存空间
        newObj.username = '八戒';
        newObj.weight = 100;
        console.log(obj);
        console.log(newObj);

剩余运算符

把逗号隔开的 值序列组合成数组

数组内剩余

    // 获取a后面的剩余所有参数
    const [a, ...rest] = [1, 2, 3, 4, 5];
    console.log(a); // 1
    console.log(rest); // [2, 3, 4, 5]

剩余参数和 arguments对象的区别

  1. 剩余参数只包含那些没有对应形参的实参,而 arguments 对象包含了传给函数的所有实参。
  2. arguments对象不是一个真正的数组,而剩余参数是真正的 Array实例,也就是说你能够在它上面直接使用所有的数组方法,比如 sortmapforEachpop
  3. arguments对象还有一些附加的属性 (如callee属性)。

Set对象

  1. 永远不会有重复元素的对象
  2. 可以理解为不重复的数组
    const set = new Set([1, 5, 3, 4]);
    set.add(5);
    set.add(5);
    console.log(set);

Set对象转为数组

    const set = new Set([1, 5, 3, 4]);
    set.add(5);
    set.add(5);
    console.log(set); //

    const arr = [...set];// 将set对象转数组
    console.log(arr);

    const beforeArr = ['ha', 1, 3, 5];
    const newArr = new Set(beforeArr); //Set 要new、首字母要大写
    newArr.add(6)
    const afterArr = [...newArr]; //将set对象转数组
    console.log(newArr); 

函数的四种调用模式

函数调用模式

如果一个函数不是一个对象的属性时,就是被当做一个函数来进行调用的。此时this指向了window。

function fn(){
  console.log(this);// 指向window 
}
fn();

方法调用模式

当一个函数被保存为对象的一个属性时,我们称之为一个方法。当一个方法被调用时,this被绑定到当前对象.

const obj = {
  sayHi:function(){
    console.log(this);//在方法调用模式中,this指向调用当前方法的对象。
  }
}
obj.sayHi();

构造函数调用模式

如果函数是通过new关键字进行调用的,此时this被绑定到创建出来的新对象上。

function Person(){
  console.log(this);
}
Person();//this指向什么?
var p = new Person();//this指向什么?

方法借用模式

上下文调用模式(借用方法模式)

也叫上下文模式,分为 apply 与 call

call

call方法可以调用一个函数,并且可以指定这个函数的this指向.

    const RichWumon = {
      name: "富婆",
      say: function () {
        console.log(this.name, " 我要重金求子");
      }
    }

    const obj = {
      name: "帅哥"
    }

    RichWumon.say();			// 富婆
    RichWumon.say.call(obj);	// 帅哥
call应用
	
    let divs = document.querySelectorAll('div');
    // let divs = document.body.children;
    console.log(divs);

    function change(nodelist) {
        console.log(Object.prototype.toString.call(nodelist));
        return Array.prototype.slice.call(nodelist);

    }

apply

apply()方法接受的是一个包含多个参数的数组。而call()方法接受的是若干个参数的列表

    const RichWumon = {
      name: "富婆",
      say: function () {
        console.log(this.name, " 我要重金求子");
      }
    }

    const obj = {
      name: "帅哥"
    }

    RichWumon.say();			// 富婆
    RichWumon.say.apply(obj);	// 帅哥

apply应用

// 简化log方法
    function log() {
        // 不需要改变this
        console.log.apply(console, arguments);
    }

bind

**bind()**方法创建一个新的函数, 可以绑定新的函数的this指向

var name = '张三';
function Fn(){
    this.age = 1;
    
    console.log(this.name + this.age);
}

Fn();			// 张三 1

// 返回值:新的函数
// 参数:新函数的this指向,当绑定了新函数的this指向后,无论使用何种调用模式,this都不会改变。
let obj = {
    name:'小强',
}
const newFn = Fn.bind(obj);
newFn();		// 小强 1

call、apply、bind写法区别

const obj = {
            name: '老王',
            skill() {
                console.log(this.name + '哈哈');
            }
        }
        const person = {
                name: '老例',
        }
        // call方法改变this指向
        obj.skill.call(person);

        // apply方法改变this指向
        obj.skill.apply(person);

        // bind方法改变this指向
        // 不会直接调用函数,而是返回一个新的函数
        let func = obj.skill.bind(person);
        // 然后主动调用新的函数-调用skill
        func();