# 前端整理博客

# [js]

[1] 对象继承方式,优缺点

现在有一个"动物"对象的构造函数。

  function Animal(){
    this.species = "动物";
  }

还有一个"猫"对象的构造函数。

  function Cat(name,color){
    this.name = name;
    this.color = color;
  }

怎样使"猫"继承"动物"呢?

  1. 构造函数绑定 (盗用构造函数/对象伪装/经典继承)

第一种方法也是最简单的方法,使用call或apply,将父对象的构造函数绑定在子对象上,即在子对象构造函数中加一行:

	function Animal(params){
    this.species = "动物";
		this.animal=function(){
			console.log('1000'+params)
		};
		this.arr=[1,2,3,4];
		
  }
	Animal.prototype.dosomething = function(){
		console.log("do something")
	}
 	function Cat(name,color,test){
    Animal.apply(this, [test]);
    this.name = name;
    this.color = color;
  }

  var cat1 = new Cat("大毛","黄色",'book');
  console.log(cat1.species=100000); // 100000
  console.log(cat1.species); // 100000
	cat1.animal()// 1000book
	console.log(cat1.arr.push(2))// 5
	console.log(cat1.arr)// [1, 2, 3, 4, 2]
	try{
		cat1.dosomething()
	}catch(e){
		console.log(e)
		//TypeError: cat1.dosomething is not a function
	}
	var cat2 = new Cat("三花","三色");
  console.log(cat2.species); // 动物
	cat1.animal()// 1000book
	console.log(cat2.arr)//[1, 2, 3, 4]

优缺点

  • 优点:和原型链继承完全反过来。
    • 父类的引用属性不会被共享
    • 子类构建实例时可以向父类传递参数
  • 缺点:父类的方法不能复用(原型上),子类实例的方法每次都是单独创建的。
  1. prototype原型链模式

如果"猫"的prototype对象,指向一个Animal的实例,那么所有"猫"的实例,就能继承Animal了。

	function Animal(params){
    this.species = "动物";
		this.animal=function(){
			console.log('1000'+params)
		};
		this.arr=[1,2,3,4];
		
  }
	Animal.prototype.dosomething = function(){
		console.log("do something")
	}
 	function Cat(name,color){
    this.name = name;
    this.color = color;
  }
	
	Cat.prototype = new Animal();
  Cat.prototype.constructor = Cat;
  var cat1 = new Cat("大毛","黄色");
  var cat2 = new Cat("三花","三色");

	cat1.animal() // 1000undefined
	console.log(cat1.arr.push(100)) // 5
	console.log(cat1.arr) // [1, 2, 3, 4, 100]
	cat1.dosomething() //do something
	console.log(cat2.arr)// [1, 2, 3, 4, 100]

原型链模式优缺点

  • 优点:父类方法可以复用,可以继承原型链上的方法
  • 缺点:
    • 父类的引用属性会被所有子类实例共享
    • 子类构建实例时不能向父类传递参数
    • 子类原型上的方法会被直接覆盖无效
  1. 直接继承prototype

第三种方法是对第二种方法的改进。由于Animal对象中,不变的属性都可以直接写入Animal.prototype。所以可以让Cat()跳过 Animal(),直接继承Animal.prototype。

  function Animal(){ }
  Animal.prototype.species = "动物";
//然后,将Cat的prototype对象,然后指向Animal的prototype对象,这样就完成了继承。
  Cat.prototype = Animal.prototype;
  Cat.prototype.constructor = Cat;
  var cat1 = new Cat("大毛","黄色");
  console.log(cat1.species); // 动物

与前一种方法相比,这样做的优点是效率比较高(不用执行和建立Animal的实例了),比较省内存。缺点是 Cat.prototype和Animal.prototype现在指向了同一个对象, 那么任何对Cat.prototype的修改,都会反映到Animal.prototype。

所以,上面这一段代码其实是有问题的。

  Cat.prototype.constructor = Cat;

这一句实际上把Animal.prototype对象的constructor属性也改掉了!

  alert(Animal.prototype.constructor); // Cat
  1. 原型式继承

用途场景:通过原型实现对象之间的信息共享

function object(o) {
  function F() {}
  F.prototype = o;
  return new F();
}

let person = {
  name: "Nicholas",
  friends: ["Shelby", "Court", "Van"]
};
           
let anotherPerson = object(person);
anotherPerson.name = "Greg";
anotherPerson.friends=[];
anotherPerson.friends.push('shi')
           
let yetAnotherPerson = object(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
           
console.log(person.friends);   // " ['Shelby', 'Court', 'Van', 'Barbie']"

console.log(anotherPerson)

es5通过增加Object.create()方法将原型式继承的概念规范化了。

  1. 组合继承
//父类:人
function Person () {
  this.head = '脑袋瓜子';
  this.emotion = ['喜', '怒', '哀', '乐']; //人都有喜怒哀乐
}
//将 Person 类中需共享的方法放到 prototype 中,实现复用
Person.prototype.eat = function () {
  console.log('吃吃喝喝');
}
Person.prototype.sleep = function () {
  console.log('睡觉');
}
//子类:学生,继承了“人”这个类
function Student(studentID) {
  this.studentID = studentID;
  Person.call(this);/*第二次调用Person*/
}
/*第一次调用Person*/
Student.prototype = new Person();  
Student.prototype.constructor = Student;  

var stu1 = new Student(1001);
console.log(stu1.emotion); //['喜', '怒', '哀', '乐']

stu1.emotion.push('愁');
console.log(stu1.emotion); //["喜", "怒", "哀", "乐", "愁"]

var stu2 = new Student(1002);
console.log(stu2.emotion); //["喜", "怒", "哀", "乐"]

stu1.eat(); //吃吃喝喝
stu2.sleep(); //睡觉
console.log(stu1.constructor);  //Student
console.log(stu1);  

优缺点

  • 优点:
    • 父类的方法可以被复用
    • 父类的引用属性不会被共享
    • 子类构建实例时可以向父类传递参数
  • 缺点:调用了两次父类的构造函数,第一次给子类的原型添加了父类的eat, sleep属性(其实head和emotion也会被添加),第二次又给子类的构造函数添加了父类的head, emotion属性, 从而覆盖了子类原型中的同名参数。这种被覆盖的情况造成了性能上的浪费。
  1. 寄生式继承

寄生式(parasitic)继承是与原型式继承紧密相关的一种思路。寄生式继承的思路与寄生构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真地是它做了所有工作一样返回对象。

//核心:使用原型式继承获得一个目标对象的浅复制,然后增强这个浅复制的能力。
function object(o){
    function F() {}
    F.prototype = o;
    return new F();
}
function createAnother(original){
    var clone = object(original);//通过调用函数创建一个新对象
    clone.sayHi = function () {//以某种方式来增强这个对象
        console.log("Hi");
    };
    return clone; //返回这个对象
}
var person = {
    name:"李白",
    friends:["杜甫","陆游"]
};

var anotherPerson = createAnother(person)
anotherPerson.sayHi();//Hi
anotherPerson.friends.push('陶渊明')
console.log(anotherPerson.friends);//['杜甫', '陆游', '陶渊明']
var anotherPerson1 = createAnother(person)
console.log(anotherPerson1.friends);//['杜甫', '陆游', '陶渊明']
  1. 寄生组合继承

组合继承有一个会两次调用父类的构造函数造成浪费的缺点,寄生组合继承就可以解决这个问题。算是比较完美的方法

function object(o) {
  function F() {}
  F.prototype = o;
  return new F();
}
function inheritPrototype(subType, superType){
    var prototype = object(superType.prototype); // 创建了父类原型的浅复制
    prototype.constructor = subType;             // 修正原型的构造函数
	console.log(SuperType.prototype.constructor) 
	//直接用Object(superType.prototype),相当于new Object,
	//会修改掉SuperType.prototype.constructor,所以需要中间创建一个空对象中介一下
    subType.prototype = prototype;               // 将子类的原型替换为这个原型
}

function SuperType(name){
    this.name = name;
    this.colors = ["red", "blue", "green"];
}

SuperType.prototype.sayName = function(){
    console.log(this.name);
};

function SubType(name, age){
    SuperType.call(this, name);
    this.age = age;
}
// 核心:因为是对父类原型的复制,所以不包含父类的构造函数,也就不会调用两次父类的构造函数造成浪费
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function(){
    console.log(this.age);
}
let s1 =new SubType('zhang','30')
let s2 =new SubType('huang','20')
console.log(s1.colors.push(1)) // 4
console.log(s1.colors) // ["red", "blue", "green", 1]
console.log(s2.colors) //  ["red", "blue", "green"]
console.log(s1)
  1. 利用空对象作为中介

由于"直接继承prototype"存在上述的缺点,所以利用一个空对象作为中介。

  var F = function(){};
  F.prototype = Animal.prototype;
  Cat.prototype = new F();
  Cat.prototype.constructor = Cat;

F是空对象,所以几乎不占内存。这时,修改Cat的prototype对象,就不会影响到Animal的prototype对象。

  alert(Animal.prototype.constructor); // Animal
  function extend(Child, Parent) {
    var F = function(){};
    F.prototype = Parent.prototype;
    Child.prototype = new F();
    Child.prototype.constructor = Child;
    Child.uber = Parent.prototype;
  }

使用的时候,方法如下

  extend(Cat,Animal);

  var cat1 = new Cat("大毛","黄色");

  alert(cat1.species); // 动物

这个extend函数,就是YUI库如何实现继承的方法。

另外,说明一点,函数体最后一行

  Child.uber = Parent.prototype;

意思是为子对象设一个uber属性,这个属性直接指向父对象的prototype属性。(uber是一个德语词,意思是"向上"、"上一层"。)这等于在子对象上打开一条通道,可以直接调用父对象的方法。这一行放在这里,只是为了实现继承的完备性,纯属备用性质。

  1. 拷贝继承

也可以换一种思路,纯粹采用"拷贝"方法实现继承。简单说,如果把父对象的所有属性和方法,拷贝进子对象,不也能够实现继承吗?

首先,还是把Animal的所有不变属性,都放到它的prototype对象上。

  function Animal(){}

  Animal.prototype.species = "动物";

然后,再写一个函数,实现属性拷贝的目的。

  function extend2(Child, Parent) {

    var p = Parent.prototype;

    var c = Child.prototype;

    for (var i in p) {

      c[i] = p[i];

      }

    c.uber = p;

  }

这个函数的作用,就是将父对象的prototype对象中的属性,一一拷贝给Child对象的prototype对象。

使用的时候,这样写:

  extend2(Cat, Animal);
  var cat1 = new Cat("大毛","黄色");
  alert(cat1.species); // 动物
  1. "非构造函数"的继承

现在有一个对象,叫做"中国人"。

  var Chinese = {
    nation:'中国'
  };

还有一个对象,叫做"医生"。

  var Doctor ={
    career:'医生'
  }

怎样才能让"医生"去继承"中国人".

这里要注意,这两个对象都是普通对象,不是构造函数,无法使用构造函数方法实现"继承"。

  function object(o) {
    function F() {}
    F.prototype = o;
     return new F();
  }

这个object()函数,其实只做一件事,就是把子对象的prototype属性,指向父对象,从而使得子对象与父对象连在一起。

使用的时候,第一步先在父对象的基础上,生成子对象:

  var Doctor = object(Chinese);

然后,再加上子对象本身的属性:

  Doctor.career = '医生';

这时,子对象已经继承了父对象的属性了。

  console.log(Doctor.nation); //中国

[2] 深拷贝和浅拷贝和手动实现

  1. 浅拷贝是创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 。
    • Object.assign():需注意的是目标对象只有一层的时候,是深拷贝
    • Array.prototype.concat()合并数组
    • Array.prototype.slice()截取数组
    • let b={...a}
  2. 深拷贝是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象。
    • 借助第三方库如 lodash 的_.cloneDeep,jquery 提供一个$.extend
    • JSON.parse(JSON.stringify())最简便的方法,适用于 90%业务场景(会忽略掉 undefined、symbol、function)
let a = {
  age: undefined,
  sex: Symbol("male"),
  jobs: function () {},
  name: "yck",
};
let b = JSON.parse(JSON.stringify(a));
console.log(b); // {name: "yck"}
//忽略掉函数和 undefined symbol
//定义检测数据类型的功能函数
function checkedType(target) {
  return Object.prototype.toString.call(target).slice(8, -1);
}
//实现深度克隆---对象/数组
function clone(target) {
  //判断拷贝的数据类型
  //初始化变量result 成为最终克隆的数据
  let result,
    targetType = checkedType(target);
  if (targetType === "Object") {
    result = {};
  } else if (targetType === "Array") {
    result = [];
  } else {
    return target;
  }
  //遍历目标数据
  for (let i in target) {
    //获取遍历数据结构的每一项值。
    let value = target[i];
    //判断目标结构里的每一值是否存在对象/数组
    if (checkedType(value) === "Object" || checkedType(value) === "Array") {
      //对象/数组里嵌套了对象/数组
      //继续遍历获取到value值
      result[i] = clone(value);
    } else {
      //获取到value值是基本的数据类型或者是函数。
      result[i] = value;
    }
  }
  return result;
}

[3] 事件循环机制,eventloop。

  • macro-task(宏任务):包括整体代码 script,setTimeout,setInterval
  • micro-task(微任务):Promise,process.nextTick

不同类型的任务会进入对应的 Event Queue,比如 setTimeout 和 setInterval 会进入相同的 Event Queue。

事件循环的顺序,决定 js 代码的执行顺序。进入整体代码(宏任务)后,开始第一次循环。接着执行所有的微任务。然后再次从宏任务开始,找到其中一个任务队列执行完毕,再执行所有的微任务。

事件循环,宏任务,微任务的关系如图所示:

console.log("1");

setTimeout(function () {
  console.log("2");
  process.nextTick(function () {
    console.log("3");
  });
  new Promise(function (resolve) {
    console.log("4");
    resolve();
  }).then(function () {
    console.log("5");
  });
});
process.nextTick(function () {
  console.log("6");
});
new Promise(function (resolve) {
  console.log("7");
  resolve();
}).then(function () {
  console.log("8");
});

setTimeout(function () {
  console.log("9");
  process.nextTick(function () {
    console.log("10");
  });
  new Promise(function (resolve) {
    console.log("11");
    resolve();
  }).then(function () {
    console.log("12");
  });
});
// 1 7 6 8 2 4 3 5 9 11 10 12

第一轮事件循环流程分析如下:

  • 整体 script 作为第一个宏任务进入主线程,遇到 console.log,输出 1
  • 遇到 setTimeout,其回调函数被分发到宏任务 Event Queue 中。我们暂且记为 setTimeout1。
  • 遇到 process.nextTick(),其回调函数被分发到微任务 Event Queue 中。我们记为 process1。
  • 遇到 Promise,new Promise 直接执行,输出 7 。then 被分发到微任务 Event Queue 中。我们记为 then1。
  • 又遇到了 setTimeout,其回调函数被分发到宏任务 Event Queue 中,我们记为 setTimeout2。
宏任务 Event Queue 微任务 Event Queue
setTimeout1 process1
setTimeout2 then1

上表是第一轮事件循环宏任务结束时各 Event Queue 的情况,此时已经输出了 1 和 7。

我们发现了 process1 和 then1 两个微任务。

  • 执行 process1,输出 6。
  • 执行 then1,输出 8。

好了,第一轮事件循环正式结束,这一轮的结果是输出 1,7,6,8。那么第二轮时间循环从 setTimeout1 宏任务开始:

首先输出 2。接下来遇到了 process.nextTick(),同样将其分发到微任务 Event Queue 中,记为 process2。new Promise 立即执行输出 4,then 也分发到微任务 Event Queue 中,记为 then2。

宏任务 Event Queue 微任务 Event Queue
setTimeout2 process2
- then2

第二轮事件循环宏任务结束,我们发现有 process2 和 then2 两个微任务可以执行。 输出 3。 输出 5。 第二轮事件循环结束,第二轮输出 2,4,3,5。 第三轮事件循环开始,此时只剩 setTimeout2 了,执行。 直接输出 9。 将 process.nextTick()分发到微任务 Event Queue 中。记为 process3。 直接执行 new Promise,输出 11。 将 then 分发到微任务 Event Queue 中,记为 then3。

宏任务 Event Queue 微任务 Event Queue
- process3
- then3

第三轮事件循环宏任务执行结束,执行两个微任务 process3 和 then3。

  • 输出 10。
  • 输出 12。 第三轮事件循环结束,第三轮输出 9,11,10,12。

整段代码,共进行了三次事件循环,完整的输出为 1,7,6,8,2,4,3,5,9,11,10,12。 (请注意,node 环境下的事件监听依赖 libuv 与前端环境不完全相同,输出顺序可能会有误差)


[4] 前端图片预览几种方式。

  1. 通过 blob 协议实现预览,windowURL.createObjectURL(document.getElementById('inputimg').files[0])[速度快]
  2. 获取 input 的 files,利用 imgfile=new FileReader.readAsDataURL 读取,然后调用 imgfile.onload 获取 base64 格式的地址
  3. 上传后获取后端返回值

[5] ajax

一个完整的 AJAX 请求包括五个步骤:

  1. 创建 XMLHTTPRequest 对象
  2. 使用 open 方法创建 http 请求,并设置请求地址 xhr.open(get/post,url,async,true(异步),false(同步))经常使用前三个参数
  3. 设置发送的数据,用 send 发送请求
  4. 注册事件(给 ajax 设置事件)
  5. 获取响应并更新页面
function ajax(url) {
  var xhr = window.XMLHttpRequest
    ? new XMLHttpRequest()
    : ActiveXObject("microsoft.XMLHttp");
  xhr.open("get", url, true);

  xhr.send();
  //如果是post请求,可以在send操作设置传参
  //xhr.setRequestHeader("content-type", "application/x-www-form-urlencoded")
  //xhr.send("username=张三");
  xhr.onreadystatechange = () => {
    if (xhr.readyState == 4) {
      if (xhr.status == 200) {
        var data = xhr.responseText;
        return data;
      }
    }
  };
}

[6] null 和 undefined 区别

  1. 首先看一个判断题:null 和 undefined 是否相等
console.log(null == undefined); //true
console.log(null === undefined); //false

观察可以发现:null 和 undefined 两者相等,但是当两者做全等比较时,两者又不等。

原因:null: Null 类型,代表“空值”,代表一个空对象指针,使用 typeof 运算得到 “object”,所以可以认为它是一个特殊的对象值。

      undefined: Undefined类型,当一个声明了一个变量未初始化时,得到的就是undefined。

实际上,undefined 值是派生自 null 值的,ECMAScript 标准规定对二者进行相等性测试要返回 true,


[7] 闭包的应用和内存泄漏

  • 闭包
    • 形成三要素
      1. 函数嵌套
      2. 内层函数使用外层变量
      3. (被变量调用整个函数嵌套体)
    • 闭包作用
      1. 保护需要保护的数据不被定义在外面被轻易修改
    • 避免无效闭包
      1. 循环引用(不写在一个函数中,或者结尾补上 test=null)
      2. for 循环中异步操作
      3. 代码优化:写在原型链上的方法优于写在构造函数上(写在构造函数,不必要的形成闭包)
    • 应用场景
      1. 权限收敛
      2. 防抖节流
      3. 缓存数据
  • 内存泄漏
    1. 意外的全局变量
    2. 被遗忘的计时器或回调函数
    3. 脱离 DOM 的引用(removeChild 之后游离的 dom)
    4. 闭包
function func() {
  var test = document.getElementById("test");
  test.onclick = funcTest;
}
function funcTest() {
  console.log("hello world");
}
//封装变量 收敛权限
//记录是否存在,不存在录入,存在则返回false,如果不用闭包,就需要暴露一个数组,容易被人为修改
function quanxian() {
  let arr = [];
  return function (val) {
    if (arr.indexOf(val) >= 0) {
      console.log(false);
      return false;
    } else {
      arr.push(val);
      console.log(true);
      return true;
    }
  };
}
let ceshi = quanxian();
ceshi(1); //true
ceshi(1); //false
ceshi(2); //true
<html>
  <style>
    #test {
      width: 400px;
      height: 400px;
      border: 1px solid red;
    }
  </style>
  <body class="m-2">
    <div id="test">1111</div>
    <div id="test">1111</div>
    <div id="test">1111</div>
    <div id="test">1111</div>
    <div id="test">1111</div>
    <div id="test">1111</div>
    <div id="test">1111</div>
    <div id="test">1111</div>
    <script>
      function throttle(fn, delay) {
        // last为上一次触发回调的时间, timer是定时器
        let last = 0,
          timer = null;
        // 将throttle处理结果当作函数返回

        return function () {
          // 保留调用时的this上下文
          let context = this;
          // 保留调用时传入的参数
          let args = arguments;
          // 记录本次触发回调的时间
          let now = +new Date();

          // 判断上次触发的时间和本次触发的时间差是否小于时间间隔的阈值
          if (now - last < delay) {
            // 如果时间间隔小于我们设定的时间间隔阈值,则为本次触发操作设立一个新的定时器
            clearTimeout(timer);
            timer = setTimeout(function () {
              last = now;
              fn.apply(context, args);
            }, delay);
          } else {
            // 如果时间间隔超出了我们设定的时间间隔阈值,那就不等了,无论如何要反馈给用户一次响应
            last = now;
            fn.apply(context, args);
          }
        };
      }

      // 用新的throttle包装scroll的回调
      const better_scroll = throttle(() => console.log("触发了滚动事件"), 1000);

      document.addEventListener("scroll", better_scroll);
    </script>
  </body>
