胖蔡说技术
随便扯扯

JS 通过sort方法实现排序功能

胖蔡阅读(519)

JSArray提供了sort()方法用于辅助我们实现数组的排序功能,其使用语法格式如下所示:

语法:
arr.sort([compareFunction])
参数:
compareFunction [可选]
用于数组排序规则的比较函数。如果不含有该参数,数组元素按照转换字符串的各个字符的Unicode编码顺序进行排序。
    compareFunction 参数:
    firstElement   用于比较的第一个元素
    secondElement  用于比较的第二个元素
返回值:
排序后的数组,返回的是当前数组。

使用

1、sort()默认可以实现将数组中的单项转入Unicode编码进行排序,如下:

let arr = ['cai',01,2,3,-1]
arr.sort()
console.log(arr)
// [-1, 1, 2, 3, 'cai']

2、自定义实现排序,使用compareFunction函数实现自定义排序,函数返回值:

  • 负数:顺序不变
  • 零:顺序不变
  • 大于零:交换位置

代码示例如下:

let arr = [311,43,54,4,40,26,31,33];
arr.sort(() => -1);
console.log(arr); // [33, 31, 26, 40, 4, 54, 43, 311]

[前端面试题] JS面试题(三)

胖蔡阅读(437)

1、JS事件循环(Event Loop)

JS的事件循环可以说是javascript的基础,JS有一个调用堆栈,当前执行上下文有待执行的函数或者全局有待执行的任务会放在这个调用堆栈,我们的JS在主线程执行的时候,会先去调用这个堆栈中的方法和任务,当堆栈中任务执行完后,就会去调用异步任务队列中的任务,将异步任务放入调用堆栈中执行。

  • 调用堆栈:当前待执行的任务,属于执行预备军
  • 异步执行任务:所有的异步任务都会放在这

在异步任务中又分为微任务和宏任务,一般的线程会先调用一个宏任务,然后再去任务队列中将所有微任务调用完成,然后再去调用宏任务,如此往复循环,形成一个JS的事件循环机制。

2、JS微任务和宏任务

JS本身是一个单线程的语言,所以它本身不可能异步,但JS宿主环境(浏览器、nodejs)是多线程的,宿主环境通过事件驱动使得JS具备了异步的属性。JS中我们一般讲任务分为两类:同步任务、异步任务

其中异步任务中有分为:宏任务、微任务。

  • 宏任务,常见如:setTimeoutsetIntervalDOM事件、ajax请求
  • 微任务,常见如:

流程:同步==》事件循环【微任务和宏任务】==》微任务==》宏任务=》微任务..

微信小程序工具:实现图片压缩

胖蔡阅读(605)

最近在写一个工具小程序,辅助实现一些常用功能,正好趁机会记录一下小工具实现的过程,希望可以凑一个系列的文章。今天我分享的是如何在微信小程序中实现图片压缩的功能。

工具使用流程

工具使用比较简单,使用步骤如下:

  1. 选择或者拍摄手机图片
  2. 选择压缩质量
  3. 实现压缩
  4. 保存压缩后的图片到手机中

功能实现

根据如上操作步骤,我们可以来一一拆分功能并实现操作。

一、选择图片

微信小程序提供了一个选择图片的wx.chooseMedia,实现代码如下:

    chooseImage(e) {
        // 选择图片
        wx.chooseMedia({
            count: 1,
            mediaType: ['image'],
            sourceType: ['album', 'camera'],
            sizeType: ['compressed'],//图片不能经过压缩处理
            camera: 'back',
            success: (res) => {
                const file = res.tempFiles[0];
                this.setData({
                    file,
                })

            }
        })
    }

二、实现压缩图片

压缩图片我们可以使用微信小程序提供的wx.compressImage,代码实现如下:

    compressImage() {
        // 压缩图片
        wx.showToast({
            title: "正在压缩图片",
            icon: "loading",
        })
        wx.compressImage({
            src: this.data.file.tempFilePath,
            quality: this.data.quality,
            success: (res) => {
                // console.log('保存后地址:', res)

            },
            fail: (res) => {
                console.log('get fail res:', res)
                wx.showToast({
                    title: '压缩失败',
                    icon: 'none'
                })
            }
        })
    }

三、保存图片到手机相册

使用的是微信小程序的wx.saveImageToPhotosAlbum,代码如下:

 wx.saveImageToPhotosAlbum({
                    filePath: res.tempFilePath,
                    success: function (res) {
                        wx.showToast({
                            title: "压缩成功,已保存至相册",
                            icon: 'success',
                            duration: 3000,
                        });
                    },
                    fail: function () {
                        wx.showToast({
                            title: '压缩失败',
                            icon: 'none'
                        })
                    }

                })

CSS修改滚动条样式:scrollbar

胖蔡阅读(481)

前端页面中的列表数据超出屏幕,当我们给外层容器设置了overflow:scroll , 就可以通过滚动条滚动显示内容,但是web自带的滚动条样式不太美观,这时候我们就可以通过CSS设置来美化滚动条。

可以通过如下设置修改滚动条:

  • ::-webkit-scrollbar :可以设置滚动条的大小、颜色、圆角
  • ::-webkit-scrollbar-thumb:设置滚动条的滑块
  • ::-webkit-scrollbar-track:设置滚动条轨道(滑块可滑动区域)
  • ::-webkit-scrollbar-track-piece:设置没有滑块的轨道部分
  • ::-webkit-scrollbar-corner:设置水平和垂直滚动条交叉部分的背景色。
  • ::-webkit-resizer:当元素设置为可拖动时(resize: horizontal | vertical | both),元素底角会出现可调整元素大小的滑块。
  • ::webkit-scrollbar-button:设置滚动条两端的上下(左右)滚动按钮(上下、左右箭头)。
/* 修改滚动条样式 */
/* 设置滚动条 */
::-webkit-scrollbar {
  width: 10px;
  height: 10px;
  background-color: #f5f5f5;
  border-radius: 5px;
}
 
/* 设置滚动条滑块 */
::-webkit-scrollbar-thumb {
  background-color: #ccc;
  border-radius: 5px;
}
 
/* 设置滚动条滑块在悬停状态下的背景色和圆角 */
::-webkit-scrollbar-thumb:hover {
  background-color: #999;
  border-radius: 5px;
}
 
/* 设置滚动条轨道的背景色和圆角 */
::-webkit-scrollbar-track {
  background-color: #f5f5f5;
  border-radius: 5px;
}
 
