JS

JS 的数据类型

基本数据类型:number/boolean/string/null/undefined/Symbol/BigInt(任意精度的整数)
引用数据类型:Object

判断数据类型

  1. typeof => 返回一个字符串,表示操作数的类型instanceof => 在原型链中查找是否是其实例 => object instanceof constructor
    1. typeof null === 'object'
    2. typeof <function> === 'function'
  2. 判断是否是数组
    1. arr instanceof Array
    2. arr.constructor === Array
    3. Array.isArray(arr)
    4. Object.prototype.toString.call(arr) === '[object Array]'

堆和栈

1、按照JS的数据类型来说,基本数据类型(即值类型,包括:undefined、null、number、string、boolean、symbol)存在栈中,引用类型(即:object array function)存在堆中。

2、基本数据类型有固定的大小和值,存放在栈中,而引用类型不确定大小,但是其引用地址是固定的,因此,它的地址存在栈中,指向存储在堆中的对象。

3、基本数据类型,在当前环境执行结束时销毁,而引用类型只有在引用的它的变量不在时,会被垃圾回收机制回收。

4、复制方式不一样,值类型的是深复制(深拷贝),引用类型的是浅复制(浅拷贝),变量的复制其实是引用的传递。

5、值类型不可添加属性和方法,而引用类型可以添加属性和方法。

6、值类型比较相等,就可以用 == 或者 ===来比较,但是对于引用类型,即使let s = {};let s1 = {};但因为他们的内存地址不一样,比较结果依然不相等。

原型链是什么?

  1. case:const a = {},此时 a.proto == Object.prototype,即 a 的原型是 Object.prototype
  2. case:我们有一个数组对象,const a = [],此时 a.proto == Array.prototype,此时 a 的原型是 Array.prototype,此时 Array.prototype.proto == Object.prototype,此时:
    1. a 的原型是 Array.prototype
    2. a 的原型的原型是 Object.prototype
    3. 于是形成了一条原型链
  3. 可以通过 const x = Object.create(原型) 或者 const x = new 构造函数() 的方式改变 x 的原型
    1. const x = Object.create(原型) => x.proto == 原型
    2. const x = new 构造函数() => x.proto == 构造函数.prototype
  4. 原型链可以实现继承,以上面的数组为例:a ===> Array.prototype ===> Object.prototype原型链的优点在于:简单优雅
    1. a 是 Array 的实例,a 拥有 Array.prototype 里的属性
    2. Array 继承了 Object
    3. a 是 Object 的间接实例,a 也就拥有 Object.prototype 里的属性
    4. a 即拥有了 Array.prototype 的属性,也拥有了 Object.prototype 的属性
  5. 但是不支持私有属性,ES6新增加的 class 可以支持私有属性

代码中的 this 是什么?

  1. 将所有的函数调用转化为 call => this 就是 call 的第一个参数
  2. func(p1, p2) => func.call(undefined, p1, p2) => 如果 context 是 null 或 undefined,window 是默认的 context(严格模式下默认是 undefined)
  3. obj.child.method(p1, p2) => obj.child.method.call(obj.child, p1, p2)

JS 的 new 做的什么?

function Person(name) {
  this.name = name;
}

const ming = new Person("ming");

const ming = (function (name) {
  // 1. var temp = {}; => 创建临时对象
  // 2. this = temp; => 指定 this = 临时对象
  this.name = name;
  // 3. Person.prototype = {...Person.prototype, constructor: Person} => 执行构造函数
  // 4. this.__proto__ == Person.prototype => 绑定原型
  // return this; => 返回临时对象
})("ming");

JS 的立即执行函数是什么?

  1. 声明一个匿名函数,然后立即执行它,这种做法就是立即执行函数
  2. 例如: 每一行代码都是一个立即执行函数在 ES6 之前只能通过立即执行函数来创建局部作用域
    1. (function() {} ())
    2. (function() {})()
    3. !function() {}()
    4. +function() {}()
    5. -function() {}()
    6. ~function() {}()
  3. 其优点在于兼容性好
  4. 目前可以使用 ES6 的 block + let 代替
    {
       let a = '局部变量';
       console.log(a); // 局部变量
    }
    console.log(a); // Uncaught ReferenceError: a is not defined

 

JS 的执行机制?

js正常从上往下执行,当遇到定时器和网络请求的时候,就会被卡主,就要暂停下当前的任务,等待卡主的任务完成,为了解决这个问题就将任务分成同步和一步两个类型:

同步任务处理不需要等待的任务,例如:const query=1 console.log(query)

异步任务处理那些需要等待的任务,比如setTimeout、网络请求等

但是当Promise.resolve().then 发起的请求已经完成,但是很久不执行then

所以js又把异步任务分成宏任务和微任务 :

宏任务:不需要立即连贯执行的,例如:srcipt、setTimeout、setInterval、setImmediate

微任务:需要立即执行的,例如:new Promise().then,process.nextTick 

微任务--需要等下一个宏任务执行前完成

他们放在不同的eventTable中,宏任务放在宏任务的eventTable,然后注册回调到Event Queue中等待执行,微任务放在微任务的eventTable,然后注册回调到Event Queue中等待执行

总结:

 

 函数柯里化:(参数复用、兼容性检测)

1、返回一个新函数

2、递归手机所有后置参数

3、最后调用普通函数

// 兼容性 
const whichEvent = (function () { if (window.addEventListener) { return function (element, type, listener, useCapture) { element.addEventListener( type, function (e) { listener.call(element, e); }, useCapture ); }; } else if (window.attachEvent) { return function (element, type, handler) { element.attachEvent("on" + type, function (e) { handler.call(element, e); }); }; } })();

 

JS 的闭包是什么?

  1. 闭包是 JS 的一种语法特性,闭包 = 函数 + 自由变量。对于一个函数来说,变量分为:全局变量、本地变量、自由变量
  2. case:闭包就是 count + add 组成的整体
    const add2 = (function() {
       var count = 0;
       return function add() {
         count++;
       }
    })()
    
    // 此时 add2 就是 add
    add2(); 
    
    // 相当于
    add();
    
    // 相当于
    count++;
    
  3. 以上就是一个完整的闭包的应用
  4. 闭包解决了其优点是:简单好用
    1. 避免污染全局环境 => 因为使用了局部变量
    2. 提供对局部变量的间接访问 => 只能 count++,不能 count--
    3. 维持变量,使其不被垃圾回收
  5. 但是闭包使用不当可能造成内存泄漏。case:
    function test() {
       var x = {name: 'x'};
       var y = {name: 'y', content: '这里很长很长,占用了很多很多字节'}
       return function fn() {
         return x;
       }
    }
    
    const myFn = test(); // myFn 就是 fn 了
    const myX = myFn(); // myX 就是 x 了
    
  6. 对于正常的浏览器来说,y会在一段时间内自动消失,被垃圾回收器回收,但是旧版本的 IE 浏览器不会回收,这是 IE 浏览器的问题

JS 如何实现类

  1. 使用原型
function Dog(name) {
  this.name = name;
  this.legsNum = 4;
}

Dog.prototype.kind = 'dog';
Dog.prototype.run = function () {
  console.log("I am running with " + this.legsNum + " legs.")
}
Dog.prototype.say = function () {
  console.log("Wang Wang, I am " + this.name);
}

const dog = new Dog("ming");
dog.say();
  1. 使用类
class Dog {
  kind = 'dog';

  constructor(name) {
    this.name = name;
    this.legsNum = 4;
  }

  run() {
    console.log("I am running with " + this.legsNum + " legs.")
  }

  say() {
    console.log("Wang Wang, I am " + this.name);
  }
}

const dog = new Dog('ming');
dog.say();

JS 实现继承

  1. 使用原型链
// dog => Dog => Animal
function Animal(legsNum) {
  this.legsNum = legsNum;
}

Animal.prototype.kind = 'animal';
Animal.prototype.run = function () {
  console.log("I am running with " + this.legsNum + " legs.");
}

function Dog(name) {
  this.name = name;
  Animal.call(this, 4); // 继承属性
}

// Dog.prototype.__proto__ == Animal.prototype
const temp = function () {}
temp.prototype = Animal.prototype;
Dog.prototype = new temp();

Dog.prototype.kind = 'dog';
Dog.prototype.say = function () {
  console.log("Wang Wang, I am " + this.name);
}

const dog = new Dog("ming"); // Dog 函数就是一个类
console.log(dog);
  1. 使用类
class Animal {
  kind = 'animal';

  constructor(legsNum) {
    this.legsNum = legsNum;
  }

  run() {
    console.log("I am running with " + this.legsNum + " legs.");
  }
}

class Dog extends Animal {
  kind = 'dog';