</html>

[8] 事件的冒泡和捕获

  • event.stopPropagation:阻止捕获和冒泡阶段中当前事件的进一步传播

  • event.stopImmediatePropagation():阻止事件冒泡并且 阻止该元素上同事件类型的监听器被触发

  • 监听事件,默认第三个参数 false,为冒泡。设为 true 则是捕获模式

c.addEventListener(
  "click",
  function (event) {
    console.log("c2");
  },
  true
);

[9] es6 新特性

  1. const let 声明,注意 const 声明对象时可以修改堆内容 (const/var/let)
  2. 模板字符串
  3. symbol
  4. 数组和对象新增的一些方法
  5. 扩展运算符 和 剩余运算符
var arr2 = [...arr1, 4, 5, 6];//扩展
var [a,...temp]=[1, 2, 4];//剩余操作符
function rest02(item, ...arr)//剩余操作符
  1. promise generator async/await
  2. class
  3. 解构赋值
  4. 箭头函数

[10] 面向对象简述

面向对象是一种思想,是基于面向过程而言的,就是说面向对象是 将功能等通过对象来实现,将功能封装进对象之中,让对象去实现具体的细节 ;这种思想是将数据作为第一位,这是对数据一种优化,操作起来更加的方便,简化了过程。

es6 之前是没有 class 类型的,但是每个函数都有一个 prototype 属性,prototype 指向一个对象,当函数作为构造函数时,prototype 就起到 类似于 class 的作用。

面向对象有三个特点:封装(隐藏对象的属性和实现细节,对外提供公共访问方式),继承(提高代码复用性,继承是多态的前提),多态(是父类或接口定义的引用变量可以指向子类或具体实现类的实例对象)


[11] new 对象

  1. 创建一个新的空对象
  2. 这个新对象内部的[[prototype]]特性被赋值为构造函数的prototype属性(将obj的__proto__成员指向了构造函数对象的prototype成员对象)
  3. 构造函数内部的 this 被赋值为这个新对象(即 this 指向新对象)
  4. 执行构造函数内部的代码:给对象添加属性和方法
  5. 如果构造函数返回非空对象,则返回该对象;否则返回刚创建的对象

[12] 原型和原型链

  1. 每个函数 都有一个 默认的 prototype 属性,叫做原型对象。
  2. 创建函数时,JS 会为这个函数自动添加 prototype 属性,值是空对象。而一旦把这个函数当作构造函数( constructor )调用(即通过 new 调用),那么 JS 就会创建该构造函数的实例,实例继承构造函数 prototype 的所有属性和方法(实例通过设置自己的 __proto__ 指向承构造函数的 prototype 来实现这种继承)。当自身不存在的属性时,就一层层的扒出创建对象的构造函数,直至到 Object 时,就没有proto指向了。

[13] promise async/await 简述使用

  1. promise:then catch finally race 选择最快的 all 并行
  2. promise 三种状态:pending resolved、fullied reject

解决回调地狱

function getPoster(page){
	const promise = new Promise(function(resolve,reject){
	$.getJSON("json/poster.json?page="+page,function(result){
		resolve(result);
	})
	});
	return promise;
}
getPoster(1).then(function(result){//获取第一页
	attachPoster(result);
	return getPoster(2);
}).then(function(result){//获取二页
	attachPoster(result);
	return getPoster(3);
}).then(funciton(result){//获取第三页 ...})

  1. async 函数返回值:一个 Promise
function getSyncTime() {
  return new Promise((resolve, reject) => {
    try {
      let startTime = new Date().getTime();
      setTimeout(() => {
        let endTime = new Date().getTime();
        let data = endTime - startTime;
        resolve(data);
      }, 500);
    } catch (err) {
      reject(err);
    }
  });
}

async function getSyncData() {
  let time = await getSyncTime();
  let data = `endTime - startTime = ${time}`;
  return data;
}

async function getData() {
  let data = await getSyncData();
  console.log(data);
}

getData();
  1. await 后面可以跟任何的 JS 表达式。虽然说 await 可以等很多类型的东西,但是它最主要的意图是用来等待 Promise 对象的状态被 resolved。
  • 如果 await 后面是 promise 对象会造成异步函数停止执行并且等待 promise 的解决,
  • 如果 await 后面是 正常的表达式则立即执行。
function sleep(second) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(" enough sleep~");
    }, second);
  });
}
async function awaitDemo() {
  let result = await sleep(2000);
  console.log("123");
  console.log(result);
}
awaitDemo(); // 两秒之后会被打印出来 '123'和' enough sleep~'
function sleep(second) {
  setTimeout(() => {
    console.log(" enough sleep~");
  }, second);
}
async function awaitDemo() {
  let result = await sleep(2000);
  console.log("123");
}
awaitDemo(); //立即输出123  两秒后输出' enough sleep~'

[14] 改变函数部 this 指针的指向函数(bind ,apply ,call 的区别)

  1. call、apply 和 bind 是 Function 对象 自带的三个方法,都是为了 改变函数体内部 this 指向 。三者第一个参数都是 this 要指向的对象,也就是想指定的上下文;apply 、 call 、bind 三者都可以利用后续参数传参;bind 是返回对应函数,便于调用;apply 、call 则是立即调用 。
  2. 实际应用
Object.prototype.toString.call(XXX); // 查看数据类型,借助对象原型的toString方法
obj.myFun.bind(k1)("上海", "芜湖"); //myFun方法的this改变为k1