/* 设置滚动条轨道在悬停状态下的背景色和圆角 */
::-webkit-scrollbar-track:hover {
  background-color: #ccc;
}

::-webkit-scrollbar-corner{
  background-color: cyan;
}

::-webkit-scrollbar-track-piece{
  background-color: green;
}


::-webkit-resizer{
  background-color: rgb(242, 5, 151);
}
::-webkit-scrollbar-button{
  background-color: pink;
  border-radius: 20px;
  width: 20px;
}

我们在百度统计的页面添加后,修改后显示的滑动条如下图所示:

TypeScript中import 和import type的区别

胖蔡阅读(452)

今天在看ant-design-vue源码的时候发现在代码中大量使用了importimport type,于是我就比较好奇他们到底有什么不同,但是查看了TypeScript手册,可能是比较基础的知识,并未对其进行解说。最终,经过我的不懈了解,终于了解这两种的区别。

如上,查看代码发现,通过import type导入的其本身也是通过export type导出的。

当我们通过import type导出后使用,如下:

// a.ts
export enum MyEnum {
  DEFAULT = 0,
  SOME_VALUE = 1,
  SOME_OTHER_VALUE = 2,
}

//b.ts
import type { MyEnum } from './b';

const someFunction = (myEnum: MyEnum) => {
  if (myEnum === MyEnum.SOME_VALUE) {
  	// some logic here
    return
  }
  if (myEnum === MyEnum.SOME_OTHER_VALUE) {
    // some logic here
    return
  }
  // some logic here
  return
}

这时编译器突然报错:

'MyEnum' cannot be used as a value because it was imported using 'import type'.ts(1361)

字面意思就是MyEnum是通过import type导出的,所以不能作为一个正常的值来使用。那么,问题来了,我们应该什么时候使用import type呢?

依据

通过反复查阅资料发现这时TypeScript 3.8新增的功能,其原文如下:

import type only imports declarations to be used for type annotations and declarations. It always gets fully erased, so there’s no remnant of it at runtime. Similarly, export type only provides an export that can be used for type contexts, and is also erased from TypeScript’s output.

https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html

大概意思就是import type是TypeScript 3.8新增的一种用于仅类型的导入和导出的语法。它在运行时是完全不存在的。主要是辅助我们做类型校验的。

总结

通过上述的了解,总结其特点如下:

  • import type 仅仅引入类型信息,不会引入实际的对象,而 import 会引入实际的 JavaScript 对象。
  • 使用 import type 时,无法使用导入的类型作为值进行实例化或传递给函数。
  • import type 只用于导入类型声明,例如接口、类型别名、枚举等。而 import 可以用于导入值、函数、类等任何东西。
// foo.ts
export interface Foo {
  bar: string;
}

// index.ts
import { Foo } from './foo'; // 这里导入了实际的 Foo 接口对象
import type { Foo as FooType } from './foo'; // 这里导入了 Foo 接口的类型信息

const foo: Foo = { bar: 'baz' }; // 正常使用 Foo 接口对象
const fooType: FooType = { bar: 'baz' }; // 无法使用 Foo 接口的类型信息创建一个对象,TypeScript3.8以前的版本这里会报错。3.8之后的版本就不会报错了

当我们只需要类型信息时,应该使用 import type。这样可以避免引入多余的代码,并提高编译性能。而当你需要实际的 JavaScript 对象时,则需要使用 import

[前端面试题] JS面试题(二)

胖蔡阅读(459)

1、什么是变量提升?如何避免变量提升?

JS在编译阶段的时候,会搜集所有的变量声明并且提前声明变量,而其他的语句都不会改变他们的顺序,因此,在编译阶段的时候,第一步就已经执行了,而第二步则是在执行阶段执行到该语句的时候才执行。。可以使用letconst等关键字来避免变量提升,因为这些关键字声明的变量不会被自动提升。

2、简单说一说let、const、var的区别

  • es6提出来let、const,而vares5提出的
  • letvar 声明的是变量,声明之后可以更改,声明时可以不赋值,const 声明的是常量,必须赋值
  • var 允许重复声明变量,后一个变量会覆盖前一个变量。let const 在同一作用域不允许重复声明变量,会报错。
  • var 不存在块级作用域,var 是函数作用域。let const 存在块级作用域。
  • let const 不存在变量提升,var 声明的变量存在变量提升

3、JS如何实现继承?有哪些实现方法?

以使用原型链、构造函数、组合继承、寄生组合继承等方式来实现继承。

  • 使用原型链:通过指定类的prototype原型为指定构造函数,让新实例的原型等于父类的实例。通过原型特性实现继承。原型上的属性是共享的,一个实例修改了原型属性,另一个实例的原型属性也会被修改!
function Parent() {
  this.name = 'Tom';
}
function Child() {}
Child.prototype = new Parent();
var child = new Child();
console.log(child.name); // 输出'Tom'
  • 使用构造函数:通过修改this指向将父类属性指向子类(使用call、apply),只继承了父类构造函数的属性,没有继承父类原型的属性。每个新实例都有父类构造函数的副本,臃肿。无法实现构造函数的复用。
function Parent() {
  this.name = 'Tom';
}
function Child() {
  Parent.call(this);
}
var child = new Child();
console.log(child.name); // 输出'Tom'
  • 使用组合继承:使用构造和原型组合方式实现,可以继承父类原型上的属性,可以传参,可复用。每个新实例引入的构造函数属性是私有的。调用了两次父类构造函数(耗内存),子类的构造函数会代替原型上的那个父类构造函数。
function Parent() {
  this.name = 'Tom';
}
function Child() {
  Parent.call(this);
}
Child.prototype = new Parent();
var child = new Child();
console.log(child.name); // 输出'Tom'
  • 使用寄生组合继承
function Parent() {
  this.name = 'Tom';
}
function Child() {
  Parent.call(this);
}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
var child = new Child();
console.log(child.name); // 输出'Tom'

4、解释JavaScript中的this关键字

在JavaScript中,this关键字表示当前执行上下文环境的所有者。在全局上下文环境中,this指向的是window对象。在函数中,this的值取决于函数的调用方式。

  • 若函数是作为一个方法调用的,this就指向这个方法所属的对象。
  • 若函数是作为函数调用的,this就指向全局上下文环境。
  • 在严格模式下,函数的this值为undefined