  constructor(name) {
    super(4);
    this.name = name;
  }

  say() {
    console.log("Wang Wang, I am " + this.name);
  }
}

const dog = new Dog('ming');
console.log(dog);

JS 手写节流 & 防抖

  1. 节流 throttle => 技能冷却中 => 场景  固定频率执行
    1. Select 去服务端动态搜索
    2. 按钮用户点击过快,发送多次请求
// 每隔一段时间执行 (后执行),先执行一次,每隔一段时间再执行
function throttle(time, callback, immediately) {
    if (immediately === undefined) {
        immediately = true
    }

    if (immediately) {
        let t
        return function () {
            if (!t || Date.now() - t > time) {
                callback.apply(null, arguments)
                t = Date.now()
            }
        }
    } else {
        let timer;
        if (timer) {
            return
        }
        const args = arguments
        timer = setTimeout(function () {
            callback.apply(null, args)
            timer = null
        }, time)
    }

}

const fn = throttle(2000, () => {console.log("Hello!")});
fn();
fn();
setTimeout(fn, 3000);
  1. 防抖 debounce => 回城被打断 => 场景  多久只运行一次
    1. 滚动事件
    2. 窗口大小改变
function debounce(callback,time) {
  let timer;
  return () => {
    timer && clearTimeout(timer);
   const args = arguments timer = setTimeout(() => { callback.apply(null, args); }, time); } } const fn = debounce(() => console.log("Hello!",2000)); fn(); setTimeout(fn, 1000);

JS 手写发布订阅

const eventBus = {
  bus: {},
  on(eventName, callback) {
    if (!this.bus[eventName]) {
      this.bus[eventName] = [callback];
    } else {
      this.bus[eventName].push(callback);
    }
  },
  emit(eventName, data) {
    if (!this.bus[eventName]) {
      throw new Error("Please check eventName " + eventName);
    }
    this.bus[eventName].forEach(callback => callback.call(null, data));
  },
  off(eventName, callback) {
    if (!this.bus[eventName]) {
      throw new Error("Please check eventName " + eventName);
    }
    const index = this.bus[eventName].indexOf(callback);
    if (index < 0) {
      return;
    }
    this.bus[eventName].splice(index, 1);
  }
}

eventBus.on('click', console.log)
eventBus.on('click', console.error)
setTimeout(() => {
  eventBus.emit('click', 'Hello!');
}, 3000)

JS 手写 AJAX

const ajax = (method, url, data, success, fail) => {
  const request = new XMLHttpRequest();
  request.open(method, url);
  request.onreadystatechange = function () {
    if (request.readyState === 4) {
      if (request.status >= 200 && request.status < 300 || request.status === 304) {
        success(request);
      } else {
        fail(request);
      }
    }
  }
  if (method === "post") {
    request.send(data);
  } else {
    request.send();
  }
}

JS 手写Promise

https://www.cnblogs.com/duanlibo/p/17393791.html 

JS 手写 Promise.all

  1. 要在 Promise 上写而不是在原型上写
  2. Promise.all 参数(Promise 数组)和返回值(新 Promise 对象)
  3. 用数组记录结果
  4. 只要有一个 reject 就整体 reject
Promise.myAll = function (list) {
  const results = [];
  let count = 0;
  return new Promise((resolve, reject) => {
    list.map((promise, index) => {
      promise.then((result) => {
        results[index] = result;
        count++;
        if (count >= list.length) {
          resolve(results);
        }
      }, reason => reject(reason));
    });
  });
}

JS 手写深拷贝

JSON

const copy = JSON.parse(JSON.stringify(a));

缺点:

  1. 不支持 Date、正则、undefined、函数等数据
  2. 不支持引用,即环状结构

递归。要点:

  1. 判断类型
  2. 检查环
  3. 不拷贝原型上的属性
const deepCopy = (a, cache) => {
  if (!cache) {
    cache = new Map();
  }

  // 不考虑跨 iframe
  if (a instanceof Object) {
    if (cache.get(a)) {
      return cache.get(a);
    }
    let result = null;
    if (a instanceof Function) {
      // 有 prototype 就是普通函数
      if (a.prototype) {
        result = function () {
          return a.apply(this, arguments);
        }
      } else {
        result = (...args) => {
          return a.call(undefined, ...args);
        }
      }
    } else if (a instanceof Array) {
      result = [];
    } else if (a instanceof Date) {
      result = new Date(a - 0);
    } else if (a instanceof RegExp) {
      result = new RegExp(a.source, a.flags);
    } else {
      result = {};
    }
    cache.set(a, result);
    for (let key in a) {
      if (a.hasOwnProperty(key)) {
        result[key] = deepCopy(a[key], cache);
      }
    }
    return result;
  }
  return a;
}