[15] 手动实现 bind、call、apply

  • 核心就是“借”,借助函数原型上的 myCall
  • 给当前的借用对象添加到要调用的函数 context.fn = this
  • 借完删除新增的属性
Function.prototype.myCall = function (context) {
  console.log(this); //fn a()
  if (typeof this !== "function") {
    throw new TypeError("Error");
  }
  context = context || window;
  console.log(context); //obj
  context.fn = this;
  console.log(typeof context.fn); //function
  console.log(context.fn); //fn a()
  const args = [...arguments].slice(1);
  console.log(args); //[]
  //通过隐式绑定的方式调用函数
  console.log(1); //1
  const result = context.fn(...args); //333
  console.log(2); //2
  //删除添加的属性
  delete context.fn;
  console.log(3); //3
  console.log(result, context.fn); //undefined  undefined
  //返回函数调用的返回值
  return result;
};

let obj = {
  name: 333,
};

function a() {
  console.log(this.name);
}
a.myCall(obj);

[16] 简单数组去重多种方案

  1. 利用 es6 set 对象,值唯一性
  2. 双层 for 循环
  3. 转成对象,再转回来,利用对象 k 值唯一性
  4. arr.reduce
let arr = [1, 8, 3, 4, 5, 1, 1, 8];
let temarr = [];
arr.reduce(function (total, now) {
  console.log(total, now);
  if (total !== null && !temarr.includes(now)) {
    temarr.push(now);
  }
}, null);
console.log(temarr);
  1. indexOf 循环去重

[17] 数组乱序


[18] 数组降维

  1. arr.flat(),指定要提取嵌套数组的结构深度,默认值为 1,如果多层使用如 arr.flat(Infinity);
  2. [].concat(...[1, 2, 3, [4, 5]]); // [1, 2, 3, 4, 5] 判断有无数组,如果有,则需要继续降维e instanceof Array
  3. 如果没有复杂的格式,如数字/字符串,也可以使用 join&split
function flatten(arr) {
  return arr
    .join(",")
    .split(",")
    .map(function (item) {
      return parseInt(item);
    });
}

let a = [1, 2, 3, [4, 5, [6, 7, 8]]].join(",");
console.log(a); //1,2,3,4,5,6,7,8

[19] 箭头函数和普通函数区别

  1. 箭头函数不能作为构造函数,不能使用 new
  2. 箭头函数没有原型属性
  3. 箭头函数的 this 永远指向其上下文的 this
  4. 箭头函数无 prototype,arguments,super 和 new.target

[20] 手写 promise


[21] 路由原理及实现

  1. hash 路由
  • 创建一个 routes 存储
  • 调用 route 方法注入路由
  • 建立关系this.routes[path] = callback || function() {};
  • 根据 hash 变化,监听事件
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>hash路由demo</title>
  </head>
  <body>
    <ul>
      <li><a href="#/">我是主页</a></li>
      <li><a href="#/a">我是a页面</a></li>
      <li><a href="#/b">我是b页面</a></li>
    </ul>
  </body>
</html>
<script type="text/javascript">
  class HashRouter {
    constructor() {
      // 存储hash与callback键值对
      this.routes = {};
      // 保存当前的hash
      this.currentHash = "";

      // 绑定事件
      // const hashChangeUrl = this.hashChangeUrl.bind(this);
      const hashChangeUrl = this.hashChangeUrl.bind(this);
      // 页面加载事件
      window.addEventListener("load", hashChangeUrl, false);
      // 监听hashchange事件
      window.addEventListener("hashchange", hashChangeUrl, false);
    }
    // path路径和callback函数对应起来,并且使用 上面的this.routes存储起来
    route(path, callback) {
      this.routes[path] = callback || function () {};
    }
    hashChangeUrl() {
      // console.log(this)
      /*
     获取当前的hash值
     location.hash 获取的值为:"#/a, 因此 location.hash.slice(1) = '/a' 这样的
    */
      this.currentHash = location.hash.slice(1) || "/";
      // 执行当前hash对应的callback函数
      this.routes[this.currentHash]();
    }
  }
  // 初始化
  const Router = new HashRouter();
  const body = document.querySelector("body");
  const changeColor = function (color) {
    body.style.backgroundColor = color;
  };
  // 注册函数
  Router.route("/", () => {
    changeColor("red");
  });
  Router.route("/a", () => {
    changeColor("green");
  });
  Router.route("/b", () => {
    changeColor("#CDDC39");
  });
</script>

[22] 数组的方法和应用场景

  1. arr.reduce array.reduce(function(total, currentValue, currentIndex, arr), initialValue)
  2. arr.map() 返回一个由回调函数的返回值组成的新数组。
  3. arr.filter() 将所有在过滤函数中返回 true 的数组元素放进一个新数组中并返回。
  4. arr.some() 如果数组中至少有一个元素满足测试函数,则返回 true,否则返回 false。
  5. arr.every() 如果数组中的每个元素都满足测试函数,则返回 true,否则返回 false。
  6. arr.slice() 方法可从已有的数组中返回选定的元素。 slice([start[,end]])方法它能基于当前数组创建一个新数组,而且对原数组也并不会有任何影响
  7. arr.concat() concat()方法可以简单的将其理解为合并数组。基于当前数组中的所有项创建一个新数组。
  8. arr.flat()数组扁平化和降维
let arr = [4, 1, 3, 5, 7, 9];
let a = arr
  .filter((e) => {
    return e > 3;
  })
  .map((e) => e * 10);
console.log(a); //[40,60,70,90]

[23] 伪数组转为数组

  1. let arr1 =[].slice.call(document.getElementsByClassName("xx"))
  2. for 循环 length
  3. Array.from

[24] 判断数据类型的方案

  1. typeof:能准确的判断基础类型,对复杂类型返回的比较模糊
  2. instanceof (object instanceof constructor):能判断自定义类型 ;instanceof 只能用来判断对象类型,原始类型不可以。并且所有对象类型 instanceof Object都是 true
  3. constructor 检测数据类型法:constructor 无法检测 null 和 undefined 两种类型的数据,会抛出异常;并且 constructor 返回的是构造函数本身,一般使用它来判断类型的情况并不多见。
var arr = [];
// console.log(arr.__proto__==Array.prototype)
// console.log(arr.constructor)
console.log(arr.__proto__.constructor); //demo.html:18 ƒ Array() { [native code] }
  1. Object.prototype.toString.call():不能精准判断自定义对象,对于自定义对象只会返回[object Object]
Object.prototype.toString.call(new Date()); // [object Date]
  • 不使用自身的toString()原因:因为 toString 为 Object 的原型方法,而 Array 、Function 等类型作为 Object 的实例,都重写了 toString 方法。
var arr = [1, 2, 3];
console.log(Array.prototype.hasOwnProperty("toString")); //true
console.log(arr.toString()); //1,2,3
delete Array.prototype.toString; //delete操作符可以删除实例属性
console.log(Array.prototype.hasOwnProperty("toString")); //false
console.log(arr.toString()); //"[object Array]"

[25] 创建一个私有变量,用特定方法访问获取

  1. 闭包
function fun() {
  var a = 10;
  return function getA() {
    return a;
  };
}
let funa = fun();
let usera = funa();
console.log(usera);
  1. 利用 class
class private2 {
  constructor() {
    let a = "class私有";
    this.getName = function () {
      return a;
    };
  }
}
let p = new private2();
console.log(p.a); //undefine
console.log(p.getName()); //class私有
  1. 设置 get set 方法读取和修改
function Person(name){
   this.getName=function(){
       return name;
     };
    this.setName=function(value){
        name=value;
    };
}
var person=new Person("yao");
alert(person.getName());   //"yao"
person.setName("xiyao"));
alert(person.getName());   //"xiyao"
  1. 立即执行函数:所有的实例共享 name,一个修改所有的都会修改
(function () {
  let name = "";
  Person = function (value) {
    // 构造函数,没有用关键字声明,会成为全局构造函数
    name = value;
  };
  Person.prototype.getName = function () {
    // 特权方法 getName 写在构造函数的原型对象上
    return name;
  };
  Person.prototype.setName = function (value) {
    // 特权方法 setName 写在构造函数的原型对象上
    name = value;
  };
})();
let person1 = new Person("Nicholas");
console.log(person1.getName()); // 'Nicholas'
person1.setName("Matt");
console.log(person1.getName()); // 'Matt'
let person2 = new Person("Michael");
console.log(person1.getName()); // 'Michael'
console.log(person2.getName()); // 'Michael'

[26] setTimeout setInterval requestAnimationFrame

  • setTimeout 延时器
  • setInterval 定时器
  • setTimeout setInterval 响应时间不准确,会受代码影响,可能造成卡顿丢帧的现象
  • requestAnimationFrame 浏览器自动去控制,且还可以通过回调函数的参数去调整动画
<div
  id="div"
  style="width:100px; height:100px; background-color:#000; position: absolute;left:0; top:0;"
></div>
<script type="text/javascript">
  let divEle = document.getElementById("div");
  const distance = 1500; // 需要移动的距离
  const timeCount = 3000; // 需要使用的时间
  function handler(time) {
    // time为rAF返回的毫秒级时间单位,当time的大于timeCount的值则停止
    // time理论上是从 1 开始到timeCount定义的3000,
    if (time > timeCount) {
      time = timeCount;
    }
    // 这句代码的作用是 time理论上是从 1 至 3000
    // 当到达3000的时候,time * distance / timeCount得到的一定是distance的值1500
    divEle.style.left = (time * distance) / timeCount;
    window.requestAnimationFrame(handler); // 循环调用,渲染完成会停止
  }
  window.requestAnimationFrame(handler);
</script>

[27] FormData 基本使用以及上传文件

let fd = new FormData();
fd.set("a", "199");
fd.set("a", "200");
fd.append("a", "2020");
fd.append("b", `sss`);
console.log(fd.get("b")); //sss
fd.delete("b");
console.log(fd.get("b")); //null
console.log(fd.has("a")); //true
console.log(fd);
console.log(fd.get("a")); //200
console.log(fd.getAll("a")); //["200", "2020"]
  • FormData 对象可使用 set 设置值,如果同名会覆盖;包括 append 先添加的 key

  • 使用 append 添加则不会覆盖

  • delete 删除 key

  • has 判断存在与否

  • get 查看第一个同名的值

  • getAll 查看同名的值得数组

  • FormData 上传

var file = e.target.files[0];
var fd = new FormData();
fd.append("upload", file, "filename.jpg");
this.$axios.post("/file/uploadHead", fd);

[28] 拖拽和选择 input 上传,获取文件信息

  1. 选择 input=file=> const [file] =e.target.files
  2. 拖拽上传=> const fileList = e.dataTransfer.files

[29] js 错误判断和处理

  • SyntaxError 语法错误
  • ReferenceError 引用错误
  • TypeError 不是预期类型时抛出
  • RangeError 超出范围
for(adsf,asdf)//Uncaught SyntaxError 语法错误

dfasdf//Uncaught ReferenceError 引用错误

let a=3; a()//Uncaught TypeError不是预期类型时抛出
  • 处理利用 try...catch...
  • 一般系统报错,或者框架报错,使用到以上的这些错误
throw new TypeError(222);

[30] js 函数

  1. 函数声明和函数表达式,函数声明可以函数提升
  2. 注意函数的参数 length
    • …args 不算 = 0
    • 有默认值如 a = 1 不算以及其后都不算,只算之前的数量
  3. arguments:函数的参数集合,类数组,它的 length 只和入参有关,后面添加了或者减少了也不会改变 length,不要在里面改形参的值或者 arguments,引发不必要的问题。
  4. arguments.callee:递归时代替函数名,缺点:严格模式不支持
function factorial(num) {
  if (num <= 1) {
    return 1;
  } else {
    return num * arguments.callee(num - 1);
  }
}
let s = factorial(10);
console.log(s); //3628800
  1. caller:这个属性引用的是 调用当前函数的函数 ,如果是在全局作用域中调用则为 null(严格模式用不了)
function outer() {
  inner();
}
function inner() {
  console.log(inner.caller);
}
outer();

// ƒ outer() {
//   inner();
// }

[31] 作用域

function Foo() {
  Foo.a = function () {
    console.log(1);
  };
  this.a = function () {
    console.log(2);
  };
}
Foo.prototype.a = function () {
  console.log(3);
};
Foo.a = function () {
  console.log(4);
};
Foo.a();
let obj = new Foo();
obj.a();
Foo.a();
function Foo() {
  Foo.a = function () {
    console.log(1);
  };
  this.a = function () {
    console.log(2);
  };
}
// 以上只是 Foo 的构建方法,没有产生实例,此刻也没有执行

Foo.prototype.a = function () {
  console.log(3);
};
// 现在在 Foo 上挂载了原型方法 a ,方法输出值为 3

Foo.a = function () {
  console.log(4);
};
// 现在在 Foo 上挂载了直接方法 a ,输出值为 4

Foo.a();
// 立刻执行了 Foo 上的 a 方法,也就是刚刚定义的,所以
// # 输出 4

let obj = new Foo();
/* 这里调用了 Foo 的构建方法。Foo 的构建方法主要做了两件事:
1. 将全局的 Foo 上的直接方法 a 替换为一个输出 1 的方法。
2. 在新对象上挂载直接方法 a ,输出值为 2。
*/

obj.a();
// 因为有直接方法 a ,不需要去访问原型链,所以使用的是构建方法里所定义的 this.a,
// # 输出 2

Foo.a();
// 构建方法里已经替换了全局 Foo 上的 a 方法,所以
// # 输出 1

