- 概念题 => 是什么 + 怎么做 + 解决了什么问题 + 优点 + 缺点 + 怎么解决缺点
HTML
如何理解 HTML 中的语义化标签
- 语义化标签是一种写 HTML 标签的方法论
- 实现方法是遇到标题就用 h1 到 h6,遇到段落用 p,遇到文档用 article,主要内容用 main,边栏用 aside,导航用 nav
- 它主要是明确了 HTML 的书写规范
- 优点在于 1. 适合搜索引擎检索 2. 适合人类阅读,利于团队维护
HTML5 有哪些新标签
文章相关:header、main、footer、nav、section、article
多媒体相关:video、audio、svg、canvas
Canvas 和 SVG 的区别是什么?
- Canvas 主要是用笔刷来绘制 2D 图形的
- SVG 主要是用标签来绘制不规则矢量图的
- 相同点:都是主要用来画 2D 图形的
- 不同点
- SVG 画的是矢量图,Canvas 画的是位图
- SVG 节点多时渲染慢,Canvas 性能更好一点,但写起来更复杂
- SVG 支持分层和事件,Canvas 不支持,但是可以用库实现
CSS
BFC 是什么
BFC 是 Block Formatting Context,是块级格式化上下文。以下可以触发 BFC
- 浮动元素(float 值不为 none)
- 绝对定位元素(position 值为 absolute 或 fixed)
- inline-block 行内块元素
- overflow 值不为 visible、clip 的块元素
- 弹性元素(display 值为 flex 或 inline-flex 元素的直接子元素)
BFC 可以解决 1. 清除浮动 2. 防止 margin 合并 的问题
但是它有相应的副作用,可以使用最新的 display: flow-root 来触发 BFC,该属性专门用来触发 BFC
如何实现垂直居中
- flex
- position + transform
CSS 选择器优先级如何确定
- 选择器越具体,其优先级越高
- 相同优先级,出现在后面的,覆盖前面的
- 属性后面加 !important 的优先级最高,但是要少用
如何清除浮动
.clearfix:after {
content: '';
display: block;
clear: both;
}
两种盒模型区别
- content-box => width 和 height 只包含内容的宽和高,不包括边框和内边距。case:{width: 350px, border: 10px solid red;} 实际宽度为 370
- border-box => width 和 height 包含内容、内边距和边框。case:{width: 350px, border: 10px solid red;} 实际宽度为 350
JS
JS 的数据类型
基本数据类型:number/boolean/string/null/undefined/Symbol/BigInt(任意精度的整数)
引用数据类型:Object
判断数据类型
- typeof => 返回一个字符串,表示操作数的类型
- typeof null === 'object'
- typeof <function> === 'function'
- instanceof => 在原型链中查找是否是其实例 => object instanceof constructor
- 判断是否是数组
- arr instanceof Array
- arr.constructor === Array
- Array.isArray(arr)
- Object.prototype.toString.call(arr) === '[object Array]'
原型链是什么?
- 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 的立即执行函数是什么?
- 声明一个匿名函数,然后立即执行它,这种做法就是立即执行函数
- 例如: 每一行代码都是一个立即执行函数
- (function() {} ())
- (function() {})()
- !function() {}()
- +function() {}()
- -function() {}()
- ~function() {}()
- 在 ES6 之前只能通过立即执行函数来创建局部作用域
- 其优点在于兼容性好
- 目前可以使用 ES6 的 block + let 代替
{ let a = '局部变量'; console.log(a); // 局部变量 } console.log(a); // Uncaught ReferenceError: a is not defined
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) {
let flag = true;
return (...args) => {
if (flag) {
flag = false;
callback(args);
setTimeout(() => {
flag = true;
}, time);
}
}
}
const fn = throttle(2000, () => {console.log("Hello!")});
fn();
fn();
setTimeout(fn, 3000);
- 防抖 debounce => 回城被打断 => 场景
- 滚动事件
function debounce(time, callback) {
let timer;
return (...args) => {
timer && clearTimeout(timer);
timer = setTimeout(() => {
callback(args);
}, time);
}
}
const fn = debounce(2000, () => console.log("Hello!"));
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
class Promise2 {
#status = 'pending';
constructor(fn) {
this.queue = [];
const resolve = (data) => {
this.#status = 'fulfilled';
const f1f2 = this.queue.shift();
if (!f1f2 || !f1f2[0]) return;
const x = f1f2[0].call(undefined, data);
if (x instanceof Promise2) {
x.then(data => resolve(data), reason => reject(reason));
} else {
resolve(x);
}
}
const reject = (reason) => {
this.#status = 'rejected';
const f1f2 = this.queue.shift();
if (!f1f2 || !f1f2[1]) return;
const x = f1f2[1].call(undefined, reason);
if (x instanceof Promise2) {
x.then(data => resolve(data), reason => reject(reason));
} else {
resolve(x);
}
}
fn.call(undefined, resolve, reject);
}
then(f1, f2) {
this.queue.push([f1, f2]);
}
}
const p = new Promise2((resolve, reject) => {
setTimeout(() => {
reject('Error!');
}, 3000);
});
p.then((data) => console.log(data), error => console.error(error)