const a = {
  number: 1, bool: false, str: 'hi', empty1: undefined, empty2: null,
  array: [
    {name: 'frank', age: 18},
    {name: 'jacky', age: 19}
  ],
  date: new Date(2000, 0, 1, 20, 30, 0),
  regex: /\.(j|t)sx/i,
  obj: {name: 'frank', age: 18},
  f1: (a, b) => a + b,
  f2: function (a, b) { return a + b }
}
a.self = a;

const b = deepCopy(a);
console.log(b.self === b); // true
b.self = 'hi'
console.log(a.self !== 'hi'); //true

JS 手写数组去重

const unique = (nums) => {
  const map = new Map();
  for (let i = 0; i < nums.length; i++) {
    if (nums[i] === undefined || map.has(nums[i])) {
      continue;
    }
    map.set(nums[i], true);
  }
  return [...map.keys()];
}

js继承

// 1、原型链继承
function Parent(name) {
  this.name = name|| '父亲'; //实例基本属性(该属性,强制私有,不共享)
  this.arr = [1];//(该属性强制素有)
}
Parent.prototype.say = () => {
  console.log('hello');
};
  
function Child(like) {
  this.like = like
}
Child.prototype = new Parent(); // 核心,但此时Child.prototype.constructor==Parent
Child.prototype.constructor = Child
  
let boy1 = new Child()
let boy2 = new Child()
//优点:共享了父亲构造函数的say方法
console.log(boy1.say(),boy2.say(),boy1.say() ===boy2.say());//hello,hello,true
//缺点1:不能向父类构造函数传参
console.log(boy1.name,boy2.name,boy1.name===boy2.name); //父亲 父亲 true
// 缺点2:子类实例共享了父类构造函数的引用属性,比如arr属性
boy1.arr.push(2)
// 修改了boy1的arr属性,boy2的arr属性也不和变化,因为两个实例的原型上(child.prototype)有了父类构造海曙的实例属性arr;所以只要修改了boy1.arr boy2.arr的属性也会变化
console.log(boy2.arr);//[1,2]
// 注意1:修改了boy1的name属性,是不会影响到boy2的name,因为设置boy1.name相当于在子类实例上新增了name属性
// 注意2:
console.log(boy1.constructor);//Parent
// 你会发现实例的构造函数居然是Parent,实际上,我们希望子类实例的构造函数是Child,所以要记得修复液构造函数的指向
// 修复如下:Child.prototype.constructor = Child
  
// 2、构造函数继承
function Parent(name) {
  this.name = name; //实例基本属性(该属性,强制私有,不共享)
  this.arr = [1]; //(该属性强制素有)
  this.asy = function () {
  // 实例引用属性(该属性强带哦复用,需要共享)
      console.log("hello");
};
}

function Child(name, like) {
Person.call(this, name); //核心 拷贝了父类的实例属性和方法
  this.like = like;
}

let boy1 = new Child("小红", "apple");
let boy2 = new Child("小明", "orange");
// 优点1:可向父类构造函数传参
console.log(boy1.name, boy2.name); // 小红、小明
// 优点2:不共享父类构造函数的引用属性
boy1.arr.push(2);
console.log(boy1.arr, boy2.arr); //[1,2] [1]
// 缺点1:方法不能复用
console.log(boy1.say === boy2.say); //false (说明,boy1和boy2的say方法独立,不是共享的)
// 缺点2:不能继承父类原型上的方法
Parent.prototype.walk = function () {
// 在父亲的原型对象上定义walk方法
  console.log("我会走路");
};
boy1.walk; //undefined(说明实例不能获取父亲原型上的方法)

// 3、组合继承
function Parent(name) {
  this.name = name; //实例基本属性(该属性,强制私有,不共享)
  this.arr = [1]; //(该属性强制素有)
}
Parent.prototype.say = function () {
//将需要复用、共享的方法定义在不累原型上
  console.log("hello");
};

function Child(name, like) {
  Person.call(this, name, like); //核心 第二次
  this.like = like;
}
Child.prototype = new Parent(); //核心 第一次
Child.prototype.constructor = Child; //修正constructor指向