//故输出的是4 2 1
function Foo() {
  getName = function () {
    console.log(1);
  };
  return this;
}
Foo.getName = function () {
  console.log(2);
};
Foo.prototype.getName = function () {
  console.log(3);
};
var getName = function () {
  console.log(4);
};
function getName() {
  console.log(5);
}
Foo.getName();
getName();
Foo().getName();
getName();
new Foo.getName();
new Foo().getName();
new new Foo().getName();
//2
//4
//1
//1
//2
//3
//3

[32] match,matchAll+replace,replaceAll 使用

  1. match 对正则处理,查找找到一个或多个正则表达式的匹配,注意全局和不全局的数据
  2. matchAll 对正则处理(必须全局正则),返回一个迭代器
  3. replace 可用于对字符串和正则查询,字符串只替换第一个;如果选择正则模式,看是不是全局模式
  4. replaceAll 可用于对字符串和正则查询,字符串替换所有符合的;如果选择正则模式,只能选择全局
  5. replace 和 replaceAll 第二个参数也可以是个函数
str.match(reg);
str.matchAll(reg);

[33] for in 和 for of

  • for...in 循环返回的值都是数据结构的键值名。 遍历对象返回的对象的 key 值; 遍历数组返回的数组的下标(key)。
  • for...of 循环用来获取一对键值对中的值,一个数据结构只要部署了 Symbol.iterator 属性, 就被视为具有 iterator 接口, 就可以使用 for of 循环。

[34] 伪类 after、before 内容获取

  • 部分内容写在伪类 after 和 before 里,可以不太被爬虫获取
  • 解决办法:利用 css 属性获取=>window.getComputedStyle
let k1 = document.getElementsByClassName("k1")[0];
console.log(window.getComputedStyle(k1, ":before").getPropertyValue("content"));
//	console.log(window.getComputedStyle(k1, ':before').content)也可以这样写

[35] 手写 instanceof

function myInstanceof(left, right) {
  let prototype = right.prototype;
  left = left.__proto__;
  while (true) {
    if (left === null || left === undefined) {
      return false;
    }
    if (prototype === left) {
      return true;
    }
    left = left.__proto__;
  }
}

[36] valueOf 和 toSting 使用优先级,加号运算

  • 装箱:把基本数据类型转化为对应的引用数据类型的操作
  • 拆箱:将引用类型对象转换为对应的值类型对象(通过引用类型的 valueOf()或者 toString()方法来实现的)
  • valueOf 和 toString
    • 二者并存的情况下,在数值运算中,优先调用了valueOf,字符串运算中,优先调用了toString。
class A {
  valueOf() {
    return 2;
  }
  toString() {
    return "哈哈哈";
  }
}
let a = new A();

console.log(String(a)); // '哈哈哈'   => (toString)
console.log(Number(a)); // 2         => (valueOf)
console.log(a + "22"); // '222'     => (valueOf)
console.log(a == 2); // true      => (valueOf)
console.log(a === 2); // false     => (严格等于不会触发隐式转换)
class A {
  valueOf() {
    return 2;
  }
}
let a = new A();

console.log(String(a)); // [object Object]==>调用了对象原型的方法toString;可以把Object.protoptype.toString=null测试
console.log(Number(a)); // 2         => (valueOf)
console.log(a + "22"); // '222'     => (valueOf)
console.log(a == 2); // true      => (valueOf)
console.log(a === 2); // false     => (严格等于不会触发隐式转换)

toString

  • undefined 和 null 没有此方法(基本类型肯定没有方法,String、Number 和 Boolean 是因为有对应的基本包装类型,才可以调用方法)
  • Object 类型返回字符串“[object Object]”
  • 函数返回字符串,具体看浏览器自身解析

valueOf

  • undefined 和 null 没有此方法
  • 基本包装类型和对应的基本类型,调用 valueOf()返回对应的基本类型值;
  • 对象类型(除Date类型)返回原对象;
  • Date 类型返回表示日期的毫秒数

如果设置了[Symbol.toPrimitive]属性,则它的优先级高于 toString 和 valueOf

class A {
  constructor(count) {
    this.count = count;
  }
  valueOf() {
    return 2;
  }
  toString() {
    return "哈哈哈";
  }
  // 我在这里
  [Symbol.toPrimitive](hint) {
    if (hint == "number") {
      return 10;
    }
    if (hint == "string") {
      return "Hello Libai";
    }
    return true;
  }
}

const a = new A(10);

console.log(`${a}`); // 'Hello Libai' => (hint == "string")
console.log(String(a)); // 'Hello Libai' => (hint == "string")
console.log(+a); // 10            => (hint == "number")
console.log(a * 20); // 200           => (hint == "number")
console.log(a / 20); // 0.5           => (hint == "number")
console.log(Number(a)); // 10            => (hint == "number")
console.log(a + "22"); // 'true22'      => (hint == "default")
console.log(a == 10); // false        => (hint == "default")
  • 加号运算符
console.log(1 + "1");
// 11
console.log(1 + true);
// 2
console.log(1 + false);
// 1
console.log(1 + undefined);
// NaN
console.log("lucas" + true);
// lucastrue

console.log({} + true);
// [object Object]true

当使用 + 运算符计算 string 和其他类型相加时,都会转换为 string 类型;其他情况,都会转换为 number 类型,但是 undefined 会转换为 NaN,相加结果也是 NaN 如果存在复杂类型,那么复杂类型将会转换为基本类型,再进行运算(结合上面的 valueof 和 tostring 方法)

let a = {
  valueOf() {
    return 1;
  },
};
let b = {};
console.log(a.toString()); //[object Object]
console.log(a.valueOf()); //1
console.log(a + 1); //2
console.log(b + 1); //[object Object]1 =>b的valueof方法仍然是{},无法计算,只能转toString处理

[37] this 指向问题

this 的指向,是在调用函数时根据执行上下文所动态确定的。

具体环节和规则,以下几条规律:

  1. 在函数体中,简单调用该函数时(非显式/隐式绑定下),严格模式下 this 绑定到 undefined,否则绑定到全局对象 window/global;
  2. 一般构造函数 new 调用,绑定到新创建的对象上;
  3. 一般由 call/apply/bind 方法显式调用,绑定到指定参数的对象上;
  4. 一般由上下文对象调用,绑定在该对象上;
  5. 箭头函数中,根据外层上下文绑定的 this 决定 this 指向。
  6. 如果引用了函数,却没调用,而是赋值给变量,那么原有的指向将会发生变化
  • 优先级:
    • 显式绑定 > 隐式绑定 > 默认绑定
    • new 绑定 > 隐式绑定 > 默认绑定

[38] 变量提升

// bar() // bar1
var bar = function () {
  console.log("bar2");
};
function bar() {
  console.log("bar1");
}
bar(); // bar2
foo(10);
function foo(num) {
  console.log(foo, 1);
  foo = num;
  console.log(foo, 2);
  var foo;
}

console.log(foo, 3);
foo = 1;
console.log(foo, 4);

// undefined 1
// 10 2
// ƒ foo(num) {
// 	  console.log(foo,1);
// 	  foo = num;
// 	  console.log(foo,2);
// 	  var foo;
// 	} 3
// 1 4
getName();
var getName = function () {
  console.log(1);
};
getName();
function getName() {
  console.log(2);
}
getName();
//2
//1 第二个打印被 var getName接管
//1

同一个标识符的情况下,变量声明与函数声明都会提升;函数声明会覆盖变量声明,但不会覆盖变量赋值,即:如果声明变量的同时初始化或赋值,那么在这行之后的代码,都可以执行变量的操作。(一家之言)


[39] a==1&&a==2&&a==3

//重写toString
let a = {
  x: 0,
  toString() {
    console.log(`执行${this.x}`);
    return ++this.x;
  },
};

//数据劫持
var i = 0;
Object.defineProperty(window, "a", {
  get() {
    return ++i;
  },
});

//数组
var a = [1, 2, 3];
a.toString = a.shift;

if (a == 1 && a == 2 && a == 3) {
  console.log("成功");
}

[40] async、await 使用

  • async函数返回的是一个包装的promise对象,如果是普通对象也会被包装
  • await 后的代码不一定比 await 后执行,如果后面跟正常的表达式
function sleep(second) {
  setTimeout(() => {
    console.log(" enough sleep~");
  }, second);
}
async function awaitDemo() {
  let result = await sleep(2000);
  console.log("123");
}
awaitDemo(); //立即输出123  两秒后输出' enough sleep~'
  • 如果 await 后面是 promise 对象会造成异步函数停止执行并且等待 promise 的解决
function sleep(second) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log("action");
      resolve(" enough sleep~");
    }, second);
  });
}
async function awaitDemo() {
  let result = await sleep(2000);
  console.log("123");
  console.log(result);
}
awaitDemo(); // 两秒之后会被打印出来action  '123'和' enough sleep~'
  • await 代码前后微任务处理
function sleep(second) {
  console.log(111);
  setTimeout(() => {
    console.log(" enough sleep~");
  }, second);
}
async function awaitDemo() {
  console.log(222);
  let result = await sleep(2000);
  console.log(333);
}
awaitDemo();

console.log(444);
//222
//111
//444
//333
//enough sleep~

[41] Object 的freeze和seal使用

  • Object.freeze() 方法可以冻结一个对象。一个被冻结的对象再也不能被修改。此外,冻结一个对象后该对象的原型也不能被修改。 但是默认不能冻结深层次对象。
  • Object.seal() 对象封存,防止新属性添加或者存在出现被移除,原先的对象还是可以修改的。

[42] ['1', '2', '3'].map(parseInt) what & why ?

答案:[1,NaN, NaN]

let arr = [1,2,3]
function fun(a,b,c){
	console.log(a,b,c)
}

arr.forEach(fun)
//1 0 (3) [1, 2, 3]
//2 1 (3) [1, 2, 3]
//3 2 (3) [1, 2, 3]

map 函数的第一个参数 callback。这个 callback 一共可以接收三个参数,其中第一个参数代表当前被处理的元素,而第二个参数代表该元素的索引。

arr.map(callback: (value: T, index: number, array: T[]) => U, thisArg?:any);

而 parseInt 则是用来解析字符串的,使字符串成为指定基数的整数。接收两个参数,第一个表示被处理的值(字符串),第二个表示为解析时的基数。

parseInt(string, radix)

[43] 手写防抖和节流

  • 防抖——触发高频事件后 n 秒后函数只会执行一次,如果 n 秒内高频事件再次被触发,则重新计算时间
function debounce(timer=1000){
	let vtimer
	return function (){
		clearTimeout(vtimer)
		vtimer = setTimeout(()=>{
			console.log('触发')
		},timer)
	} 
	
}
let myfun = debounce()
window.addEventListener('scroll', myfun);
  • 节流——高频事件触发,但在 n 秒内只会执行一次,所以节流会稀释函数的执行频率
function throttle(fn) {
	let canRun = true // 通过闭包保存一个标记
	return function() {
		if (!canRun) return
		// 在函数开头判断标记是否为 true,不为 true 则 return
		canRun = false // 立即设置为 false
		setTimeout(() => {
			// 将外部传入的函数的执行放在 setTimeout 中
			fn.apply(this, arguments)
			// 最后在 setTimeout 执行完毕后再把标记设置为 true(关键) 表示可以执行下一次循环了。当定时器没有执行的时候标记永远是 false,在开头被 return 掉
			canRun = true
		}, 500)
	}
}

function sayHi(e) {
	console.log('every')
}
window.addEventListener('scroll',throttle(sayHi))

[44] set,weakSet,map,weakMap使用和区别

Set 和 Map 主要的应用场景在于 数据重组 和 数据储存

  • Set 是一种叫做集合的数据结构
add(value):新增
delete(value):存在即删除集合中value
has(value):判断集合中是否存在 value
clear():清空集合
size:个数
  • WeakSet 对象允许将弱引用对象储存在一个集合中

WeakSet 与 Set 的区别

  • WeakSet 只能储存对象引用,不能存放值,而 Set 对象都可以,set存储的对象,如arr,即使将arr=null,依旧在set集合中存在且保持原先存在的值!!!而Weakset则会移除该数组,同时如果直接在weakset中设置了一个数组,因为根本无法在找到,也会在稍后被移除(可以声明,然后add变量名)
  • [如果同时有set和weakset都引用了某个值,然后arr=null,set和weakset都会存在这个值,但注意:xxx.has(arr)两者均为false;如果set删除了该选项,则weakset中的也会删除]
  • WeakSet 对象中储存的对象值都是被弱引用的,即垃圾回收机制不考虑 WeakSet 对该对象的应用,如果没有其他的变量或属性引用这个对象值,则这个对象将会被垃圾回收掉(不考虑该对象还存在于 WeakSet 中),所以,WeakSet 对象里有多少个成员元素,取决于垃圾回收机制有没有运行 ,运行前后成员个数可能不一致,遍历结束之后,有的成员可能取不到了(被垃圾回收了),WeakSet 对象是无法被遍历的,也没有办法拿到它包含的所有元素 [故而用不了size+遍历以及clear等方法,也只能存引用类型]
  • Map 是一种叫做字典的数据结构:任何具有 Iterator 接口、且每个成员都是一个双元素的数组的数据结构都可以当作Map构造函数的参数
set(key, value):向字典中添加新元素
get(key):通过键查找特定的数值并返回
has(key):判断字典中是否存在键key
delete(key):通过键 key 从字典中移除对应的数据
clear():将这个字典中的所有元素删除
size:返回字典中所包含的元素个数
  • WeakMap
