JS
JS 的数据类型
基本数据类型:number/boolean/string/null/undefined/Symbol/BigInt(任意精度的整数)
引用数据类型:Object
判断数据类型
- typeof => 返回一个字符串,表示操作数的类型instanceof => 在原型链中查找是否是其实例 => object instanceof constructor
- typeof null === 'object'
- typeof <function> === 'function'
- 判断是否是数组
- arr instanceof Array
- arr.constructor === Array
- Array.isArray(arr)
- 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 = {};但因为他们的内存地址不一样,比较结果依然不相等。
原型链是什么?
- case:const a = {},此时 a.proto == Object.prototype,即 a 的原型是 Object.prototype
- case:我们有一个数组对象,const a = [],此时 a.proto == Array.prototype,此时 a 的原型是 Array.prototype,此时 Array.prototype.proto == Object.prototype,此时:
- a 的原型是 Array.prototype
- a 的原型的原型是 Object.prototype
- 于是形成了一条原型链
- 可以通过 const x = Object.create(原型) 或者 const x = new 构造函数() 的方式改变 x 的原型
- const x = Object.create(原型) => x.proto == 原型
- const x = new 构造函数() => x.proto == 构造函数.prototype
- 原型链可以实现继承,以上面的数组为例:a ===> Array.prototype ===> Object.prototype原型链的优点在于:简单优雅
- a 是 Array 的实例,a 拥有 Array.prototype 里的属性
- Array 继承了 Object
- a 是 Object 的间接实例,a 也就拥有 Object.prototype 里的属性
- a 即拥有了 Array.prototype 的属性,也拥有了 Object.prototype 的属性
- 但是不支持私有属性,ES6新增加的 class 可以支持私有属性
代码中的 this 是什么?
- 将所有的函数调用转化为 call => this 就是 call 的第一个参数
- func(p1, p2) => func.call(undefined, p1, p2) => 如果 context 是 null 或 undefined,window 是默认的 context(严格模式下默认是 undefined)
- 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 的立即执行函数是什么?
- 声明一个匿名函数,然后立即执行它,这种做法就是立即执行函数
- 例如: 每一行代码都是一个立即执行函数在 ES6 之前只能通过立即执行函数来创建局部作用域
- (function() {} ())
- (function() {})()
- !function() {}()
- +function() {}()
- -function() {}()
- ~function() {}()
- 其优点在于兼容性好
- 目前可以使用 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 的闭包是什么?
- 闭包是 JS 的一种语法特性,闭包 = 函数 + 自由变量。对于一个函数来说,变量分为:全局变量、本地变量、自由变量
- case:闭包就是 count + add 组成的整体
const add2 = (function() { var count = 0; return function add() { count++; } })() // 此时 add2 就是 add add2(); // 相当于 add(); // 相当于 count++;
- 以上就是一个完整的闭包的应用
- 闭包解决了其优点是:简单好用
- 避免污染全局环境 => 因为使用了局部变量
- 提供对局部变量的间接访问 => 只能 count++,不能 count--
- 维持变量,使其不被垃圾回收
- 但是闭包使用不当可能造成内存泄漏。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 了
- 对于正常的浏览器来说,y会在一段时间内自动消失,被垃圾回收器回收,但是旧版本的 IE 浏览器不会回收,这是 IE 浏览器的问题
JS 如何实现类
- 使用原型
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();
- 使用类
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 实现继承
- 使用原型链
// 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);
- 使用类
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 手写节流 & 防抖
- 节流 throttle => 技能冷却中 => 场景 固定频率执行
- Select 去服务端动态搜索
- 按钮用户点击过快,发送多次请求
// 每隔一段时间执行 (后执行),
先执行一次,
每隔一段时间再执行
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);
- 防抖 debounce => 回城被打断 => 场景 多久只运行一次
- 滚动事件
- 窗口大小改变
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
- 要在 Promise 上写而不是在原型上写
- Promise.all 参数(Promise 数组)和返回值(新 Promise 对象)
- 用数组记录结果
- 只要有一个 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));
缺点:
- 不支持 Date、正则、undefined、函数等数据
- 不支持引用,即环状结构
递归。要点:
- 判断类型
- 检查环
- 不拷贝原型上的属性
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 事件模型
- 先经历从上到下的捕获阶段,再经历从下到上的冒泡阶段
- addEventListener("click", fn, options, useCapture)可以使用 event.stopPropagation() 来阻止捕获或冒泡
- options 中有一个 capture 参数,true 表示捕获阶段,false 表示冒泡阶段
- useCapture true 表示捕获阶段,false 表示冒泡阶段
手写事件委托
ul.addEventListener('click', (e) => {
if (e.target.tagName.toLowerCase() === 'li') {
// do something
}
});
- 如果点击 li 里面的 span,就没有办法触发事件
- 点击元素之后,递归遍历点击元素的祖先元素直至遇到 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));
- 事件委托优点事件委托缺点 => 调试比较复杂,不容易确定监听者
- 节省监听器
- 实现动态监听
手写可拖曳 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容器再装入每张图片,这样方便之后样式的编写。<!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 的区别是什么?有什么优势?
- 语法层面 => TS = JS + Type。TS 就是 JS 的超集
- 执行环境层面 => 浏览器、NodeJS 可以直接执行 JS,但不能直接执行 TS
- 编译层面 => TS 有编译阶段。JS 没有编译阶段
- TS 类型更安全,IDE 可以进行提示
any、unknown、never 的区别是什么?
- any 和 unknown 都是顶级类型(top type),任何类型的值都可以赋值给顶级类型变量
let foo: any = 123; // 不报错 let bar: unknown = 123; // 不报错
- 但是 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; // 不报错
- 如果改成 any,基本在哪都不会报错。所以能用 unknown 就优先使用 unknown,类型更安全一点
- 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; } }
- 在 default 里把被收窄为 never 的 val 赋值给了一个显示声明为 never 的变量,如果一切逻辑正确,那么这里可以编译通过
- 某一天更改了 All 的类型 =>
type All = Foo | Bar | Baz
- 此时如果没有修改 handleValue,此时 default 会被收窄为 Baz,无法赋值给 never,此时会产生一个编译错误
- 通过这个方法,可以确保 handleValue 总是穷尽(exhaust)了所有 All 的可能类型
type 和 interface 的区别是什么?
- 组合方式 => interface 使用 extends 来实现继承。type 使用 & 来实现联合类型
- 扩展方式 => interface 可以重复声明用来扩展(merge)。type 一个类型只能声明一次
interface Foo { title: string } interface Foo { content: string } type Bar = { title: string } // Error: Duplicate identifier 'Bar' type Bar = { content: string }
- 范围不同 => type 适用于基本类型。interface 被用于描述对象(declare the shapes of objects)
- 命名方式 => 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