let boy1 = new Child("小红", "apple");
let boy2 = new Child("小明", "orange");
// 优点1:可向父类构造函数传参
console.log(boy1.name, boy2.like); //小红 apple
// 优点2:可复用父类原型上的方法
console.log(boy1.say === boy2.say); //true
// 优点3:不共享父类构造函数的引用属性 arr
boy1.arr.push(2);
console.log(boy1.arr, boy2.arr); //[1,2] [1]
// 缺点:由于电泳了2次父类的构造函数,会存在一份多余的父类实例

// 4、寄生组合继承1
function Parent(name) {
  this.name = name; //实例基本属性(该属性,强制私有,不共享)
  this.arr = [1]; //(该属性强制素有)
}
Parent.prototype.say = function () {
//将需要复用、共享的方法定义在不累原型上
  console.log("hello");
};

function Child(name, like) {
Person.call(this, name, like); //核心
  this.like = like;
}
Child.prototype = Parent.prototype; //核心 子类原型和父类原型实际是同一个

let boy1 = new Child("小红", "apple");
let boy2 = new Child("小明", "orange");
let p1 = new Parent("小爸爸");
// 优点1:可向父类构造函数传参
console.log(boy1.name, boy2.like); // 小红 apple
// 优点2:可复用父类原型上的方法
console.log(boy1.say === boy2.say); //true
// 缺点:当修复子类构造函数的指向后,父类实例的构造函数指向也会跟着变了
// 没修复之前:
console.log(boy1.constructor); //Parent
// 修复代码:
Child.prototype.constructor = Child;
// 修复之后:
console.log(boy1.constructor); //Child
console.log(p1.constructor); //Child 这里就是存在的问题(我们希望是Parent)
// 原因:是通过原型来实现继承的,Child.prototype的上面是没有constructor属性的,就会网上找,这样就找到了Parent。prototype上的constructor属性,当改变了子类实例上的constructor属性,所有的constructor的指向就都会发生变化

// 5寄生组合继承
function Parent(name) {
  this.name = name; //实例基本属性(该属性,强制私有,不共享)
  this.arr = [1]; //(该属性强制素有)
}
Parent.prototype.say = function () {
//将需要复用、共享的方法定义在不累原型上
  console.log("hello");
};

function Child(name, like) {
  Person.call(this, name, like); //核心
  this.like = like;
}
Child.prototype = Object.create(Parent.prototype); //核心 子类原型和父类原型会隔离开,不是同一个了
Child.prototype.constructor = Child; //这里是修复构造函数指向的代码
let boy1 = new Child("小红", "apple");
let boy2 = new Child("小明", "orange");
let p1 = new Parent("小爸爸");
// 注意:这种方法也是要修复构造函数的
// 修复代码Child.prototype.constructor = Child
// 修复之后
console.log(boy1.constructor); //Child
console.log(p1.constructor); //Parent
  
// 6、class类继承
class Person {
constructor() {
this.name = "小明";
this.eats = ["苹果"];
this.getName = function () {
  console.log(this.name);
};
}
get = () => {
  console.log("Person.prototype上的方法");
};
}
class Student extends Person {}
const stu1 = new Student();

stu1.name = "小花";
stu1.eats.push("香蕉");
console.log(stu1.name);
console.log(stu1.eats);

stu1.getName();
stu1.get();
console.log("-----");
const stu2 = new Student();
console.log(stu2.name);
console.log(stu2.eats);
stu2.getName();
stu2.get();

console.log(stu2, "stu2"); 

DOM

DOM 事件模型

  1. 先经历从上到下的捕获阶段,再经历从下到上的冒泡阶段
  2. addEventListener("click", fn, options, useCapture)可以使用 event.stopPropagation() 来阻止捕获或冒泡
    1. options 中有一个 capture 参数,true 表示捕获阶段,false 表示冒泡阶段
    2. useCapture true 表示捕获阶段,false 表示冒泡阶段

手写事件委托

ul.addEventListener('click', (e) => {
  if (e.target.tagName.toLowerCase() === 'li') {
    // do something
  }
});
  1. 如果点击 li 里面的 span,就没有办法触发事件
  2. 点击元素之后,递归遍历点击元素的祖先元素直至遇到 li 或者 ul