+ 只接受对象作为键名(null除外),不接受其他类型的值作为键名
+ 键名是弱引用,键值可以是任意的,键名所指向的对象可以被垃圾回收,此时键名是无效的
+ 不能遍历,方法有get、set、has、delete
+ 同weakset和set的关系一致,weakmap和map之间也在删除和引用到同一个数据时的关联问题

[45] Object.assign 和 Object.create 使用和注意

  • Object.assign: 合并对象,后面的会覆盖前面的,且不会复制原型上的内容。
  • Object.create: 创建一个新对象,第一个参数是放在原型上的数据,第二个参数按Object.defineProperty规则实施
  • Object.defineProperty:在调用时, configurable 、 enumerable 和 writable 的值如果不指定,则都默认为 false 。

[46] 对象的遍历


# [axios]

[1] axios 基本使用

  1. axios.create 创建
  2. 利用 axios 响应拦截器和请求拦截器对请求进行封装
import axios from "axios";
import { Message } from "element-ui";
import { getToken } from "@/utils/auth";
import store from "@/store";

axios.defaults.headers["Content-Type"] = "application/json;charset=utf-8";
// 创建axios实例
const service = axios.create({
  // axios中请求配置有baseURL选项,表示请求URL公共部分
  // baseURL: '/ct-admin',
  baseURL: "/billing",
  // 超时
  timeout: 1200000,
});
// request拦截器
service.interceptors.request.use(
  (config) => {
    // 是否需要设置 token
    const isToken = (config.headers || {}).isToken === false;
    if (getToken() && !isToken) {
      config.headers["token"] = "Bearer " + getToken(); // 让每个请求携带自定义token 请根据实际情况自行修改
    }
    return config;
  },
  (error) => {
    Promise.reject(error);
  }
);

// 响应拦截器
service.interceptors.response.use(
  (res) => {
    // 获取错误信息
    if (res.data.status !== "OK") {
      Message({
        message: res.data.message,
        type: "error",
        duration: 5 * 1000,
      });
      if (res.data.code === "99999") {
        store.dispatch("LogOut").then(() => {
          location.reload();
        });
      }
      if (res.data.code === "401") {
        store.dispatch("LogOut").then(() => {
          location.reload();
        });
      }
      return Promise.reject(res.data.message);
    } else {
      return res.data;
    }
  },
  (error) => {
    let { message } = error;
    if (message === "Network Error") {
      message = "网络异常";
    } else if (message.includes("timeout")) {
      message = "系统接口请求超时";
    } else if (message.includes("Request failed with status code")) {
      message = "系统接口" + message.substr(message.length - 3) + "异常";
    }
    Message({
      message,
      type: "error",
      duration: 5 * 1000,
    });
    return Promise.reject(error);
  }
);

export default service;
  1. 并发请求利用 axios.all 和 axios.spread
this.$axios
  .all([
    that.$axios.post("/org/findUser1", postData),
    that.$axios.post("/org/findUser2", postData),
  ])
  .then(
    that.$axios.spread(function (userResp1, serResp2) {
      console.log(userResp);
    })
  );
  1. axios 取消,使用 axios.CancelToken 取消请求
export function pjinvoicedDetailTotal(data, _this) {
  return request({
    url: '/expenseReport/billReport/invoicedDetailTotal',
    method: 'post',
    data,
    cancelToken: new axios.CancelToken((c) => {
      _this.cancelFun = c
    })
  })
}

cancelRequest() {
  if (typeof this.cancelFun === 'function') {
	this.cancelFun()
  }
},
this.cancelRequest()
areaDetailTotal(data, this).then(res => {
	// console.log(res)
	this.totalData1 = res.data
 })

# [canvas]

[1] 压缩图片,转化格式。 参考链接 (opens new window)

canvas 转图片,并且压缩,然后填充到新的 img 标签中展示预览。

  1. 创建一个 FileReader 的实例 imgFile
  2. 使用 imgFile.readAsDataURL 读取 input 选中的图片 document.getElementById('inputimg').files[0]
  3. imgFile.onload =function(e)===>e.target.result,图片的 base64 格式数据
  4. 创建一个 image,用 image.src 接收 e.target.result,等 image.onload 之后,调用创建 canvas 的方法
  5. 创建一个 canvas,设置宽高,调用 canvas.getContext("2d").drawImage(image, 0, 0) 绘制 canvas
  6. 获取页面 image 元素
  7. 利用 canvas.toDataURL(type,压缩率) ;将压缩和转换格式后的 url 返回,type 选择图片的类型,默认 png. toDataURL
  8. 对应的节点调用 setAttribute 设置 src(预览转换后的图片)
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <input type="file" id="inputimg" />
    <select id="myselect">
      <option value="1">jpeg格式</option>
      <option value="2">webp格式</option>
      <option value="3">png格式</option>
    </select>
    <button id="start">开始转换</button>
    <p>预览:</p>
    <img id="imgShow" src="" alt="" />
  </body>

  <script>
    /*事件*/
    document.getElementById("start").addEventListener("click", function () {
      getImg(function (image) {
        let can = imgToCanvas(image);
        imgshow = document.getElementById("imgShow");
        imgshow.setAttribute("src", canvasToImg(can));
      });
    });
    // 把image 转换为 canvas对象
    function imgToCanvas(image) {
      let canvas = document.createElement("canvas");
      canvas.width = image.width;
      canvas.height = image.height;
      canvas.getContext("2d").drawImage(image, 0, 0);
      return canvas;
    }
    //canvas转换为image
    function canvasToImg(canvas) {
      let array = ["image/jpeg", "image/webp", "image/png"];
      type = document.getElementById("myselect").value - 1;
      console.log(canvas);
      let src = canvas.toDataURL(array[type]);
      // canvas.toDataURL('image/jpeg', 0.9); // 压缩图片,第二个参数控制图片压缩效率
      // console.log(src);
      return src;
    }
    //获取图片信息
    function getImg(fn) {
      let imgFile = new FileReader();
      try {
        // 使用FileReader来把文件读入内存,并且读取文件中的数据。 readAsDataURL方法可以在浏览器主线程中异步访问文件系统,
        //读取文件中的数据,且读取后 result 为 DataURL, DataURL 可直接 赋值给 img.src
        imgFile.readAsDataURL(document.getElementById("inputimg").files[0]);
        imgFile.onload = function (e) {
          let image = new Image();
          image.src = e.target.result; //base64数据
          image.onload = function () {
            fn(image);
          };
        };
      } catch (e) {
        console.log("请上传图片!" + e);
      }
    }
  </script>
</html>

# [css]

[1] 清除浮动。

  1. overflow:hidden/auto
  2. 给父级设置高度
  3. 空 div 设置 clear:both
  4. after 伪类法(万能清除法)
#a {
  border: 1px solid red;
  background: orange;
  zoom: 1; //兼容低版本浏览器
}
#a:after {
  display: block;
  clear: both;
  content: "";
  visibility: hidden;
  overflow: hidden;
  height: 0;
}

[2] BFC 是什么,及其作用。

  1. 在页面中元素都有一个隐含的属性叫作 Block Formatting Context,即块级格式化上下文,简称 BFC。该属性能够设置打开或关闭,默认是关闭的。页面上的一个隔离渲染区域,容器里面的子元素不会在布局上影响到外面的元素

  2. BFC 作用 :多栏布局,清除浮动,上下margin重叠

  3. 普通文档流布局规则

    • 浮动的元素是不会被父级计算高度
    • 非浮动元素会覆盖浮动元素的位置(浮动 div 会被非浮动的 div 围绕)
    • margin 会传递给父级
    • 两个相邻元素上下 margin 会重叠
  4. BFC 布局规则

    • 浮动的元素会被父级计算高度(父级触发了 BFC)
    • 非浮动元素不会覆盖浮动元素位置(非浮动元素触发了 BFC)
    • margin 不会传递给父级(父级触发了 BFC)
    • 两个相邻元素上下margin会重叠(给其中一个元素增加一个父级,然后让他的父级触发)【margin重叠三个条件:同属于一个BFC;相邻;块级元素】
  5. 产生方式

    • float 不为 none
    • overflow 不为 visible
    • position 不为 relative 和 static
    • display 为 table-cell table-caption inline-block 之一
    • 根元素
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title></title>
    <style>
      * {
        margin: 0;
        padding: 0;
      }
      .k1 {
        width: 100px;
        height: 100px;
        border: 1px solid #00d6b2;
        float: left;
      }
      .k2 {
        width: 200px;
        height: 200px;
        background-color: bisque;
        /* overflow: hidden; */
        /*解开注释就是创建BFC,就不会围绕k1模块*/
      }
    </style>
  </head>
  <body>
    <div class="k1"></div>
    <div class="k2">
      abc def weq1 abcdef weq1 abc def weq1 abcdef weq1 abc def weq1 abcdef weq1
      abc def weq1 abcdef weq1 abc def weq1 abcdef weq1 abc def weq1 abcdef weq1
      abc def weq1 abcdef weq1 abc def weq1 abcdef weq1 abc def weq1 abcdef weq1
      abc def weq1 abcdef weq1
    </div>
  </body>
</html>
overflow: hidden;

如果 k2 的设置 overflow:hidden,则不会再 k1 周围环绕


[3] div 水平垂直居中

  1. 定位
  2. margin-top/left 减去 div 宽高
  3. margin-top/left+transform:translate(-50%,-50%)
  4. 弹性布局:父级设置主轴侧轴居中
#k1 {
  display: flex;
  justify-content: center;
  align-items: center;
}
#k1 div {
  width: 100px;
  height: 100px;
}
  1. 弹性布局+margin:auto==>前提在 flex 布局下,设置 div 的 margin 为 auto 时,会水平垂直居中(可以理解为子元素被四周的 margin “挤” 到了中间。)
div.parent {
  display: flex;
}
div.child {
  margin: auto;
}
  1. table-cell:与 flex 一样,需要写在父级元素上。[text-align+vertical-align]
<div class="wrapper">
  <p>horizontal and vertical</p>
</div>
<style>
  .wrapper {
    width: 300px;
    height: 300px;
    border: 1px solid #ccc;
    display: table-cell;
    text-align: center;
    vertical-align: middle;
  }
</style>
  1. grid 布局:写法与 flex 不同。[align-self+justify-self 写在子元素上]
<div class="wrapper">
  <p>horizontal and vertical</p>
</div>
.wrapper { width: 300px; height: 300px; border: 1px solid #ccc; display: grid; }
.wrapper > p { align-self: center; justify-self: center; }
  1. after 伪类:垂直方向,可以理解为 ::after 把 p 内容 往下拉到了中间。
<div class="wrapper">
  <p>1234</p>
</div>
<style type="text/css">
  .wrapper {
    width: 300px;
    height: 300px;
    border: 1px solid #ccc;
    text-align: center;
  }

  .wrapper::after {
    content: "";
    display: inline-block;
    vertical-align: middle;
    height: 100%;
  }

  .wrapper > p {
    vertical-align: middle;
    display: inline-block;
  }
</style>
  1. writing-mode 利用这个属性去实现

[4] 盒模型

  1. 盒子模型有两种,ie 盒模型和标准盒模型
  2. content-box 标准盒模型 width 不包括 padding 和 border
  3. border-box 怪异盒模型 width 包括 padding 和 border
box-sizing: content-box|border-box|inherit;

[5] 让浏览器支持 12px 以下的字体(画一条 0.5px 的线)

借助 css3 的 scale


[6] 简述弹性盒子布局属性有哪些

  1. flex-direction:弹性容器中子元素排列方式(主轴排列方式)
  2. flex-wrap:设置弹性盒子的子元素超出父容器时是否换行
  3. flex-flow:是 flex-direction 和 flex-wrap 简写形式
  4. align-item:设置弹性盒子元素在侧轴上的对齐方式
  5. align-content:设置行对齐
  6. justify-content:设置弹性盒子元素在主轴上的对齐方式

[7] 三栏布局方案

  1. flex/grid 布局
  2. 结合定位布局
  3. 圣杯布局
<style>
  * {
    margin: 0;
    padding: 0;
  }
  #container {
    padding-left: 200px;
    padding-right: 150px;
  }
  #container .column {
    float: left;
  }

  #center {
    width: 100%;
    background: greenyellow;
  }

  #left {
    width: 200px;
    margin-left: -100%;
    position: relative;
    right: 200px;
    background: skyblue;
  }
  #right {
    width: 150px;
    margin-right: -150px;
    background: mediumvioletred;
  }

  #footer {
    clear: both;
  }
</style>
<div id="header">header</div>
<div id="container">
  <!-- center位于left和right前面 -->
  <div id="center" class="column">1</div>
  <!-- left和right因为被center浮动独占一行,只能在下方 -->
  <!-- 通过更改位置,修改margin-left和left/right调整位置 -->
  <div id="left" class="column">2</div>
  <div id="right" class="column">3</div>
</div>
<div id="footer">footer</div>
  1. 双飞翼布局

相对圣杯布局,给 center 加一个子元素 inner。给 inner 设置 左 margin 和 右 margin,将 inner 挤到中间显示。

<style type="text/css">
  body {
    min-width: 500px;
  }

  #container {
    width: 100%;
  }

  .column {
    float: left;
  }

  #center {
    margin-left: 200px;
    margin-right: 150px;
    background: green;
  }

  #left {
    width: 200px;
    margin-left: -100%;
    background: #00d6b2;
  }

  #right {
    width: 150px;
    margin-left: -150px;
    background: indianred;
  }

  #footer {
    clear: both;
  }
</style>
<body>
  <div id="header"></div>
  <div id="container" class="column">
    <div id="center">1</div>
  </div>
  <div id="left" class="column">2</div>
  <div id="right" class="column">3</div>
  <div id="footer"></div>
  <body></body>