5、解释一下JavaScript中的严格模式(Strict Mode)是什么,以及它的作用?

严格模式(Strict Mode)JavaScript中的一种执行模式,它可以通过在脚本或函数的顶部添加特定语句来启用。严格模式引入了一些限制和变化,以帮助开发者编写更规范、更安全的JavaScript代码。

  • 消除了一些不合理或不安全的语法和行为:在严格模式下,一些以前被视为有效但容易导致错误的语法或行为会被禁止,从而帮助开发者避免潜在的问题。
  • 提升代码的可读性和维护性:严格模式要求变量声明必须使用关键字(如varletconst),这样可以避免意外创建全局变量,提高代码的可读性和可维护性。
  • 阻止使用一些不推荐的功能:在严格模式下,一些过时的或不推荐使用的功能被废弃或禁止,例如使用with语句、使用arguments.callee访问函数自身等。
  • 提升JavaScript引擎的优化能力:严格模式下的代码通常比非严格模式下的代码更容易进行静态分析和优化,这可以使JavaScript引擎更好地执行代码,提高性能。

要在整个脚本中启用严格模式,可以在脚本的顶部添加如下语句:"use strict";要在函数内部启用严格模式,可以在函数体的顶部添加如下语句:

function myFunction() {
  "use strict";
  // 函数体中的代码将以严格模式执行
}

6、解释JavaScript中的事件委托

JS的事件委托又称之为事件代理,是一种在父元素上监听事件,然后通过事件冒泡机制来处理子元素的事件的技术。通过事件委托,可以避免为每个子元素都绑定事件处理程序,提高性能并简化代码。

事件委托的实现原理是基于事件冒泡机制。当一个元素上发生了某个事件(如点击事件),事件会从触发元素开始向上冒泡,直到根节点。事件委托利用了事件冒泡的特性,将事件处理程序绑定在父元素上,通过事件冒泡捕获子元素的事件。

  • 给父元素绑定事件
  • 给父元素定义事件,监听子元素的冒泡事件 
  • 找到是哪个子元素的事件,通过匿名回调函数的参数e用来接收事件对象,通过target获取触发事件的目标

当有多个列表元素需要绑定事件时,一个一个去绑定即浪费时间,又不利于性能,这时候就可以用到事件委托,给他们的一个共同父级元素添加一个事件函数去处理他们所有的事件情况。

在 javascript 中,页面中事件处理程序的数量与页面整体性能直接相关。每个函数都是对象,都占用内存空间,对象越多,性能越差。为指定事件处理程序所需访问 DOM 的次数会造成整个页面交互的延迟。使用事件委托可以减少内存的消耗和DOM操作,提高性能,节省花在设置页面事件处理程序上的时间。指定一个事件处理程序可以节省 DOM 引用,也可以节省时间。

注意:事件委托是基于事件冒泡的基础上实现的,若事件不支持冒泡则无法实现委托,常见不支持冒泡的事件如下:

  • 焦点事件:focus、blur
  • 鼠标事件: mouseenter、mouseleave
  • UI事件:load、unload、scroll、resize

7、undefined和null的区别是什么?

undefined 表示一个变量已声明但未初始化,或者不存在该变量。null 表示一个变量已经定义,但值为null。另外,在 JavaScriptundefined 是一个全局变量,而 null 是一个关键字。

8、箭头函数的特点?

  • 不需要 function 关键字来创建函数
  • 省略 return 关键字
  • 改变 this 指向