const delegate = (element, eventType, selector, fn) => {
  element.addEventListener(eventType, e => {
    let ele = el.target;
    while (!ele.matches(selector)) {
      if (ele === element) {
        return;
      }
      ele = ele.parentNode;
    }
    fn.call(ele, e);
  });
  return element;
}

delegate(ul, 'click', 'li', (e) => console.log(e));
  1. 事件委托优点事件委托缺点 => 调试比较复杂,不容易确定监听者
    1. 节省监听器
    2. 实现动态监听

手写可拖曳 div

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Drag</title>
    <style>
        .drag {
            border: 1px solid red;
            position: absolute;
            top: 0;
            left: 0;
            width: 100px;
            height: 100px;
        }
    </style>
</head>
<body>
<div class="drag"></div>
<script>
    let dragging = false;
    let position = null;
    const dragEle = document.querySelector('.drag');
    dragEle.addEventListener("mousedown", e => {
        dragging = true;
        position = [e.clientX, e.clientY];
    });

    document.addEventListener("mousemove", e => {
        if (!dragging) return;
        const x = e.clientX;
        const y = e.clientY;
        const moveX = x - position[0];
        const moveY = y - position[1];
        const left = parseInt(dragEle.style.left || 0);
        const top = parseInt(dragEle.style.top || 0);
        dragEle.style.left = left + moveX + 'px';
        dragEle.style.top = top + moveY + 'px';
        position = [x, y];
    });

    document.addEventListener('mouseup', e => {
        dragging = false;
    })
</script>
</body>
</html>

 

如何用 JS 实现瀑布流布局

1、首先我们需要准备很多张图片(我这准备的是40张),还有vscode开发工具。

2、编写前端代码,给图片添加样式让图片等宽并同行显示。

3、编写js代码,要知道第一行图片数量下一张图片的序号以及前一行最矮的元素的序号,还有摆放的位置

一、前端代码编写

1、首先我们定义一个container容器来装所有图片,在这个容器中用box容器装box-img容器再装入每张图片,这样方便之后样式的编写。
2、使图片同行显示--给box容器使用float:left;属性。
3、让图片等宽显示--给box-img容器设置**width:150px;,img标签设置width:100%;继承父容器box-img高度的100%**。
4、为了使效果更好看,我们给装图片的容器设置内边距,边框和阴影的效果。
<!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>waterFall</title>
    <script src="./index.js"></script>
</head>
<style>
    *{
        margin: 0;
        padding: 0;
    }
    .container {
        overflow: hidden;
        position: relative;
    }
    .box{
        float: left;
        padding: 5px;
    }
    .box-img{
        width: 150px;
        padding: 5px;
        border: 1px solid #484848;
        box-shadow: 0 0 5px #484848;
    }
    .box-img img{
        width: 100%;
    }
</style>
<body>
    <div id="container">
        <div class="box">
            <div class="box-img">
                <img src="./img/1.jpg" alt="">
           </div>
        </div>
        /*.......后面接39个box,此处省略*/
    </div>
    
</body>
</html>
二、js代码编写
1、首先用window.onload=function(){}来实现页面加载完毕后立即执行的功能
在这个函数中,我们调用imgLocation('container','box')函数来呈现最终效果,传入的实参是父容器'container'以及装图片的子容器'box'。

window.onload=function() {
    imgLocation('container','box')
}
复制代码
2、实现imgLocation()函数功能
1)首先我们得获取所有要摆放的图片,并将其存入一个数组中

利用document.getElementById(parent)得到父容器;

调用getChildElement(cparent,content)方法,在这个方法中,先获取父容器所有标签,对所有标签遍历并用if条件语句得到类名为box的容器(装图片的容器),将其存入我们自己构建的一个数组中,返回值为这个数组,方便之后通过遍历来操作图片摆放的位置。

function imgLocation(parent,content){
    var cparent=document.getElementById(parent)
    //cparent下的所有的第一层的子容器 box
    var ccontent=getChildElement(cparent,content)//数组,40个div
}

//取到父容器中的某一层子容器
function getChildElement(parent,content){
    var contentArr=[]
    var allContent=parent.getElementsByTagName('*')//通过标签来选中得到一个数组
    //遍历allContent把其中类名为content的容器都存到contentArr数组中
    for(var i=0;i<allContent.length;i++){
        if(allContent[i].className==content){    //当前这个容器的类名是否为content
            contentArr.push(allContent[i])
        }
    }
    return contentArr;

}
复制代码
2)得到这个数组后,找出从谁开始是需要被摆放位置的