</body>

[8] 粘连布局

  1. flex 布局实现
  2. min-height+上面元素 padding-botoom,底部元素 margin-top
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      * {
        margin: 0;
        padding: 0;
      }
      html,
      body {
        height: 100%;
      }
      #main {
        min-height: 100%;
        background-color: orange;
      }
      .main {
        padding-bottom: 100px;
      }
      #footer {
        height: 100px;
        background: pink;
        margin-top: -100px;
      }
    </style>
  </head>
  <body>
    <div id="main">
      <div class="main">
        main<br />
        main<br />
        main<br />
        main<br />
        main<br />
        main<br />
      </div>
    </div>
    <div id="footer">footer</div>
  </body>
</html>

[9] 画一个三角形方案

  1. boder 设置宽度 背景透明,width=0;选取一个边设置颜色如 border-top-color:red
  2. 借助 canvas 或 svg 绘制

[10] 元素页面隐藏方案优缺点

  • 结构: display:none: 会让元素完全从渲染树中消失,渲染的时候不占据任何空间, 不能点击, visibility: hidden:不会让元素从渲染树消失,渲染元素继续占据空间,只是内容不可见,不能点击 opacity: 0: 不会让元素从渲染树消失,渲染元素继续占据空间,只是内容不可见,可以点击
  • 继承: display: none 和 opacity: 0:是非继承属性,子孙节点消失由于元素从渲染树消失造成,通过修改子孙节点属性无法显示。 visibility: hidden:是继承属性,子孙节点消失由于继承了 hidden,通过设置 visibility: visible;可以让子孙节点显式。
  • 性能: display : none 修改元素会造成文档回流,读屏器不会读取 display: none 元素内容,性能消耗较大 visibility:hidden: 修改元素只会造成本元素的重绘,性能消耗较少读屏器读取 visibility: hidden 元素内容 opacity: 0 : 修改元素会造成重绘,性能消耗较少
  • 联系:它们都能让元素不可见

[11] link 和 @import 区别

区别 1:link 是 XHTML 标签,除了加载 CSS 外,还可以定义 RSS,定义 rel 连接属性等其他事务;@import 属于 CSS 范畴,只能加载 CSS。

区别 2:link 引用 CSS 时,在页面载入时同时加载;@import 需要页面网页完全载入以后加载。

区别 3:link 是 XHTML 标签,无兼容问题;@import 是在 CSS2.1 提出的,低版本的浏览器不支持。

区别 4:link 支持使用 Javascript 控制 DOM 去改变样式;而@import 不支持。


[12] 避免文档流空格合并

空白符合并,可通过设置 white-space 来解决

  • pre:不合并,不换行
  • pre-wrap:不合并,可换行
  • pre-line:合并,渲染换行符,可换行
  • nowrap:合并,不渲染换行符,不自动换行
  • normal:合并,不渲染换行符,自动换行

# [html]

[1] 浏览器的标准模式和怪异模式区别

  1. 标准模式:浏览器按照 W3C 标准解析执行代码
  2. 怪异模式:浏览器根据自己的方式解析执行代码,因为不同浏览器解析,执行方式不一样,所以叫怪异模式
  3. 页面添加了<!DOCTYPE html>(注意:大小写不敏感),那么就等同于开启了标准模式
  4. 使用document.compatMode可查看模式,如果文档处于“混杂模式”,则该属性值为"BackCompat",如果文档处于“标准模式”,则该属性为"CSS1Compat"

[2] html5 与之前有哪些不同

  1. 代码层面:语义化标签
  2. 提升用户交互输入:提供各种类型的输入选择器,输入框新属性,定位
  3. 提升页面渲染输出:音频,视频,SVG,canvas,embed
  4. 增强浏览器性能和功能:websocket,webworker,stroage
  5. 拖放 drag

[3] SPA 和 MPA 的区别

- 单页面应用(SPA) 多页面应用(MPA)
组成 一个主页面和多个页面片段 多个主页面
刷新方式 局部刷新 整页刷新
SEO 搜索引擎优化 难实现,可使用 SSR 方式改善 容易实现
数据传递 容易 通过 url、cookie、localStorage 等传递
页面切换 速度快,用户体验良好(首次渲染速度相对较慢) 切换加载资源,速度慢,用户体验差
维护成本 相对容易 相对复杂

[4] WebSocket 的实现和应用

  • WebSocket 是 HTML5 中的协议,支持持久连续,http 协议不支持持久性连接。Http1.0 和 HTTP1.1 都不支持持久性的链接,HTTP1.1 中的 keep-alive,将多个 http 请求合并为 1 个.
  • WebSocket 是基于 Http 协议的,或者说借用了 Http 协议来完成一部分握手,在握手阶段与 Http 是相同的
  • 应用:聊天室,消息推送

[5] 简述 web worker

  • 在 HTML 页面中,如果在执行脚本时,页面的状态是不可相应的,直到脚本执行完成后,页面才变成可相应。web worker 是运行在后台的 js,独立于其他脚本,不会影响页面你的性能。并且通过 postMessage 将结果回传到主线程。这样在进行复杂操作的时候,就不会阻塞主线程了。
  • 如何创建 web worker:
    • 检测浏览器对于 web worker 的支持性
    • 创建 web worker 文件(js,回传函数等)
    • 创建 web worker 对象

[6] cookie localstorage sessionstorage

  1. cookie 前后端都可以设置,后端加 httponly,前端就不可操作,体积有限
  2. localstorage 永久存储,除非手动清除,存储的是字符串,如果是存对象,需要提前 JSON.stringify,使用再转回 JSON.parse
  3. sessionstorage 页面会话在浏览器打开期间一直保持,并且重新加载或恢复页面仍会保持原来的页面会话。关闭页面清除

[7] iframe 优缺点

  • 可以解决加载缓慢的第三方内容:广告/图标等
  • 实现安全沙箱
  • 可以并行加载脚本
  • 增加代码的重用性
  • iframe 会阻塞主页面的 onLoad 事件
  • iframe 即使为空,加载也需要时间
  • iframe 无语义

[8] 属性 property 和 attribute

  • Attribute:HTML属性,书写在标签内的属性,使用 setAttribute()和 getAttribute()进行设置和获取。
  • Property:DOM属性,html 标签对应的 DOM 节点属性,使用 .属性名 或者 ['属性名']进行设置和获取。
  • 简单理解,Attribute 就是 dom 节点自带的属性,例如 html 中常用的 id、class、title、align 等;而 Property 是这个 DOM 元素作为对象,其附加的内容,例如 childNodes、firstChild 等。
  • 另外,常用的 Attribute,例如 id、class 等,已经被作为 Property 附加到 DOM 对象上,可以和 Property 一样取值和赋值。但是自定义的Attribute,就不会有这样的优待。
  • 不管是修改 attribute 还是 property 都会影响到对方的属性值
  • value 的 Attriubte 和 property 没有映射关系
  • 自定义的属性,在 property 上是不存在的,修改或者获取都只能通过 setAttribute()或 getAttribute()

# [web]

[1] web 性能优化

  1. 代码层优化
    • 尽量减少代码,复用,组件化开发
    • css 选择器从右到左,不要写的复杂和多
    • 代码压缩,减小体积(gzip)
    • 代码加载 css+html>js
    • 按需加载,不全量加载
    • 如果不是需求必须,颜色和图形最好越简单越好
    • tree-shaking
    • 减少重排重绘,利用 fragment+css3transform+display:none
    • 出现两次以上的变量缓存,如 arr.length(减少复杂度),box.offsetWidth(防止重复刷新队列,避免触发同步布局事件)
    • window.requestAnimationFrame
    • fastdom
    • call/apply/bind 借用代码
  2. 减少请求
    • 合并请求,减少请求次数:合并文件,精灵图,用 css3/svg/canvas 代替部分图片的展示
    • 图片懒加载,组件懒加载(vue 配置 webpack)
    • 需要的时候预加载,更好的提供体验
    • 防抖动(搜索时等用户完整输入内容后再发送查询请求,文本编辑器实时保存)
    • 节流(定期发送,浏览器播放事件;scroll 事件,每隔一秒计算一次位置信息等)
    • 服务端渲染
    • 小图片转 base64
  3. 缓存
    • 前端缓存:localstroage/websql/indexdb/cookie(每次请求占体积)
    • IE6,userData 用于本地存储,考虑到兼容性,更通用的是使用 Flash
    • 304 协商缓存
    • 200 强缓存,直接读取,不会真的像后台发请求
    • 优先级:Service Worker>Memory Cache(短期贮存)>Disk Cache(存储在硬盘)>网络请求
  4. 框架自带
    • vue 虚拟 dom 加 key 值减少比对
    • vuecomputed 的缓存
    • keep-alive
  5. 其它
    • CDN
    • 去除 console.log
    • 去除 sourcemap

[2] 浏览器网页查看/F5/ctrl+F5

  1. 打开网页,地址栏输入地址: 查找 disk cache 中是否有匹配。如有则使用;如没有则发送网络请求。
  2. 普通刷新 (F5):因为 TAB 并没有关闭,因此 memory cache 是可用的,会被优先使用(如果匹配的话)。其次才是 disk cache。
  3. 强制刷新 (Ctrl + F5):浏览器不使用缓存,因此发送的请求头部均带有 Cache-control: no-cache(为了兼容,还带了 Pragma: no-cache)。服务器直接返回 200 和最新内容。

[3] 浏览器输入地址到显示页面

  1. URL 解析+检查缓存
  2. DNS 查询
  3. TCP 连接
  4. 处理请求
  5. 接受响应
  6. 渲染页面(css+html=>renderTree;jstree)

[4] 垃圾回收机制

  1. 标记清除:当变量进入执行环境是,就标记这个变量为“进入环境”。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为只要执行流进入相应的环境,就可能会用到他们。当变量离开环境时,则将其标记为“离开环境”。
  2. 引用计数: 当声明了一个变量,并用一个引用类型的值赋值给改变量,则这个值的引用次数为 1,;相反的,如果包含了对这个值引用的变量又取得了另外一个值,则原先的引用值引用次数就减 1,当这个值的引用次数为 0 的时候,说明没有办法再访问这个值了,因此就把所占的内存给回收进来,这样垃圾收集器再次运行的时候,就会释放引用次数为 0 的这些值。
  • 垃圾回收的使用场景优化:
    • 公共变量 for 循环能提取在外面就不要放在循环里面计算
    • 要释放的变量对象 = null
    • arr.length =0 取代 arr = []
// 在循环中最好也别使用函数表达式。
for (var k = 0; k < 10; k++) {
  var t = function (a) {
    // 创建了10次  函数对象。
    console.log(a);
  };
  t(k);
}
// 推荐用法
function t(a) {
  console.log(a);
}
for (var k = 0; k < 10; k++) {
  t(k);
}
t = null;

[5] 跨域

  • 同源策略/SOP(Same origin policy)是一种约定,是浏览器最核心基本的安全功能,缺少了同源策略,很容易受到 XSS、CSFR 等攻击。所谓同源是指 "协议+域名+端口" 三者相同。
  • 线上跨域方案
    1. 通过 jsonp 跨域(only get)
    2. 跨域资源共享(CORS)
    3. nginx 代理跨域
    4. WebSocket 协议跨域
    5. H5 新增 apipostMessage 跨域
  • 本地联调
    1. webpack 配置 devserver
module.exports = {
  devServer: {
    host: "0.0.0.0",
    open: true,
    proxy: {
      "/billing": {
        target: `https://mbilling-test.jphl.com/billing`,
        changeOrigin: true,
        pathRewrite: {
          "^/billing": "",
        },
        timeout: 1200000,
      },
    },
    disableHostCheck: true,
  },
};

[6] web 前端提升安全的措施

  1. XSS 【Cross Site Script】跨站脚本攻击;
    • 过滤关键字:script javascript 等
    • 限制输入,规定输入的只能是某些格式的数据;转义大于号小于号等
    • CSP: Content-Security-Policy 内容安全策略(白名单制度);<meta http-equiv="Content-Security-Policy" content="form-action 'self';default-src 'self';">
    • 为了减轻这些攻击,需要在 HTTP 头部配上,set-cookie:
      • httponly-这个属性可以防止 XSS,它会禁止 javascript 脚本来访问 cookie。
      • secure - 这个属性告诉浏览器仅在请求为 https 的时候发送 cookie。
  2. CSRF【Cross Site Request Forgery】站点伪造请求
    • 在请求地址中添加 token 并验证
    • 在 HTTP 头中自定义属性并验证
    • 验证 HTTP Referer 字段 ( req.headers.referer )
    • Get 请求不对数据进行修改
    • 不让第三方网站访问到用户 Cookie
  3. 文件上传
    • 限制大小
    • 检查格式

# [算法]

[1] 数组使用冒泡排序从大到小和优化性能

  1. 两个 for 循环,外层是循环次数为 arr.length-1,因为最后自己不用跟自己比
  2. 里面的 for 循环,循环次数 j < len - i - 1;每次外层循环完后,就排除最后一位
  3. 考虑优化,如果发现某一次没有发现任何位置交换,就可以结束外层 for 循环提高性能