9、简单介绍下原型和原型链

  • 原型(Prototype):每个 JavaScript 对象都有一个原型(prototype)属性,该属性指向另一个对象,称为原型对象。原型对象本身也有一个原型,通过这种方式形成了原型链。原型对象可以包含共享的属性和方法,在对象需要访问或调用这些属性和方法时,会沿着原型链进行查找。
  • 原型链(Prototype Chain):原型链指的是通过原型对象的引用关系,将对象连接起来形成的链表结构。当访问一个对象的属性或方法时,如果该对象本身没有这个属性或方法,JavaScript 引擎会沿着原型链向上查找,直到找到该属性或方法或者到达原型链的末端(null

10、简单介绍下Promise

Promisees6 引入异步编程新解决方案,语法上 Promise 是一个构造函数,用来封装异步操作并可以获取其成功或失败的结果。

Promise构建出来的实例存在以下方法: then() 是实例状态发生改变时的回调函数,第一个参数是resolved 状态的回调函数,第二个参数是 rejected 状态的回调函数 catch() 用于指定发生错误时的回调函数 finally() 用于指定不管 Promise 对象最后状态如何,都会执行的操作,Promise 有三种状态:pending(进行中),fulfilled(已成功),reject(已失败)

11、Localstorage、SessionStorage、cookie有什么区别?

一、Cookie

  • 存储量太小,只有4kb
  • 所有 http 请求都带着,会影响获取资源的效率
  • api 简单,需要封装才能用 document.cookie
  • 本身用于客户端和服务端通信

二、localStorage、sesseionStorage

  • 为存储而设计,最大容量5M
  • ios safari隐藏模式下:localStorage.getItem会报错,建议统一使用try-catch封装
  • api简单易用
  • sessionStorage用于本地存储一个会话(session)中的数据,只有在同一个会话中的页面才能访问并且当会话结束后数据也随之销毁
  • locaStorage是一个持久化的存储,除非主动删除数据,否则数据是永远不会过期的。

12、什么是模块化? 如何在 JavaScript 中使用模块化?

模块化是指将代码分解为独立的模块,并通过导出和导入来组合这些模块的过程。JavaScript 中的模块化可以使用 CommonJS、AMD ES6 模块等规范来实现。

13、==和===有什么不同?  

  •  比较的是值:  string == number || boolean || number ....都会隐式转,换 通过 valueOf 转换(valueOf() 方法通常由 JavaScript 在后台自动调用,并不显式地出现在代码中。)
  • === : 除了比较值,还比较类型

[前端面试题] JS面试题(一)

胖蔡阅读(530)

一、谈谈你对回流和重绘的理解?

  • 回流:当一个元素自身的宽高,布局,显示或隐藏,或元素内部的文字结构发生变化,导致需要重新构建页面的时候,就产生了回流复制
  • 重绘:当一个元素自身的宽高,布局,及显示或隐藏没有改变,而只是改变了元素的外观风格的时候,就产生了重绘。

当元素添加或者删除可见的DOM元素,当元素的位置发生改变,元素的尺寸发生改变,内容改变和页面第一次渲染的时候页面会进行回流。

当改变元素的color、background、box-shadow、visibility、background-size的时候页面会进行重绘。

二、JS数据类型有哪些?

基本数据类型:

  • Number
  • String
  • Boolean
  • Null
  • Undefined
  • Symbol
  • bigInt

引用数据类型:

  • object
  • Array
  • Date
  • Function
  • RegExp

三、如何判断js的数据类型?

  • typeof:可以用来区分除了 Null 类型以外的原始数据类型,对象类型的可以从普通对象里面
  • instanceofA instanceof B 可以判断A是不是B的实例,返回一个布尔值,由构造类型判断出数据类型。
  • 通过Object下的toString.call()方法来判断:Object.prototype.toString.call(true); // => "[object Boolean]"
  • 根据对象的contructor判断:arr.constructor === Array
  • 通过Jquery中判断数据类型:jQuery.isArray()

四、JS中创建对象的几种方法

1、{} 或者 new Object()

就是先创建对象,后通过赋值法给对象添加属性,如下:

 <script>
        'use strict'; //使用strict模式
        /**
        使用{}创建对象,等同于 new Object();
        **/
        var o = {};

        o.name = '胖蔡';
        o.sayName = function(){
            alert(this.name);
        }

        alert(o.name+'-'+o.age);

        o.sayName();

    </script>

2、使用字面量

象字面变量是对象定义的一种简写形式,举个例子:var person = {name: 'zhang', age:20}, 这就是字面量形式,完全等价于var person = {}; person.name='zhang'; person.age=20;

3、工厂模式创建对象

通过函数来创建对象,和上述并没有本质区别:

<script>
        'use strict';

        // 使用工厂模式创建对象
        // 定义一个工厂方法
        function createObject(name){
            var o = new Object();
            o.name = name;
            o.sayName = function(){
                alert(this.name);
            };
            return o;
        }

        var o1 = createObject('zhang');
        var o2 = createObject('li');

        //缺点:调用的还是不同的方法
        //优点:解决了前面的代码重复的问题
        alert(o1.sayName===o2.sayName);//false

    </script>

4、构造函数模式(constructor)

使用构造函数实现创建对象:

 <script>
        'use strict';

        /**
         *  构造函数模式创建对象
         **/
        function Person(name){
            this.name = name;
            this.sayName = function(){
                alert(this.name);
            };
        }

        var p1 = new Person('zhang');
        var p2 = new Person('li');

        p1.sayName();
        p2.sayName();

        alert(p1.constructor === p2.constructor);//true
        alert(p1.constructor === Person);//true

        alert(typeof(p1));//object

        alert(p1 instanceof Object); //true
        alert(p2 instanceof Object); //true

        alert(p1.sayName===p2.sayName);//false

    </script>

5、原型模式(prototype)

每个方法中都有一个原型(prototype),每个原型都有一个构造器(constructor),构造器又指向这个方法。

  <script>
        'use strict';

        /*
         *  原型模式创建对象
         */
        function Animal() { }

        Animal.prototype.name = 'animal';
        Animal.prototype.sayName = function () { alert(this.name); };

        var a1 = new Animal();
        var a2 = new Animal();

        a1.sayName();

        alert(a1.sayName === a2.sayName);//true
        alert(Animal.prototype.constructor);//function Animal(){}
        //修改a2.name,a1的name不会变
        a2.name = 'dog';
        a2.sayName();//dog
        a1.sayName();//animal
    </script>

这个方法和上述类似,对象的创建均是使用构造函数实现的,但对象属性是通过 JS 的原型来进行创建的。通过原型创建对象,把属性和方法绑定到prototype上,通过这种方式创建对象,方法是共享的,每个对象调用的是同一个方法。

如果往新建的对象中加入属性,那么这个属性是放在对象中,如果存在与原型同名的属性,也不会改变原型的值。但是访问这个属性,拿到的是对象的值。

访问的顺序:对象本身>构造函数的prototype

如果对象中没有该属性,则去访问prototype,如果prototype中没有,继续访问父类,直到Object,如果都没有找到,返回undefined

6、构造函数+原型模式

这种方式结合上面两种方法,代码如下:

 <script>
        'use strict';

        function Animal(name){
            this.name = name;
            this.friends = ['dog','cat'];
        }
        Animal.prototype.sayName = function(){
            alert(this.name);
        };
        var a1 = new Animal('d');
        var a2 = new Animal('c');
        a1.friends.push('snake');
        alert(a1.friends);//[dog,cat,snake]
        alert(a2.friends);//[dog,cat]

  </script>

五、简单介绍下JS闭包,什么是函数柯里化?

闭包是指函数可以访问其外部上下文中定义的变量,即使这些变量在闭包函数执行时已经销毁。闭包可以创建私有变量并且可以在不同的函数调用中维持它们的值。在JavaScript中,每次函数调用都会创建一个新的执行上下文环境,闭包需要在其执行环境中创建一个持久化环境以保存其外部上下文的状态。

function counter() {
  let count = 0;
  return function increment() {
    count++;
    console.log(count);
  }
}
var c = counter();
c(); // 输出1
c(); // 输出2
c(); // 输出3

函数柯里化是通过将一个多参数的函数转换成一个多个单参数函数的过程,例如将函数f(x,y)转换成函数f(x)(y)。这个过程可以用闭包来实现。柯里化的用途包括简化函数调用、提供默认参数值以及延迟求值等。示例代码如下:


    function fn(a, b) {
      return function inner(type) {
        let res
        switch (type) {
          case '+': res = a + b; break;
          case '-': res = a - b; break;
          case '*': res = a * b; break;
          case '/': res = a / b; break;
        }
        return res
      }
    }

    // f 接受到的就是 inner 函数
    var f = fn(100, 200)
    console.log(f('+'))
    console.log(f('-'))
    console.log(f('*'))
    console.log(f('/'))

由于闭包的内部函数可以访问函数外部参数,所以有可能导致本该回收的变量没有被回收,导致内存泄漏,所以不能滥用闭包。

六、JS中的作用域是什么?作用域链又是什么?

代码(变量)可以使用的范围就是作用域。主要是为了提高程序的可靠性,也是为了减少命名冲突:

  • 全局作用域:指的是整个js文件,定义在全局中的全局变量,可以在局部作用域中使用,函数内部没有声明直接赋值的变量也叫全局变量
  • 局部作用域:主要指的是函数作用域,函数内部也就是局部作用域

在函数内部var定义的变量,叫做局部变量,局部变量无法被外部获取。

作用域链是指JavaScript在查找变量的时候,先从当前作用域开始查找,如果没有找到则向上一级作用域查找,直到找到为止。理解作用域链可以帮助我们更好地理解变量的作用范围及可访问性。例如,当我们在一个函数中访问一个变量时,JavaScript会先查找当前函数内的变量,如果没有找到则向上一级作用域查找,直到找到为止。如果一直找到全局作用域还没有找到,则会返回undefined

七、如何修改this的指向?

this指针都是当前运行的函数执行上下文,也可以理解为一个变量。当为了实现的需求,我们需要对某些运行的代码修改this指向,这时就可以使用JavaScript中提供的修改this指向的三种方法:callapplybind

  • call、bind、apply 都是 JavaScript 中用于改变函数执行上下文(即 this 指向)的方法。
  • call apply 的作用是一样的,都是用来调用函数并且改变函数内部的 this 指向。区别在于传参的方式不同,call 的参数是一个一个传递的,而 apply 的参数是以数组的形式传递的。
  • bind 方法不会立即执行函数,而是返回一个新的函数,这个新的函数的 this 值被绑定到了指定的对象,调用时也可以传入参数。同时使用 bind 方法可以实现柯里化,即将函数转化为接收部分参数的函数。

八、什么是事件冒泡?怎么阻止事件冒泡?

当我们点击子元素触发父元素的事件,这种现象,我们叫做事件冒泡,即由子元素向祖先元素传播,就像气泡从水底上浮,我们可以使用event.stopPropagation();阻止事件冒泡。

  • event.stopPropagation(); // 阻止了事件冒泡,但不会阻击默认行为。
  • event.preventDefault(); // 阻止默认事件,比如a的跳转事件。

九、简单说下深拷贝和浅拷贝

  • 浅拷贝:基本数据类型拷贝的是值,引用数据类型拷贝的是地址
  • 深拷贝:引用数据类型拷贝的是开辟新地址中的值

使用场景可以在数据传值的时候,当需要进行数据传值的时候,使用...或者Object.assign进行数据复制的时候,使用的就是浅拷贝,通过JSON.stringify、JSON.parse或者直接通过对象遍历进行实现深拷贝。

十、防抖与节流

  • 防抖:前面的所有的触发都被取消,最后一次执行在规定的时间之后才会触发,也就是说如果连续快速的触发,只会执行最后一次。
  • 节流:在规定的间隔时间范围内不会重复触发回调,只有大于这个时间间隔才会触发回调,把频繁触发变为少量触发。

防抖的原理是:在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。节流的原理是:规定在一个单位时间内,只能触发一次函数,可以配合setTimeout、clearTimeout来使用。如果这个函数时间触发多次函数,只有一次有效。和防抖函数差不多。

十一、请解释一下 JavaScript 的同源策略

JavaScript的同源策略(Same-Origin Policy)是一种安全机制,用于保护Web浏览器中的文档对象模型(DOM)免受恶意网站的跨域攻击。同源策略要求在访问Web页面中的资源时,只能与来源相同的资源进行交互,来源由协议、主机名和端口号组成。

具体来说,以下条件满足时,就认为两个URL具有相同的源:

  • 协议(Protocol):两个URL的协议必须相同,如http://http://,或https://https://
  • 主机名(Host):两个URL的主机名必须相同,如example.comexample.com
  • 端口号(Port):如果URL中指定了端口号,两个URL的端口号必须相同,如example.com:80example.com:80。如果URL中没有指定端口号,则默认为80(对于HTTP)或443(对于HTTPS)。

十二、什么是跨域?如何解决跨域?

跨域:指浏览器同源策略限制的请求。跨域解决方法如下:

1、jsonp

浏览器的script、img、iframe标签是不受同源策略限制的 ,所以通过script标签引入一个js文件,这个js文件载入成功后会执行我们在url参数中指定的callback函数,并把把我们需要的json数据作为参数传入。在服务器端,当req.params参数中带有callback属性时,则把数据作为callback的参数执行,并拼接成一个字符串后返回。

  • 兼容性好,在很古老的浏览器中也可以用,简单易用,支持浏览器与服务器双向通信。 
  • 只支持GET请求,且只支持跨域HTTP请求这种情况(不支持HTTPS

2、iframe

H5提供了postMessage()的方法,可以在父子页面进行通信,iframe窗口和window.open方法打开的窗口,它们与父窗口无法通信。HTML5为了解决这个问题,引入了一个全新的API:跨文档通信 API(Cross-document messaging)。这个APIwindow对象新增了一个window.postMessage方法,允许跨窗口通信,不论这两个窗口是否同源。

3、服务器配置CORS

服务器端通过检查请求头部的origin,从而决定请求应该成功还是失败。具体的方法是在服务端设置Response Header响应头中的Access-Control-Allow-Origin为对应的域名,实现了CORS(跨域资源共享),这里出于在安全性方面的考虑就是尽量不要用 *,但对于一些不重要的数据则随意,例如图片。
CORS需要后端配置,前端不需要处理。

4、中间件代理(nodejs等)

通过一个nodejs服务来进行跨域处理,网页数据直接通过中间件获取,这样获取的数据就都是同源的了。

5、websocket

websocket可以不需要考虑跨域问题,WebSocket 在建立连接时需要借助 HTTP 协议,连接建立好了之后 client server 之间的双向通信就与 HTTP 无关了。

十三、JavaScript中的垃圾回收机制

JavaScript中的内存回收机制主要依靠自动垃圾回收(Garbage Collection)来管理内存。垃圾回收器定期扫描内存中的对象,并标记那些不再被引用的对象,然后释放它们所占用的内存空间。

  • 标记-清除(Mark and Sweep):这是最常用的垃圾回收算法。垃圾回收器首先标记所有从根对象(如全局对象、活动执行上下文、当前调用栈等)可访问到的对象。然后,它遍历所有对象并清除未被标记的对象,即释放它们所占用的内存。
  • 引用计数(Reference Counting):这种算法会为每个对象维护一个引用计数器。当对象被引用时,计数器加1;当对象的引用被移除时,计数器减1。当计数器为零时,表示对象不再被引用,即可被回收。然而,引用计数算法难以处理循环引用的情况,导致循环引用的对象无法被回收。

现代JavaScript引擎通常使用标记-清除算法作为主要的垃圾回收机制。除此之外,还使用了一些优化技术来改进回收性能,如分代回收、增量回收和空闲时间回收等。

[前端面试题]CSS面试题

胖蔡阅读(425)

1、CSS实现元素两个盒子垂直水平居中方法

  • 子布局设置为绝对布局:position:absolte; 父布局设置为相对布局,且子布局设置为父布局宽高的50%,然后通过向上、向左移动自身宽高的负一半。
  • 通过使用transform实现向x轴、y轴移动,样式如:transform: translate(-50%,-50%); 在上述前提下。
  • 父布局宽高固定,可以在子布局中通过:margin:auto;实现垂直水平居中。
  • flex布局,通过在父布局中设置:justify-content:center;align-items:center;
  • 父布局使用table-cell与转换text-allign(可以对行内块生效)

2、BFC的理解

BFC(Block Formatting Contenxts) : 块级格式上下文,块格式上下文,是一块独立的渲染区域,内部元素不会影响外部的元素。通过BFC可以用于清除元素内部浮动,解决盒子margin合并问题,如何使用BFC:

  • float不是none
  • overflow属性不是visible
  • position属性不是staticrelative
  • display属性为以下值 : table-cell 、 inline-block 、 table-caption

3、盒子模型的理解

CSS 将html每一个元素都抽象为一个盒子,每个盒子有固定的组成部分,为了能达到相应的效果,我们需要对每个盒子的组成部分进行设置、布局。盒子模型将一个html元素分成4个部分组成:

  • 外边距:margin
  • 内边距:padding
  • 边框:border
  • 内容:content

盒子模型类型分为:

  • W3C标准盒子模型
  • IE标准盒子模型(怪异盒子模型)

准盒子模型=content(内容)+border(边框)+padding(内边距),怪异盒子模型=content(内容)(已经包含了 padding 和 border)。

4、CSS选择器的权重

!important > 行内样式 >ID选择器 > 类选择器 > 标签选择器 > 通配符 > 继承 > 浏览器默认属性

5、CSS中哪些属性不可继承

元素的自带属性不可被继承,比如a标签的颜色,如下为CSS中不可继承的属性:

  • display:元素的格式
  • 文本属性:vertical-align、text-decoration
  • 盒子模型属性:width、height、margin、border、padding
  • 背景属性:background、background-color、background-image

6、Bootstrap 栅格系统的工作原理?

Bootstrap内置了一套响应式、移动设备优先的流式栅格系统,随着屏幕设备或视口(viewport)尺寸的增加,系统会自动分为最多12列。网格系统的实现原理非常简单,仅仅是通过定义容器大小,平分12份(也有平分成24份或32份,但12份是最常见的),再调整内外边距,最后结合媒体查询,就制作出了强大的响应式网格系统。Bootstrap框架中的网格系统就是将容器平分成12份。

7、CSS3的新特性有哪些?

  • 新增 RGBAHSLA 模式,新增透明度
  • 使用 box-shadow 属性,可以为元素添加投影效果,包括阴影的颜色、大小、模糊度和偏移量等。
  • CSS3允许通过 border-radius 属性为元素的边框添加圆角,创建圆形、椭圆形或具有不同角度的矩形边框。
  • CSS3 引入了linear-gradient(线性渐变)和radial-gradient (径向渐变),允许在元素的背景中创建平滑过渡的颜色效果。
  • 自定义动画:CSS3的 @keyframes 规则允许创建复杂的动画效果,通过定义关键帧和过渡细节来控制动画的执行
  • 使用 transform 属性,可以对元素进行旋转、缩放、倾斜和平移等变换操作
  • CSS3提供了更多的字体控制选项,包括使用 @font-face 规则引入自定义字体文件,以及设置字体的粗细、斜体、大小调整和字间距等。
  • 弹性布局:flex
  • 新增多种选择器:伪类选择器、伪元素选择器、属性选择器等
  • 通过 column-count 和 column-width 等属性,可以将文本内容分成多列显示,类似报纸或杂志的版面布局。
  • 媒体查询允许根据设备的特性和屏幕尺寸来适应不同的样式和布局。通过媒体查询,可以创建响应式网页设计,使网页在不同设备上显示良好。
  • CSS3的 filter 属性允许应用各种图形效果到元素上,如模糊、亮度调整、对比度调整、灰度化、色彩反转等,为图像和元素添加特殊的视觉效果。
  • 网格布局:Grid

8、position的值有哪些,分别有什么作用?

  • static(静态定位):默认值。没有定位,元素出现在正常的流中(忽略 top, bottom, left, right 或者 z-index 声明)。
  • relative(相对定位):生成相对定位的元素,通过top,bottom,left,right的设置相对于其正常(原先本身)位置进行定位。可通过z-index进行层次分级。
  • absolute(绝对定位):生成绝对定位的元素,相对于 static 定位以外的第一个父元素进行定位。元素的位置通过 "left", "top", "right" 以及 "bottom" 属性进行规定。可通过z-index进行层次分级。
  • fixed(固定定位):生成绝对定位的元素,相对于浏览器窗口进行定位。元素的位置通过 "left", "top", "right" 以及 "bottom" 属性进行规定。可通过z-index进行层次分级。

9、::before和::after中双冒号和单冒号有什么区别、作用?

伪元素在 CSS1 中已存在,当时语法是用 : 表示,如 :before 和 :after,后来在 CSS3 中修订,伪元素用 :: 表示,如 ::before 和 ::after,以此区分伪元素和伪类,为了兼容性各浏览器,继续使使用 :after 这种老
语 法表示伪元素。

  • 单冒号(:)用于 CSS3 的伪类
  • 双冒号(::)用于 CSS3 的伪元素

伪类用于向某些选择器添加特殊的效果。它并不改变任何DOM内容。只是插入了一些修饰类的元素,这些元素对于用户来说是可见的,但是对于DOM来说不可见。伪元素创建了一个虚拟容器,这个容器不包含任何DOM元素,但是可以包含内容。另外,开发者还可以为伪元素定制样式。

伪类与伪元素的本质区别就是是否抽象创造了新元素;可以同时使用多个伪类,而只能同时使用一个伪元素。

10、CSS中单位有哪些?相互之间有什么区别?

  • px像素:绝对单位,页面按精确像素展示
  • in:表示英寸,是一个物理单位,在CSS中被直接映射成为px; 转换的方法是 1in = 96px
  • cm:表示厘米,在生活中常用的单位,同样被映射为 px; 转换方法为 1cm = 37.8px
  • mm:表示毫米,与cm类似,转换方法为 1mm = 0.1cm = 3.78px
  • em:参考物是父元素的font-size,具有继承的特点。相对单位,基准点为父节点字体的大小,如果自身定义了font-size按自身来计算(浏览器默认字体是16px),整个页面内1em不是一个固定的值。
  • rem:相对单位,可理解为”root em”, 相对根节点html的字体大小来计算,CSS3新加属性,chrome/firefox/IE9+支持。
  • 百分比%:相对长度单位,相对于父元素的百分比值
  • vwvhCSS3中加入的 相对长度单位,取决于浏览器视窗的宽(高),1vw 即为浏览器视窗宽度的1/100,适用于依据屏幕宽或高进行的排版,适用于自适应。

11、元素的显示与隐藏?

  • display:none 隐藏对应的元素,在文档布局中不再给它分配空间,它各边的元素会合拢,就当他从来不存在。
  • visibility:hidden 隐藏对应的元素,但是在文档布局中仍保留原来的空间。

12、如何绘制一个0.5px的直线?

使用transform设置scale函数,代码如下:

height: 1px;
transform: scale(0.5);

13、简单介绍一下Flex布局

Flex布局是html中的弹性布局,需要将指定容器设置为flex布局,通过设置flex属性实现界面布局,flex布局支持:

  • 主轴对齐方式:使用justify-content调节元素在主轴的对齐方式
    • flex-start:默认值,起点开始依次排序
    • flex-end:终点依次排列
    • center:沿主轴居中排序
    • space-around:弹性盒子沿主轴均匀排列,空白间距均分在弹性盒子两侧
    • space-between:弹性盒子沿主轴均匀排列,空白间距均分在相邻盒子之间
    • space-evenly:弹性盒子沿主轴均匀排列,弹性盒子与容器之间间距相等
  • 侧轴对齐方式:使用align-items调节元素在侧轴的对齐方式

[前端面试题] HTML面试题(二)

胖蔡阅读(424)

1、介绍下从浏览器地址栏输入url到显示页面的全过程

  • 输入url后会通过DNS解析,实现网址到IP地址的转换
  • 建立TCP链接,经过三次握手成功建立连接
  • 客户端发起http请求网页数据,后台返回网页数据给浏览器
  • 读取页面内容,浏览器渲染,构建 DOM 树、构建 CSSOM
  • 将解析步骤中创建的 CSSOM 树和 DOM 树合成为 Render 树,然后用于计算每个可见元素的布局,最后将其绘制到屏幕上

2、简单介绍下TCP的三次握手

  • 客户端向服务器发送一个 SYN 包,并等待服务器确认。
  • 服务器接收到客户端发来的 SYN 包后,对该包进行确认后结束 LISTEN 阶段,并返回一段 TCP 报文。
  • 客户端接收到发送的 SYN + ACK 包后,明确了从客户端到服务器的数据传输是正常的,从而结束 SYN-SENT 阶段,并返回最后一段报文。
  • 当服务器端收到来自客户端确认收到服务器数据的报文后,得知从服务器到客户端的数据传输是正常的,从而结束 SYN-RECV 阶段,进入 ESTABLISHED 阶段,从而完成三次握手。

3、简单介绍下TCP的四次挥手

和三次握手的发起者必须是客户端不同,断开 TCP 连接的发起方可以是任何一方。为了方便讲解,下面我们以客户端作为发起者进行描述。

  • 第一次挥手,客户端向服务端发送 TCP 请求,FIN 设置为 1,seq 设置为一个随机数 x,FIN 是一个标志位,表示结束(finish)的意思,1 等同于 true。seq 是个序列号,一个装数据的地方,我们这里给他设置为一个随机数,用于给服务端做确认,好对应上这个 TCP 请求。
  • 第二次挥手,服务端发送 TCP,并将 TCP 头部中的ACK 设置为 1(acknowledge,表示 “收到” 的意思),ack 确认号设置为 x+1(x 来自第一次挥手),当客户端收到这个 TCP 请求时,表示从客户端到服务端的通道已经关闭,你不能再向服务端发正常的数据请求了。此时服务端到客户端还是可以发送数据的。如果服务端有一些之前的 TCP 请求没来得及响应,在第二次挥手和第三次挥手期间还是可以去返回的。
  • 第三次挥手,服务端向客户端发送 TCP 请求:FIN 设置为 1,seq 设置为一个随机数 y。类似第一次挥手,只是这次发送方为服务端。
  • 第四次挥手,客户端向服务端发送 TCP 请求:ACK 设置为 1,ack 确认号设置为 y+1。服务端接收到这个请求后,服务端就能成功变成关闭(CLOSE)状态。客户端则会等一段时间再进入关闭状态,因为第四次挥手不一定能成功发给服务端,所以要等一下,看看服务端会不会因为没收到第四次挥手,而重发第三次挥手。

4、link和@import 引入CSS有什么区别

link@import是外部引入CSS的两种方式。link只能在HTML页面中引入外部样式,在加载页面时会被同时加载。@import 既可以在HTML页面中导入外部样式,也可以在css样式文件中导入外部css样式,将在页面加载完毕后才会被加载。

5、如何做好SEO优化?

  • 多使用html语义化标签,保证网站结构的清晰、简洁
  • 关键词优化
  • 内容优化:要提供高质量、有价值的内容,吸引用户留下来,增加用户停留时间和页面访问量,同时也可以提升网站的信誉度和排名。
  • 外部链接优化:多添加高权重外链

6、HTML性能优化能做哪些?

  • DNS解析优化:可能稳定的DNS服务器
  • 浏览器下载、解析、渲染页面优化:优化 CSS 性能,减少外部HTTP请求,压缩 CSS, JS 和 HTML,使用 CDN 和缓存提高速度。
  • 服务端处理优化:Redis缓存、数据库存储优化或是系统内的各种中间件以及Gzip压缩等。

[前端面试题] HTML面试题(一)

胖蔡阅读(436)

1、HTML语义化的理解

HTML语义化就是用正确的标签做正确的事

  • Html语义化可以让页面的内容机构化、结构清晰化,以便于浏览器、搜索引擎进行解析
  • 爬虫也依赖HTML标签来确定上下文和关键字的权重,利于SEO的优化
  • 对开发者友好,让人更容易读懂,利于代码可读性

2、块状元素和行内元素有哪些?如何相互转换?

块状元素是指独占一行,默认宽度为100%,可以设置宽高。高度由自身内容决定,可以设置marginpadding属性。常见的块状元素:

  • <address>:定义地址
  • <caption>:定义表格标题
  • <dd>:定义列表中定义条目
  • <div>:定义文档中的分区或节
  • <dl>:定义列表
  • <dt>:定义列表中的项目
  • <fieldset>:对表单中的相关元素进行分组
  • <form>:创建 html 表单
  • <h1> - <h6>:定义标题
  • <hr>:创建一条水平线
  • <li>:定义列表项目
  • <legend>元素用于表示其父元素 <fieldset> 的内容标题。
  • <ol>:有序列表
  • <ul>:无序列表
  • <p>:定义段落
  • <pre>:预格式化的文本
  • <table>: HTML 表格
  • <tbody>:表格主体(正文)
  • <td>:表格中的标准单元格
  • <tfoot>:表格的页脚(脚注或表注)
  • <th>:表头单元格
  • <thead>:表格的表头
  • <tr>:表格中的行

不独占一行,与其他行内元素共享一行;设置宽高无效,高度由自身内容决定;margin 上下无效、左右有效;padding 均有效;行内元素不能包含块元素,除了a元素。常见的行内元素如下:

<span> <a> <srong> <b> <i> <em> <sub> <sup> <mark> <u> <img > <del> <input> <textarea> <select>

行内块元素和相邻的行内元素(包含行内块)在一行上,它们直接会有空白缝隙,一行可以显示多个(行内元素特点),默认宽度就是内容的宽度(行内元素特点),高度、宽度、内外边距都可以自定义(块元素特点)。

如何转换?

  • 转为块状:display: block;
  • 转为行内元素:display: inline;
  • 转为行内块元素:display: inline-block;

3、h5新增哪些元素?

  • canvas:定义绘制图形
  • audio:音频
  • video:视频
  • embed:定义嵌入式内容
  • datalist:定义选项列表
  • nav:定义导航链接的部分
  • section:定义文档中的节(section、区段)
  • footer:定义 section 或 document 的页脚。
  • header:定义了文档的头部区域
  • aside:定义页面的侧边栏内容。

4、src和href的区别?

href表示超文本引用,用于建立当前元素与文档的链接,目的不是为了引用资源,而是为了建立联系,让当前标签能够链接到目标地址。

src指向的内容会嵌入到文档中当前标签所在的位置。src与href的却别如下:

  • href 指向网络资源所在位置,建立和当前元素(锚点)或当前文档(链接)之间的联系。在请求 src 资源时会将其指向的资源下载并应用到文档中,比如 JavaScript 脚本,img 图片;
  • href 用于在当前文档和引用资源之间确立联系;src 用于替换当前内容;
  • 当浏览器解析到src ,会暂停其他资源的下载和处理,直到将该资源加载、编译、执行完毕,图片和框架等也如此,类似于将所指向资源应用到当前内容。

5、iframe 的优缺点?

优点

  • 解决加载缓慢的第三方内容,如图标或广告,这些问题可以由iframe来解决
  • iframe能够原封不动的把嵌入的网页展现出来
  • 并行加载脚本
  • 网页如果为了统一风格,头部和版本都是一样的,就可以写成一个页面,用iframe嵌套,可以增加代码的可重用

缺点:

  • 阻塞主页面的Onload事件
  • 没有语义,不利于搜索引擎识别
  • 增加http请求,加载也需要时间
  • 移动设备无法完全显示框架,设备兼容性差。

6、常见的浏览器内核有哪些?

  • Gecko(Firefox)内核:Firefox、Netscape6及以上版本、Mozilla 
  • WebKit内核:Safari、Chrome
  • Blink内核:Chrome
  • Trident内核:IE浏览器
  • Presto内核:Opera(Opera内核原为 Presto,现为Blink)

7、DOCTYPE的作用?严格模式和混杂模式的区别?

<!DOCTYPE>声明位于位于HTML文档中的第一行,声明叫做文件类型定义(DTD),声明的作用为了告诉浏览器该文件的类型。处于 <html> 标签之前。告知浏览器的解析器用什么文档标准解析这个文档。DOCTYPE不存在或格式不正确会导致文档以兼容模式呈现。标准模式的排版 和JS运作模式都是以该浏览器支持的最高标准运行。在兼容模式中,页面以宽松的向后兼容的方式显示,模拟老式浏览器的行为以防止站点无法工作。

  • 严格模式(标准模式):是指浏览器按照 W3C 标准解析代码。严格模式是浏览器根据web标准去解析页面,是一种要求严格的DTD,不允许使用任何表现层的语法,如<br/>
  • 混杂模式(兼容模式):又称怪异模式或兼容模式,是指浏览器用自己的方式解析代码。混杂模式是一种比较宽松的向后兼容的模式。混杂模式通常模拟老式浏览器的行为,以防止老站点无法工作。

HTML5 没有 DTD ,因此也就没有严格模式与混杂模式的区别,HTML5 有相对宽松的语法,实现时,已经尽可能大的实现了向后兼容。( HTML5 没有严格和混杂之分)

8、对于WEB标准以及W3C的理解与认识

WEB标准分为结构(structural layer)、表示(presentation layer)和行为(behavior layer)。三部分独立分开,使其更具有模块化:

  • 结构(structural layer):其中主要由HTML标签组成。通俗来说:页面body里面我们写入的标签都是为了页面的结构。
  • 表示(presentation layer):css样式表,通过css可以使页面的结构标签更具美感
  • 行为(behavior layer):指页面和用户具有一定的交互,同时页面结构或者表现发生变化,主要是有js组成

W3Cweb标准提出了规范化的要求:

  • 标签字母要小写 
  • 标签要闭合 
  • 标签不允许随意嵌套
  • -尽量使用外链css样式表和js脚本,让结构、表现和行为分为三块,符合规范,同时提高页面渲染速度,提高用户的体验。
  • 样式尽量少用行间样式表,使结构与表现分离,标签的id和class等属性名要做到见文知义,标签越少,加载越快,用户体验更高,代码维护简单,便于改版
  • 不需要变动页面内容,便可提高打印版本而不需要复制内容,提高网站易用性。