我们采用以下方法:

首先获取视窗的宽度和每张图片的宽度,将两者相除并向下取整可得到第一行可以放置图片的数量,自然也就知道了我们需要操作的那张图片的序号。

    //从谁开始是需要被摆放位置的
    var winWidth=document.documentElement.clientWidth;//视窗宽度
    var imgWidth=ccontent[0].offsetWidth;//图片宽度

    var num=Math.floor(winWidth/imgWidth)//第一行能放几张图
复制代码
3)得到需要被摆放位置的图片序号后,确定其摆放位置

我们自己定义一个存储高度的数组,对前一行元素的高度进行遍历并存入数组,当遍历到需要被摆放位置的图片时,用Math.min()方法获取前一行高度最矮的元素高度,并用indexOf()方法获取到其下标。

再对我们所操作的这个图片容器的样式调整:
position:absolute;绝对定位, top值设置为前一行高度最矮的图片高度minHeight,left值设置为单张图片宽度乘这张图片的下标minIndex。

最后,不要忘啦,摆放好图片后,还要更新摆放的那一列的高度哟~

    //操作num+1张图
    var BoxHeightArr=[]
    for(var i=0;i<ccontent.length;i++){
        //前num张只要计算高度
        if(i<num){
            BoxHeightArr[i]=ccontent[i].offsetHeight
        }
        else{
            //我们要操作的box  :ccontent[i]
            var minHeight=Math.min.apply(null,BoxHeightArr)//apply:把最小值这个方法借给它用
            var minIndex=BoxHeightArr.indexOf(minHeight)//返回数组下标
            ccontent[i].style.position='absolute'//style设置样式
            ccontent[i].style.top=minHeight+'px'
            ccontent[i].style.left=imgWidth*minIndex+'px'

            //更新最矮的那一列的高度
            BoxHeightArr[minIndex]+=ccontent[i].offsetHeight
        }
    }
复制代码
最终整合一下就可以实现瀑布流的布局效果啦!
 //取到父容器中的某一层子容器
    function getChildElement(parent, content) {
      var contentArr = [];
      var allContent = parent.getElementsByTagName("*"); //通过标签来选中得到一个数组
      //遍历allContent把其中类名为content的容器都存到contentArr数组中
      for (var i = 0; i < allContent.length; i++) {
        if (allContent[i].className == content) {
          //当前这个容器的类名是否为content
          contentArr.push(allContent[i]);
        }
      }
      return contentArr;
    }
    function imgLocation(parent, content) {
      var cparent = document.getElementById(parent);
      //cparent下的所有的第一层的子容器 box
      var ccontent = getChildElement(cparent, content); //数组,40个div
      console.log(ccontent, "ccontent");
      //从谁开始是需要被摆放位置的
      var winWidth = document.documentElement.clientWidth; //视窗宽度
      var imgWidth = ccontent[0].offsetWidth; //图片宽度

      var num = Math.floor(winWidth / imgWidth); //第一行能放几张图
      var BoxHeightArr = [];
      for (var i = 0; i < ccontent.length; i++) {
        //前num张只要计算高度
        if (i < num) {
          BoxHeightArr[i] = ccontent[i].offsetHeight;
        } else {
          //我们要操作的box  :ccontent[i]
          var minHeight = Math.min.apply(null, BoxHeightArr); //apply:把最小值这个方法借给它用
          var minIndex = BoxHeightArr.indexOf(minHeight); //返回数组下标
          ccontent[i].style.position = "absolute"; //style设置样式
          ccontent[i].style.top = minHeight + "px";
          ccontent[i].style.left = imgWidth * minIndex + "px";

          //更新最矮的那一列的高度
          BoxHeightArr[minIndex] += ccontent[i].offsetHeight;
        }
      }
    }

    window.onload = function () {
      imgLocation("container", "box");
    };
 
 


TypeScript

TS 和 JS 的区别是什么?有什么优势?

  1. 语法层面 => TS = JS + Type。TS 就是 JS 的超集
  2. 执行环境层面 => 浏览器、NodeJS 可以直接执行 JS,但不能直接执行 TS
  3. 编译层面 => TS 有编译阶段。JS 没有编译阶段
  4. TS 类型更安全,IDE 可以进行提示