// 初始未产生交换
let isSwap = false;
for (let i = 0; i < len - 1; i++) {
  // 每次i变化之后最大的值已经排序到最后一位,无需对最后一位进行比较,所以j的最大值为len-i-1
  for (let j = 0; j < len - i - 1; j++) {
    num1 += 1;
    // 如果当前位置的数比下一位置的数大,则交换位置
    if (arr[j] > arr[j + 1]) {
      num2 = +1;
      isSwap = true;
      flag = arr[j];
      arr[j] = arr[j + 1];
      arr[j + 1] = flag;
    }
  }
  // 如果没有产生任何交换,则可以直接结束循环
  if (!isSwap) {
    return;
  } else {
    // 如果之前已经产生了交换,那么下次循环重新设置isSwap
    // 因为加入仅仅有一个数位置不对,没有这里他之后isSwap一直都是true
    // 就再也不能处理位置不变的情况了
    isSwap = false;
  }
}

[2] 数组选择排序处理

  1. 将序列分为未排序和已排序,从未排序序列中找到最小的数,放到无序序列起始位置,然后继续从剩余未排序序列中继续寻找最小值
  2. 最外层循环 arr.length-1 次
  3. 里层每次都是 let j = i + 1,因为每次都是左边多一位不要去处理的小数
  4. 通过比对,找到最小值的下标,进行当前外层的 i 交换
// 选择出无序序列中最小的值放到无序第一位
let arr = [3, 45, 16, 8, 65, 15, 36, 22, 19, 1, 96, 12, 56, 12, 45];
let len = arr.length;
let minIndex;
let flag;
let num1 = 0; // 比较次数
let num2 = 0; // 交换次数
for (let i = 0; i < len - 1; i++) {
  // 每次选择最小值之后,无序区的开始位置往后推1
  minIndex = i;
  // j循环到最后一位,选择出当前无序数组中数值最小的索引值
  for (let j = i + 1; j < len; j++) {
    num1 += 1;
    if (arr[minIndex] > arr[j]) {
      minIndex = j;
    }
  }
  num2 += 1;
  flag = arr[i];
  arr[i] = arr[minIndex];
  arr[minIndex] = flag;
}
console.log(arr, num1, num2);
//[1, 3, 8, 12, 12, 15, 16, 19, 22, 36, 45, 45, 56, 65, 96] 105 14

[3] 插入排序

  1. 记住当前的值
  2. 比较上一位和当前值大小,如果当前值较小,改变当前值索引
  3. 如果当前值大于前一位,直接结束内层循环
  4. 把记住的值赋给索引遍历位置+1
let arr = [3, 45, 16, 8, 65, 15, 36, 22, 19, 1, 96, 12, 56, 12, 45];
let len = arr.length;
// 定义当前未排序数据起始位置值也就是即将插入的数据
let currentValue;
// 有序序列遍历位置
let preIndex;
let num1 = 0; // 比较次数
let num2 = 0; // 交换次数
for (let i = 1; i < len; i++) {
  // 定义原始数据第二位为未排序数据第一位,默认原始数据第一位已排序
  currentValue = arr[i];
  // 当前有序序列最大索引
  preIndex = i - 1;
  // 当索引大于等于0且当前索引值大于需要插入的数据时
  for (let j = preIndex; j >= 0; j--) {
    // 第一次比较,如果有序序列最大索引值其实就是待插入数据前一位,比待插入数据大,则后移一位
    num1 += 1;
    console.log(arr[preIndex], preIndex);
    if (arr[preIndex] > currentValue) {
      arr[preIndex + 1] = arr[preIndex];
      preIndex--;
      // 索引减1,继续向前比较
    } else {
      break;
    }
  }
  // 当出现索引所在位置值比待插入数据小时,将待插入数据插入
  // 为什么是preIndex+1,因为在循环里面后移一位之后,当前索引已经变化
  num2 += 1;
  arr[preIndex + 1] = currentValue;
}
console.log(arr, num1, num2);

# [http]

[1] get 和 post

  1. GET在浏览器回退时是无害的,而POST会再次提交请求
  2. GET 请求会被浏览器主动缓存 ,而 POST 不会,除非手动设置
  3. GET 请求参数会被完整保留在浏览器历史记录里,而 POST 中的参数不会被保留
  4. GET 请求在 URL 中传送的参数是有长度限制的,而 POST 没有限制
  5. GET 参数通过 URL 传递,POST 放在 Request body 中
  6. GET 比 POST 更不安全,因为参数直接暴露在 URL 上,所以不能用来传递敏感信息。

[2] HTTP 协议和 HTTPS 区别

  1. http 叫做超文本传输协议,信息是明文传输.特点:基于请求-响应的模式 无状态保存 无连接
  2. https 是具有安全性的 ssl 传输协议.http 和 https 连接方式完全不同,端口也不同,http 是 80,https 是 443.https 协议是由 ssl+http 协议构建的可进行加密传输,身份认证的网络协议,比 http 协议安全,https 需要 ca 安全证书,费用高。
  3. http2 提升访问速度(可以对于,请求资源所需时间更少,访问速度更快,相比 http1.0)允许多路复用:多路复用允许同时通过单一的 HTTP/2 连接发送多重请求-响应信息。改善了:在 http1.1 中,浏览器客户端在同一时间,针对同一域名下的请求有一定数量限制(连接数量),超过限制会被阻塞。二进制分帧:HTTP2.0 会将所有的传输信息分割为更小的信息或者帧,并对他们进行二进制编码+首部压缩+服务器端推送

[3] http 状态码

  • 100:这个状态码是告诉客户端应该继续发送请求,这个临时响应是用来,通知客户端的,部分的请求服务器已经接受,但是客户端应继续发送求
  • 200:成功
  • 202:表示服务器已经接受了请求,但是还没有处理,而且这个请求最终会不会处理还不确定
  • 204:服务器成功处理了请求,但没有返回任何实体内容
  • 304: 缓存
  • 400: Bad Request 客户端请求的语法错误,服务器无法理解
+ 前端提交数据的字段名称和字段类型与后台的实体没有保持一致
+ 前端提交到后台的数据应该是 json 字符串类型,但是前端没有将对象 JSON.stringify 转化成字符串。
  • 401: 没 token
  • 403: 没权限
  • 405:请求方法可能写错
  • 408:超时
  • 413:请求实体过大
  • 414:URI 可能过长
  • 404:请求失败,客户端请求的资源没有找到或者是不存在
  • 500:服务器遇到未知的错误,导致无法完成客户端当前的请求。
  • 503:服务器由于临时的服务器过载或者是维护,无法解决当前的请求

[4] TCP 和 UDP 区别

  1. TCP 是面向连接的,udp 是无连接的即发送数据前不需要先建立链接。
  2. TCP 提供可靠的服务。也就是说,通过 TCP 连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP 尽最大努力交付,即不保证可靠交付。 并且因为 tcp 可靠,面向连接,不会丢失数据因此适合大数据量的交换。
  3. TCP 是面向字节流,UDP 面向报文,并且网络出现拥塞不会使得发送速率降低(因此会出现丢包,对实时的应用比如 IP 电话和视频会议等)。
  4. TCP 只能是 1 对 1 的,UDP 支持 1 对 1,1 对多。
  5. TCP 的首部较大为 20 字节,而 UDP 只有 8 字节。
  6. TCP 是面向连接的可靠性传输,而 UDP 是不可靠的。

[5] 接口重复请求两次

  1. 带预检(Preflighted)的跨域请求,一次是预检 options
  2. 除 GET、HEAD 和 POST(json 格式会预检)以外的 HTTP 方法,请求中出现自定义 HTTP 头部,会产生预检。
  3. 配置 Access-Control-Max-Age 设置时间不去预检

[6] 前后端数据交互方式

  1. form 表单
  2. ajax
  3. websocket:性能高,双工通信,支持跨域
  4. cookie
  5. fetch
  6. jsonp

# [vue]

[1] Vue 常用的修饰符有哪些

  1. .lazy 改变后触发,光标离开 input 输入框的时候值才会改变
  2. .number 将输出字符串转为 number 类型
  3. .trim 自动过滤用户输入的首尾空格
  4. .stop 阻止点击事件冒泡,相当于原生 js 中的 event.stopPropagation()
  5. .prevent 防 止 执 行 预 设 的 行 为 , 相 当 于 原 生 js 中 event.preventDefault()
  6. .capture 添加事件侦听器时使用事件捕获模式,就是谁有该事件修饰符,就先触发谁
  7. .self 只会触发自己范围内的事件,不包括子元素
  8. .once 只执行一次

[2] Vue v-for 循环 key 的作用

  1. 当数据更新后,不会减少 patch 比对,但是会尽可能减少更新
  2. 通过比对 key 值和 value 可以确定值是位移还是新值
  3. 头头,头尾,尾头和尾尾比对,如果没设置 key,key 默认为 undefined

[3] Vuex

是一个专为 vue.js 应用程序开发的状态管理模式,通过创建一个集中的数据存储,方便程序中的所有组件进行访问,简单来说 vuex 就是 vue 的状态管理工具.

Vuex 有五个属性 state getters mutations actions modules

  • State 就是数据源存放地,对应一般 vue 对象的 data,state 里面存放的,数据是响应式的,state 数据发生改变,对应这个数据的组件也会发生改变用 this.$store.state.xxx 调用
  • Getters 相当于 store 的计算属性,主要是对 state 中数据的过滤,用 this.$store.getters.xxx 调用
  • Mutations 处理数据逻辑的方法全部放在 mutations 中,当触发事件想改变 state 数据的时候使用 mutations,用 this.$store.commit 调用,给这个方法添加一个参数,就是 mutation 的载荷(payload)
  • Actions 异步操作数据,但是是通过 mutation 来操作用this.$store.dispatch来触发,actions 也支持载荷

使用场景:组件之间的状态登录状态,加入购物车

  • computed 与 vuex 数据的结合运用
computed:{
    category: {
     get() {
     return this.$store.state.category
     },
     set (value) {
     console.log("Value of category changed")
     this.store.commit("SET_CAT", value)
     }
    }
}
  • vuex 辅助函数:mapState, mapGetters, mapMutations, mapActions
  • namespaced:命名空间
  • vuex 插件
// Vuex 的 store 接受 plugins 选项,这个选项暴露出每次 mutation 的钩子。Vuex 插件就是一个函数,它接收 store 作为唯一参数
const myPlugin = (store) => {
  // 当 store 初始化后调用
};

//注册使用插件
import myPlugin from "xxx";
const store = new Vuex.Store({
  // ...
  plugins: [myPlugin],
});

[4] Vue 路由模式 hash 和 history

  • Hash 模式地址栏中有#,history 没有,history 模式下刷新,会出现 404 情况,需要后台配置
  • 使用 JavaScript 来对 loaction.hash 进行赋值,改变 URL 的 hash 值,可以使用 hashchange 事件来监听 hash 值的变化
  • HTML5 提供了 History API 来实现 URL 的变化。其中最主要的 API,有以下两个:history.pushState() 和 history.repalceState()。这两个 API 可以在不进行刷新的情况下,操作浏览器的历史纪录。唯一不同的是,前者是新增一个历史记录,后者是直接替换当前的历史记录。

[5] Vue 路由传参的两种方式 params 和 query

  • 动态路由也可以叫路由传参,就是根据不同的选择在同一个组件渲染不同的内容
  • 用法上:query 用 path 引入,params 用 name 引入,接收参数都是类似的,分别是 this.$route.query.name 和 this.$route.params.name
  • url 展示上:params 类似于 post,query 类似于 get,也就是安全问题,params 传值相对更安全点,query 通过 url 传参,刷新页面还在,params刷新页面不在了

[6] Vue 路由守卫

  • 全局守卫
  • 独享守卫
  • 组件内守卫
  1. 全局守卫作用:登录校验跳转等

回调函数中的参数,to:进入到哪个路由去,from:从哪个路由离开,next:函数,决定是否展示你要看到的路由页面。

import router from "./router";

router.beforeEach((to, from, next) => {
  console.log(to, from);
  next();
});
// 在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用
router.beforeResolve((to, from, next) => {
  console.log(2);
  next();
});

router.afterEach((to, from) => {
  console.log(to, from);
});
  1. 独享守卫
.....
const routes = [
  {
	  path:'/give',
	  name:"Give",
	  beforeEnter : ( to , from , next ) => {
	  		  console.log("hello")
	  },
	  component: () => import(/* webpackChunkName: "about" */ '../views/Give.vue')

  }
]
  1. 组件内守卫
    • beforeRouteEnter
    • beforeRouteUpdate
    • beforeRouteLeave

beforeRouteEnter 守卫 不能 访问 this,因为守卫在导航确认前被调用,因此即将登场的新组件还没被创建。不过,可以通过传一个回调给 next 来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数:

beforeRouteEnter (to, from, next) {
  next(vm => {
    // 通过 `vm` 访问组件实例
  })
}

[7] Vue 的 keep-alive

  1. *keep-alive 一般用于 动态组件 或者 router-view*
  2. 借助 beforeRouteEnter 可以根据路由设置什么时候清除缓存,什么时候保存缓存
  3. activated deactivated 两个 keep-alive 的生命周期钩子处理一些进出的逻辑和数据
  4. router 对应的路由 meta 设置 keepAlive 作为一个变量可以在需要的时候动态控制路由的那些缓存哪些不缓存,将组件的 name 取和 router 的 name 一致时,就可以利用 name 进行动态设置
  5. keep-alive 所包裹的文件最好要设置选项 name,否则 keep-alive 可能无效
  6. Props
    • include - 字符串或正则表达式。只有名称匹配的组件会被缓存
    • exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存,exclude 优先级大于 include
    • max - 数字。最多可以缓存多少组件实例,在新实例被创建之前,已缓存组件中最久没有被访问的实例会被销毁掉
  7. 匹配首先检查组件自身的 name 选项,如果 name 选项不可用,则匹配它的局部注册名称 (父组件 components 选项的键值)。匿名组件不能被匹配
  8. :include="" 为空字符串时,代表被 keep-alive 包裹的组件都缓存,当 :include=[] 为空数组时,代表不缓存被 keep-alive 包裹的组件
  9. 当组件第一次渲染时 activated 也会被调用,即 beforeCreate -> created -> beforeMount -> mounted -> activated,当跳转另一组件时即当前组件被停用时,则只会调用 deactivated,它的 beforeDestroy 和 destroy 不会被调用,当再次激活此组件时,则也只会调用 activated,其它钩子函数也不会调用
  • 作用:在组件切换过程中将状态保留在内存中,防止重复渲染 DOM,减少加载时间以及性能消耗,提高用户体验。
  • 生命周期函数:Activated 在 keep-alive 组件激活时调用,deactivated 在 keep-alive 组件停用时调用