any、unknown、never 的区别是什么?

  1. any 和 unknown 都是顶级类型(top type),任何类型的值都可以赋值给顶级类型变量
    let foo: any = 123; // 不报错
    let bar: unknown = 123; // 不报错
    
  2. 但是 unknown 比 any 类型检查更严格,any 什么检查都不做,unknown 要求先收窄类型
    const value: unknown = "hello world";
    const str: string = value; // 报错:Type 'unknown' is not assignable to type 'string'.(2322)
    const str1: string = value as string; // 不报错
    
  3. 如果改成 any,基本在哪都不会报错。所以能用 unknown 就优先使用 unknown,类型更安全一点
  4. never 是底类型,表示不应该出现的类型
    interface Foo {
       type: 'foo'
    }
    interface Bar {
       type: 'bar'
    }
    
    type All = Foo | Bar;
    
    function handleValue(val: All) {
         switch (val.type) {
             case 'foo':
                 // 这里的 val 被收窄为 Foo
                 break;
             case 'bar':
                 // 这里的 val 被收窄为 Bar 
                 break;
             default:
                 // val 在这里是 never
                 const check: never = val;
                 break;
         }
    }
    
  5. 在 default 里把被收窄为 never 的 val 赋值给了一个显示声明为 never 的变量,如果一切逻辑正确,那么这里可以编译通过
  6. 某一天更改了 All 的类型 => type All = Foo | Bar | Baz
  7. 此时如果没有修改 handleValue,此时 default 会被收窄为 Baz,无法赋值给 never,此时会产生一个编译错误
  8. 通过这个方法,可以确保 handleValue 总是穷尽(exhaust)了所有 All 的可能类型

type 和 interface 的区别是什么?

  1. 组合方式 => interface 使用 extends 来实现继承。type 使用 & 来实现联合类型
  2. 扩展方式 => interface 可以重复声明用来扩展(merge)。type 一个类型只能声明一次
    interface Foo {
       title: string
    }
    interface Foo {
       content: string
    }
    
    type Bar = {
       title: string
    }
    
    // Error: Duplicate identifier 'Bar'
    type Bar = {
       content: string
    }
    
  3. 范围不同 => type 适用于基本类型。interface 被用于描述对象(declare the shapes of objects)
  4. 命名方式 => interface 会创建新的类型名。type 只是创建类型别名,并没有新创建类型

 并发任务控制

function timeout(time) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve()
    }, time);
  })
}

class SuperTask {
  constructor(parallelCount = 2) {
    this.parallelCount = parallelCount
    this.runningCount = 0
    this.tasks = []
  }
  add(task) {
    return new Promise((resolve, reject) => {
      this.tasks.push({
        task,
        resolve,
        reject
      })
      this.run()
    })
  }
  run() {
    while (this.runningCount < this.parallelCount && this.tasks.length > 0) {
      const {
        task,
        resolve,
        reject
      } = this.tasks.shift()
      this.runningCount ++
        task().then(resolve, reject).finally(() => {
          this.runningCount--
          this.run()
        })
    }
  }
}
const superTask = new SuperTask()

function addTask(time, name) {
  superTask.add(() => timeout(time))
    .then(() => {
      console.log(`任务${name}完成`);
    })
}

addTask(10000, 1) //10000ms 后输出:任务1完成
addTask(5000, 2) //5000ms 后输出:任务2完成
addTask(3000, 3) //8000ms 后输出:任务3完成
addTask(4000, 4) //1200ms 后输出:任务4完成
addTask(5000, 5) //1500ms 后输出:任务5完成
// 2,3,1,4,5

 

 

加水印:防篡改 MutationObserve 还可以实现微队列
主题跟随系统
DNS解析
intersetionObserve
任务并发
IIFE提升性能:正常每次都执行一次函数,立即执行函数会在刚开始就确定好用哪个
分块加载大数据
拼音输入法导致高频事件
let isComposite = false addEventListener('input',function () { if(!isComposite){ search() } }) addEventListener('compositionstart',function () { isComposite = true })
addEventListener('compositionend',function () { isComposite = false search() })
自动注入less全局变量:
webpack: css:loaderOptions:less:additionalData:"@import url("~@/var.less")"
vite: css:prepracessorOptions:less:additionalData:"@import url("~@/var.less")"
但是要配置:resolve:alias:'~@': path.resolve(__dirname,'./src')
posted @ 2023-04-24 10:52  前端菜园子  阅读(312)  评论(0编辑  收藏  举报