<template>
  <div class="oawrap"></div>
</template>

<script>
export default {
  name: "Task",
  mounted() {
    console.log("keepalive");
  },
  beforeRouteEnter(to, from, next) {
    next((vm) => {
      // 如果在子页面刷新了,那其实回来也需要重新init数据
      //那么可以设置一个变量去监控,如果刷新,这个变量就重置了
      if (
        (from.name === "Sign" || from.name === "Contract") &&
        vm.$store.state.cache == true
      ) {
        // 缓存不做处理
      } else {
        vm.init();
      }
    });
  },
  activated(v) {
    console.log("activated");
  },
  deactivated() {
    console.log("deactivated");
  },
  methods: {
    init() {},
  },
};
</script>
<template>
  <div id="app">
    <div id="nav">
      <router-link to="/">Home</router-link> |
      <router-link to="/about">About</router-link>
    </div>
    <keep-alive :include="x">
      <router-view />
    </keep-alive>
    <button @click="add">add</button>
  </div>
</template>
<script type="text/javascript">
export default {
  data() {
    return {
      x: [],
    };
  },
  methods: {
    add() {
      this.x = ["Home"];
    },
  },
};
</script>

[8] Vue 双向绑定原理

  1. Vue 双向绑定就是:数据变化更新视图,视图变化更新数据
  2. Vue 数据双向绑定是通过数据劫持和观察者模式来实现的,数据劫持,object.defineproperty 它的目的是:当给属性赋值的时候,程序可以感知到,就可以控制改变属性值,观察者模式 当属性发生改变的时候,使用该数据的地方也发生改变
  3. Vue3 改为 proxy

[9] Vue 组件通信

  1. $emit $on props
  2. vuex
  3. bus 总线
  4. provide/inject
  5. $refs.childa.init() $parent.reset()
  6. 兄弟组件通信:依赖 this.$parent.$on 和 this.$parent.$emit
  7. $attrs 和 $listeners:当传给子组件的内容没有被props接受,可以使用$attrs进行处理,如果还要传给孙子组件的话,如果值比较多,可以使用v-bind='$attrs'进行类似解构传给孙子组件
  8. slot 插槽

[10] Vue3 和 Vue2 之间的异同

  1. 响应式原理不同,proxy Object.defineProperty
  2. 结构不同 compistion Api Options Api=>setup 代替生命周期的大部分写法
  3. vue3 模块按需加载,体积小
  4. vue3 中移除了过滤器,可以用计算属性替代
  5. vue3 指令的变化
  6. vue3 props 更加严格
  7. v-for 和 v-if 的优先级更改
  8. v-model 不同
  9. 重写了虚拟 Dom 实现
  10. vue3 没有组件模板有唯一顶层元素的限制
  11. 生命周期钩子修改

[11] Vue3 和 Vue2 指令的变化

  1. vue2 和 vue3 的自定义指令的生命周期函数改变,vue3 的自定义指令和 vue3 生命周期基本一致。
  2. 如果简写,只考虑 mounted 和 updated,那么和 vue2 的写法一致,不过一个 Vue,一个是 Vue 实例 app 调用
app.directive("pos", (el, binding) => {
  el.style[binding.arg] = binding.value + "px";
});

[12] v-model 原理,怎么写在自定义组件上

  1. 在 vue2 中,v-model 只能在组件上单独使用一次,不可一个组件加多个
  2. v-model 实际上是 value 和 input 事件的语法糖
  3. 定义在 type=checkbox 时,需要注意的 emit 的是 e.target.checked
  4. 默认的传给子组件的 props 都是 value,而触发事件都是 input,不过可以在子组件通过 model 进行配置修改
<template>
  <input type="checkbox" :checked="checked" @change="fun" />
</template>

<script>
export default {
  props: ["checked"],
  model: {
    prop: "checked",
    event: "change",
  },
  methods: {
    fun(e) {
      this.$emit("change", e.target.checked);
    },
  },
};
</script>
  1. 如果自定义组件有多个需要响应的值,可以借助 xxx.sync 来实现(触发 xxx:value)
<my-input :value.sync="say"></my-input>

my-input: { props: ['value'], template: "
<div><input v-bind:value="value" v-on:input="change1" />{{value}}</div>
", methods: { change1: function(e) { var v = e.target.value
this.$emit('update:value', v) }, } }
  • vue3 版本,:value.sync 弃用,转 v-model,一个组件可以有多个 v-model
  • 默认 props 接受 modelValue,使用 update:modelValue 事件触发
  • 如果需要自定义名字,v-model:skt;props:['skt']; $emit(update:skt)

[13] vue 生命周期

  • vue2 和 vue3 基本一致,只不过 vue3 更趋向 react 名称,且 vue3 很多场合使用 compoision api,利用 setup 来实现大部分功能
  • mounted 操作 dom
  • created 可以进行请求,el 还没初始化
  • beforeDestroy 处理清空事件,回收内存,不重复订阅

# [react]

[1] react this 绑定问题

  1. 直接 bind this 型 缺点:性能不太好,这种方式跟 react 内部帮加入 bind 一样的,每次 render 都会进行 bind,而且如果有两个元素的事件处理函数是同一个,也还是要进行 bind,这样会多写点代码,而且进行两次 bind,性能不是太好。
<button onClick={this.handleClick.bind(this)}>Click me</button>
  1. constuctor 手动 bind 型
class Foo extends React.Component {
  constuctor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
  }
  handleClick() {
    this.setState({ xxx: aaa });
  }

  render() {
    return <button onClick={this.handleClick}>Click me</button>;
  }
}
  1. 箭头函数 缺点: 每次 render 都会重复创建函数,性能会差一点。
class Foo extends React.Component {
  handleClick() {
    this.setState({ xxx: aaa });
  }

  render() {
    return <button onClick={(e) => this.handleClick(e)}>Click me</button>;
  }
}
  1. public class fields 型(实验阶段)
class Foo extends React.Component {
  handleClick = () => {
    this.setState({ xxx: aaa });
  };

  render() {
    return <button onClick={this.handleClick}>Click me</button>;
  }
}
  1. 以上四种写法传递参数
class IndexPage extends React.Component {
  constructor(props) {
    super(props);

    this.event1 = this.event1.bind(this, 1);
  }

  event1(a, e) {
    console.log("event1", a, e); // 1 {}
  }
  event2(a, e) {
    console.log("event2", a, e); // 2 {}
  }
  event3(a, e) {
    console.log("event3", a, e); // 3 {}
  }
  event4 = (e) => {
    console.log("event4", e); // {}
  };

  render() {
    return (
      <div>
        <button onClick={this.event1}>event1</button>
        <button
          onClick={(e) => {
            this.event2(2, e);
          }}
        >
          event2
        </button>
        <button onClick={this.event3.bind(this, 3)}>event3</button>
        <button onClick={this.event4}>event4</button>
      </div>
    );
  }
}
event1方式可以在bind时传入自定义的参数,在最后会补上event参数。
event2方式由于显示的传参,所以他需要显示的传入event参数。
event3方式同event1一致,但是可以传入在render中获取或计算后的参数。
event4方式没法传入自定义参数,但是event参数是可以拿到的。

由于 event2 和 event3 方式是在使用时返回 event 实例,对性能有影响,所以在没有参数要传时不建议使用,又由于 event1 需要额外写代码,所以推荐使用 event4 这种方式。当需要传如参数时,固定的参数区分不同函数时可以使用 event1,其他方式 event2 或 event3 都可以。


[2] react 组件传值方案

  1. 父传子,使用 props 属性传递信息
//父组件
import C from "./views/c.js";
function App() {
  return (
    <div className="App">
      <C give="foryou" />
    </div>
  );
}
export default App;
//c.js
import React, { Component } from "react";

export default class Cc extends Component {
  constructor(arg) {
    console.log(arg); //{give: 'foryou'}
    super();
    console.log(this.props); //undefined
    this.state = { a: 1999, b: "" };
  }
  componentWillMount() {
    this.setState(() => {
      return {
        b: this.props.give + "xxxxx",
      };
    });
  }
  componentDidMount() {
    console.log(this.props); //{give: 'foryou'}
  }
  render() {
    return (
      <div>
        react01{this.props.give}----{this.state.b}
      </div>
    );
  }
}

当在子组件的 constructor 中,用 this.porps 拿不到父组件传递的值,不过子组件的 constructor 的参数实际上就是 props,在 componentDidMount 中可以用 this.props 拿到对应的父组件返回的值

  1. 子传父,逆向传值需要调用函数进行回传,子组件通过 props 调用到父组件的处理方法
//子组件
this.props.callback("子组件传过来的值");
//父组件
<div>
  <Children toChildren={this.state.msg} callback={this.changeMsg}></Children>
</div>
  1. 同级的可借助 props 和回调,稍微麻烦一点;也可以借助第三方插件如 pubsub-js 等(publish/subscribe)
  2. React.createContext 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props。一句话概括就是:跨级传值,状态共享。但是需要 谨慎使用 ,因为这会使得组件的复用性变差。
  3. redux

[3] Vue 与 react 区别

  • 相同点:
    1. 都支持服务器渲染
    2. 都有虚拟 dom,组件化开发,通过 props 参数进行父子组件数据的传递,
    3. 都实现 webcomponent 规范
    4. 都是数据驱动视图
    5. 都有状态管理,react 有 redux,vue 有 vuex
    6. 都有支持 native 的方案 react 有 react native vue 有 weex
  • 不同点:
    1. react 严格上只针对 mvc 的 view 层,vue 是 mvvm 模式
    2. 虚拟 dom 不一样,vue 会跟踪每一个组件的依赖关系,不需要重新渲染整个 dom 组件树,而 react 不同,当应用的状态被改变时,全部组件都会重新渲染,所以 react 中用 shouldcomponentupdate 这个生命周期的钩子函数来控制
    3. 组件写法不一样 ,react 是 jsx 和 inline style ,就是把 html 和 css 全写进 js 中,vue 则是 html,css ,js 在同一个文件
    4. 数据绑定不一样,vue 实现了数据双向绑定,react 数据流动是单向的
    5. 在 react 中,state 对象需要用 setstate 方法更新状态,在 vue 中,state 对象不是必须的,数据由 data 属性在 vue 对象中管理

[4] (组件的)状态(state)和属性(props)之间有何不同

  • Props 是一个从外部传进组件的参数,主要作用就是父组件向子组件传递数据,但是 props 对于使用它的组件来说是只读的,一旦赋值不能修改,只能通过外部组件主动传入新的 props 来重新渲染子组件
  • State 一个组件的显示形态可以由数据状态和外部参数决定,外部参数是 props,数据状态就是 state,首先,在组件初始化的时候,用 this.state 给组件设定一个初始的 state,在第一次渲染的时候就会用这个数据来渲染组件,state 不同于 props 一点时,state 可以修改,通过 this.setState()方法来修改 state

[5] shouldComponentUpdate 作用

这个 react 生命周期钩子函数是来解决这个问题:在更新数据的时候用 setState 修改整个数据,数据变了之后,遍历的时候所有内容都要被重新渲染,数据量少还好,数据量大就会严重影响性能

  • 解决办法:
  1. shouldcomponentupdate 在渲染前进行判断组件是否更新,更新了 再渲染
  2. purecomponent(纯组件)省去了虚拟 dom 生成和对比的过程 在 类组件中使用
  3. react.memo() 类似于纯组件 在无状态组件中使用
  • 子组件如果没有发生变化,但是父组件 render 改变,也会触发子组件 render 变化,可以使用 shouldComponentUpdate 进行拦截处理;(返回 false 并不会阻止子组件在 state 更改时重新渲染。)
//性能优化
shouldComponentUpdate(nextProps,nextState){
		if(nextProps.content!==this.props.content){
			return true
		}else{
			return false
		}

}

# [移动端]

[1] 移动端 300ms 处理

  1. 禁用缩放 <meta name = "viewport" content="user-scalable=no" >缺点: 网页无法缩放
  2. css touch-action: touch-action 的默为 auto,将其置为 none 即可移除目标元素的 300 毫秒延迟 缺点: 新属性,可能存在浏览器兼容问题
  3. fastclick 原理: 在检测到 touchend 事件的时候,会通过 DOM 自定义事件立即出发模拟一个 click 事件,并把浏览器在 300ms 之后真正的 click 事件阻止掉,缺点: 脚本相对较大
import FastClick from "fastclick";
FastClick.attach(document.body);

# [other]

[1] 多个项目切换不同的 node 版本

  • 安装 nvm
    • 安装不同版本 nvm install xxx
    • 查看 node nvm list
    • 切换 node nvm use xxx
  • 最好在管理员模式下操作

最后更新: 6/22/2022, 8:24:00 AM