Skip to main content

前端知识之HTML和JavaScript

html

html标签的类型(head、body、!DOCTYPE)它们的作用是什么?

参考答案

!DOCTYPE

  • 它是指示web浏览器关于页面使用哪个HTML版本进行编写的指令

head

  • 是所有头部元素的容器,绝大多数头部标签的内容不会显示给读者
  • 该标签下所包含的部分可加入的标签有,<base>,<link>,<script>,<meta>,<title>以及<style>

body

  • 用于定义文档的主体,包含了文档的所有内容
  • 该标签支持html的全局属性和事件属性

h5新特性

参考答案
  • 新增选择器
    • document.querySelector
    • document.querySelectorAll
  • 拖拽释放API
    • Drag and drop
  • 媒体播放的 videoaudio
  • 本地储存
    • localStorage
    • sessionStorage
  • 离线应用
    • manifest
  • 桌面通知
    • Notifications
  • 语义化标签
    • article
    • footer
    • header
    • nav
    • section
  • 增强表单控件
    • calendar
    • date
    • time
    • email
    • url
    • search
  • 地理位置
    • Geolocation
  • 多任务
    • web worker
  • 全双工通讯协议 websocket
  • 历史管理 history
  • 跨域资源共享 CORS(Access-Control-Allow-Origin)
  • 页面可见性改变事件 visibilitychange
  • 跨窗口通讯 PostMessage
  • Form Data 对象
  • 绘画 canvas

H5移除的元素:

  • 纯表现的元素
    • basefont
    • big
    • center
    • font
    • s
    • strike
    • tt
    • u
  • 对可用性产生负面影响的元素
    • frame
    • frameset
    • noframes

伪类和伪元素

伪类:用于已有元素处于某种状态时为其台南佳对应的样式,这个状态是根据用户的行为而动态变化的

  • :hover
  • :active
  • :focus
  • :link
  • :visited
  • :first-child
  • :lang()
  • :last-child
  • :nth-child()
  • ...

伪元素:用于创建一些不在DOM树中的元素,并为其添加样式

  • ::before
  • ::after
  • ::first-letter
  • ::first-line
  • ::selection

HTML5语义化

在HTML5出现之前我们最喜欢使用 <div> 来表示一切,其本身并没有什么具体含义,也不利于SEO。HTML5添加了一些语义化标签,用于更好的DOM结构。

  • <hn>
    • h1~h6 分级标题
  • <ul> <ol> <li>
    • 有序 / 无序列表
  • <header>
    • 页眉,包含网站标示、导航、链接、搜索等
  • <main>
    • 页面主要内容,一个页面只能用一次。
  • <footer>
    • 页脚
  • <article>
    • 表示文档、页面、应用或网站中的独立结构,其意在成为可独立分配的或可复用的结构,如在发布中,它可能是论坛帖子、杂志或新闻文章、博客、用户提交的评论、交互式组件,或者其他独立的内容项目。
  • <section>
    • 表示 HTML 文档中一个通用独立章节,它没有更具体的语义元素来表示。一般来说会包含一个标题。
  • <aside>
    • 定义所处内容以外的内容
  • <nav>
    • 标记导航

文字相关的,标签同时含有文字样式以及更好的语义化:

<small></small> <!-- HTML 中的<small>元素將使文本的字体变小一号。在 HTML5 中,除了它的样式含义,这个元素被重新定义为表示边注释和附属细则,包括版权和法律文本。 -->
<strong></strong> <!-- 表示文本十分重要,一般用粗体显示。 -->
<em></em> <!-- HTML 着重元素 (<em>) 标记出需要用户着重阅读的内容, <em> 元素是可以嵌套的,嵌套层次越深,则其包含的内容被认定为越需要着重阅读。
通常地,该元素会被浏览器展示为斜体文本, 但是,它不应该仅仅用于应用斜体样式;为此目的,请使用 CSS 样式
使用 <cite> 元素标记作品的标题(书籍,戏剧,歌曲等);它通常也采用斜体样式,但具有不同的含义。使用 <strong> 元素标记比周围文本更重要的文本。-->
<mark></mark> <!-- 表示为引用或符号目的而标记或突出显示的文本,这是由于标记的段落在封闭上下文中的相关性或重要性造成的。 -->
<figure></figure> <!-- 代表一段独立的内容,并且作为一个独立的引用单元。当它属于主内容流(main flow)时,它的位置独立于主体。这个标签经常是在主文中引用的图片,插图,表格,代码段等等, -->
<figcaption></figcaption> <!--用于描述其父节点 <figure> 元素里的其他数据。<figcaption> 是<figure> 子标签第一个或最后一个。 -->
<cite></cite> <!-- 引用标签,表示一个作品的引用,且必须包含作品的标题。这个引用可能是一个根据适当的上下文约定关联引用的元数据的缩写。 -->
<blockquote></blockquote> <!-- 块级引用元素,代表其中的文字是引用内容。通常在渲染时,这部分的内容会有一定的缩进 -->
<q></q> <!-- 引用标签 (<q>) 表示一个封闭的并且是短的行内引用的文本。这个标签是用来引用短的文本,所以请不要引入换行符; 对于长的文本的引用请使用 <blockquote> 替代。 -->
<time></time> <!-- HTML time 标签用来表示 24 小时制时间或者公历日期,若表示日期则也可包含时间和时区。 -->
<abbr title=""></abbr> <!-- HTML 缩写元素(<abbr>)用于代表缩写,并且可以通过可选的 title 属性提供完整的描述。若使用 title 属性,则它必须且仅可包含完整的描述内容。 -->
<dfn></dfn> <!-- HTML 定义元素表示术语的一个定义。 -->
<address></address> <!-- 表示其中的 HTML 提供了某个人或某个组织(等等)的联系信息。 -->
<del></del> <!-- 表示一些被从文档中删除的文字内容。比如可以在需要显示修改记录或者源代码差异的情况使用这个标签。 -->
<ins></ins> <!-- 表示文档中添加的内容。 -->
<code></code> <!-- 呈现一段计算机代码。默认情况下,它以浏览器的默认等宽字体显示。 -->
<pre></pre> <!-- 表示预定义格式文本。在该元素中的文本通常按照原文件中的编排,以等宽字体的形式展现出来,文本中的空白符(比如空格和换行符)都会显示出来。 -->
<meter></meter> <!-- 用来显示已知范围的标量值或者分数值。 -->
<progress></progress> <!-- 用来显示一项任务的完成进度。 -->

推荐文章:

语义化的优点:

  1. 易于用户阅读,样式丢失的时候能让页面呈现清晰的结构
  2. 有利于SEO,搜索引擎根据标签来确定上下文和各个关键字的权重
  3. 方便其它设备解析
  4. 有利于开发和维护,语义化是代码更加具有可读性

audio标签的API

audio 常用属性:

  • src 音乐的url
  • preload 预加载(autoplay开启的话,则失效)
  • loop 循环播放
  • controls 控制条
  • autoplay 自动播放

js可获取到的属性:

  • duration 时长
  • paused 是否暂停
  • ended 是否播放完毕
  • muted 是否静音
  • volume 音量大小,0~1
  • startTime 起始播放时间
  • error 错误代码;1是用户终止;2是网络错误;3是解析错误;4URL无效
  • currentTime 当前播放的时间
  • currentSrc 当前的audio文件

audio控制函数:

  • load() 加载文件
  • play() 加载并播放音频
  • pause() 暂停
  • canPlayType(obj) 测试是否支持给定的mini类型的文件

audio事件:

  • loadstart 开始请求数据
  • progress 正在缓冲
  • play play() 或 autoplay 触发时
  • pause pause() 触发时
  • ended 播放结束
  • timeupdate 播放时间发生改变时
  • canplaythrouph 歌曲已经加载完成
  • canplay 缓冲至目前可播放状态

JavaScript基础

var/let/const

ES6新增?区别
varx变量未赋值时未undefined
为方法作用域,只要在方法内定义了,整个方法 定义变量后的代码都可以使用
let变量声明前使用会报错,而var不会报错,只会显示undefined;
是块作用域;
let禁止重复声明变量,否则会报错,而var可以
const常量声明方式,且必须在声明变量时初始化,后面也不能再改;
实际上只是保证变量指向的那个地址不得改动

js数据类型

基本数据类型引用数据类型
包含Number,String,Booolean,null,undefined,
Symbol,bigint(为ES6新增)
objectfunction。其中function比较特殊。
储存直接储存在栈中的简单数据段,占用空间小,大小固定,属于被频繁使用的数据。引用数据类型是储存在堆内存中,占据空间大,大小不固定。引用数据类型在栈中储存了指针,该指针指向堆中该实体的起始地址,当解释器寻找引用值时,会检索其在栈中的地址,取得地址后从堆中获取实体
区别堆比栈空间大,栈比堆运行速度快;
堆内存是无序储存,可以根据引用直接获取。

注: 栈是储存基本类型值和执行代码的空间。 ES6泛指2015年以后的所有新增特性

Object.assign

作用: Object.assign 可以实现对象的合并。 语法: Object.assign(target, ...sources) 解析:

  • Object.assign会将source 里面的可枚举属性复制到 target,如果和target中已有的属性将会覆盖
  • 后续的source会覆盖前面的source的同名属性
  • Object.assign复制的是属性值,如果属性值是一个引用类型,那么复制的其实是引用地址,就会存在引用共享的问题。

constructor

创建的每个函数都有一个prototype(原型)对象,这个属性是一个指针,指向一个对象。在默认情况下,所有原型对象都会自动获得一个constructor(构造函数)的属性,这个属性是一个指向prototype属性所在函数的指针。当调用构造函数创建一个新实例后,该实例的内部将包含一个指针(继承自构造函数的prototype),指向构造函数的原型对象。注意当将构造函数的prototype设置等于一个以对象字面量形式创建的新对象时,constructor属性不在指向该构造函数。

map 和 forEach 的区别

相同点:

  • 都是循环遍历数组
  • 每次执行匿名函数都支持三个参数:item当前每一项,index索引值,arr原数组
  • 匿名函数中的this都是指向window
  • 只能遍历数组

不同点:

  • map()会分配内存空间储存新数组并返回,forEach() 不会返回数据。
  • forEach()允许callback更改原始数组的元素,map()返回新的数组并不允许改变原数组。

for of 可以遍历哪些对象

for...of...es6 新增的遍历方法,但只限于迭代器(iterator),所以普通的对象用 for...of 遍历会报错。 可迭代的对象有:包括Array Map Set TypedArray arguments

web component

web component总的来说是提供一整套完善的封装机制来把Web组件化进行标准化;每个框架实现的组件都同意标准的进行输入输出,更好的进行复用。 web component包含四个部分:

  • custom element
    • 提供一种方法让开发者可以自定义HTML元素,包含特定的组合,样式和行为。支持web component标准的浏览器会提供一些列API给开发者用于创建自定义的元素,或者扩展现有元素
  • html import
    • 一种早在HTMLs中引用以及复用其它HTML文档的方式。
  • html template
    • 模板
  • shadow DOM
    • 提供一种更好的组织页面元素的方式,避免代码间相互影响。

变量提升

Javascript是单线程语言(动态编译),执行是按顺序执行的,但是并不是逐行的分析和执行,而是一段一段的分析执行,会先进行编译阶段才是执行阶段。在编译阶段,代码真正执行前的几毫秒,会检测到所有的变量和函数声明,所有这些函数和变量声明都被添加到名为Lexical EnvironmentJavaScript数据结构的内存中,所以这些变量和函数能在它们真正被声明之前使用。

作用域

作用域是一个独立的地盘,让变量不会外泄、暴露出去。也就是说作用域最大的用处就是隔离变量,不同的作用域下同名变量不会有冲突。

ES6之前, JavaScript没有块级作用域,只有全局作用域和函数作用域。

Objects 和 maps 的比较

Object 和 Map 类似的是,它们都允许你按键存取一个值、删除键、检测一个键是否绑定了值。因此(并且也没有其他内建的替代方式了)过去我们一直都把对象当成 Map 使用。

不过 Map 和 Object 有一些重要的区别,在下列情况中使用 Map 会是更好的选择:

MapObject
意外的键Map 默认情况不包含任何键,只包含显式插入的键。一个 Object 有一个原型,原型链上的键名有可能和你自己在对象上的设置的键名产生冲突。(备注:虽然从 ES5 开始可以用 Object.create(null) 来创建一个没有原型的对象,但是这种用法不太常见。)
键的类型一个 Map 的键可以是任意值,包括函数、对象或任意基本类型。一个 Object 的键必须是一个 String 或是 Symbol
键的顺序Map 中的键是有序的。因此,当迭代的时候,一个 Map 对象以插入的顺序返回键值。虽然 Object 的键目前是有序的,但并不总是这样,而且这个顺序是复杂的。因此,最好不要依赖属性的顺序。[1]
SizeMap 的键值对个数可以轻易地通过 size 属性获取。Object 的键值对个数只能手动计算.
迭代Map可迭代的Object 没有实现 迭代协议,所以使用 JavaSctiptfor...of 表达式并不能直接迭代对象。[2]
性能在频繁增删键值对的场景下表现更好。在频繁添加和删除键值对的场景下未作出优化。
序列化和解析没有元素的序列化和解析的支持。解决方案 - stackoverflow原生的由 ObjectJSON 的序列化支持,使用 JSON.stringify()。原生的由 JSONObject 的解析支持,使用 JSON.parse()

注:

[1]
  • 自 ECMAScript 2015 规范以来,对象的属性被定义为是有序的;ECMAScript 2020 则额外定义了继承属性的顺序。但是,请注意没有可以迭代对象所有属性的机制,每一种机制只包含了属性的不同子集。
  • for-in 仅包含了以字符串为键的属性
  • Object.keys 仅包含了对象自身的、可枚举的、以字符串为键的属性
  • Object.getOwnPropertyNames 包含了所有以字符串为键的属性,即使是不可枚举的
  • Object.getOwnPropertySymbols 与前者类似,但其包含的是以 Symbol 为键的属性
  • 等等
[2]
  • 对象可以实现迭代协议,或者你可以使用 Object.keysObject.entries
  • for...in 表达式允许你迭代一个对象的可枚举属性。

Arguments 对象

在js中,我们在调用有参数的函数时,当传参时,js会把所有的参数都存到一个叫 arguments 的对象中,它是一个类数组数据。

注意

“类数组” 意味着 arguments长度 属性 并且属性的索引是从零开始的,但是它没有 Array的 内置方法, 例如 forEach()map()都是没有的。但是它可以被转换为一个真正的Array:

var args = Array.prototype.slice.call(arguments);
var args = [].slice.call(arguments);

// ES2015
const args = Array.from(arguments);
const args = [...arguments];

arguments对象是所有(非箭头)函数中都可用的局部变量。你可以使用arguments对象在函数中引用函数的参数。此对象包含传递给函数的每个参数,第一个参数在索引 0 处。

如果调用的参数多于正式声明接受的参数,则可以使用arguments对象。这种技术对于可以传递可变数量的参数的函数很有用。使用 arguments.length来确定传递给函数参数的个数,然后使用arguments对象来处理每个参数。要确定函数签名中(输入)参数的数量,请使用Function.length属性。

ES6,除了 Arguments ,还可以使用剩余参数

function any(a, b, ...theArgs) {
// ...
}

扩展:Function.length

instanceof的实现原理及手写instanceof的实现

在JavaScript中,检测一个变量的类型,有以下三种方法:

  • typeof
  • Object.prototype.toString.call()
  • instanceof
    • 判断一个实例是否属于某个类型

instanceof 的实现原理:instanceof 运算符用于测试构造函数的 prototype 属性是否出现在对象原型链中的任何位置。

知道原理就好办了,一直往下查询即可:

function isInstanceOf(child, parent) {
let pt = parent.prototype;
let ct = child.__proto__;

while(true) {
if(ct === null) {
return false;
}
if(ct === pt) {
return true;
}
ct = ct.__proto__;
}
}

js原型链

  • prototype
  • __proto__
  • constructor

参考文章:js原型链

javascript原型链图示

编码和字符集的区别

字符集是书写系统字母与符号的集合,而字符编码则是将字符映射为一特定的字节或字节序列,是一种规则。 通常特定的字符集采用特定的编码方式(即一种字符集对应一种字符编码(例如:ASCII、IOS-8859-1、 GB2312、GBK,都是即表示了字符集又表示了对应的字符编码,但Unicode不是,它采用现代的模型))

  • 字符:在计算机和电信技术中,一个字符是一个单位的字形、类字形单位或符号的基本信息。即一个字符可以是一个中文汉字、一个英文字母、一个阿拉伯数字、一个标点符号等。
  • 字符集:多个字符的集合。例如GB2312是中国国家标准的简体中文字符集,GB2312收录简化汉字(6763个) 及一般符号、序号、数字、拉丁字母、日文假名、希腊字母、俄文字母、汉语拼音符号、汉语注音字母,共 7445 个图形字符。
  • 字符编码:把字符集中的字符编码为(映射)指定集合中的某一对象,以便文本在计算机中存储和通过通信网络的传递。

null 和 undefined

undefined 的字面意思就是:未定义的值 。这个值的语义是,希望表示一个变量最原始的状态,而非人为操作 的结果 。 这种原始状态会在以下 4 种场景中出现:

  1. 声明了一个变量,但没有赋值
  2. 访问对象上不存在的属性
  3. 函数定义了形参,但没有传递实参
  4. 使用 void 对表达式求值

你也可以手动给一个变量赋值 undefined,但这样做没有意义,因为一个变量不赋值就是 undefined。

null 的字面意思是:空值 。这个值的语义是,希望表示 一个对象被人为的重置为空对象,而非一个变量最原始的状态 。在内存里的表示就是,栈中的变量没有指向堆中的内存对象 null 有属于自己的类型 Null,而不属于Object类型,typeof 之所以会判定为 Object 类型,是因为JavaScript 数 据类型在底层都是以二进制的形式表示的,二进制的前三位为 0 会被 typeof 判断为对象类型,而 null 的二进 制位恰好都是 0 ,因此,null 被误判断为 Object 类型。

数组和类数组的区别

数组是一个特殊对象,与常规对象的区别:

  • 当由新元素添加到列表中时,自动更新length属性
  • 设置length属性,可以截断数组
  • Array.protoype中继承了方法
  • 属性为Array

类数组是一个拥有length属性,并且他属性为非负整数的普通对象,类数组不能直接调用数组方法。 类数组是简单对象,它的原型关系与数组不同。

类数组转换为数组

  • 使用 Array.from()
  • 使用 Array.prototype.slice.call()
  • 使用 Array.prototype.forEach() 进行属性遍历并组成新的数组

转换后的数组长度由 length 属性决定。索引不连续时转换结果是连续的,会自动补位 且转换仅考虑 0或正整数 的索引

转换示例:

let arr = {
length: 4,
'-1': -1,
'0': 0,
a: 'a',
1: 1
};
console.log(Array.from(arr)); // [ 0, 1, undefined, undefined ]
console.log(Array.prototype.slice.call(arr)) // [ 0, 1, <2 empty items> ] 稀疏数组

类数组尽管不能直接调用数组的方法,但是可以间接的使用Function.call方法调用; 或者使用数组方法的静态函数版本。

var a = {"0": "a", "1": "b", "2": "c", length: 3};      // 类数组对象
Array.prototype.join.call(a, "+"); // => "a+b+c"
Array.prototype.slice.call(a, 0); // => ["a", "b", "c"]
Array.prototype.map.call(a, function(x) {
return x.toUpperCase(); // => ["A", "B", "C"]
})
// -----
Array.join(a, "+");
Array.slice(a, 0);
Array.map(a, function(x) {
return x.toUpperCase();
})

扁平数组和JSON树的转换

扁平数组 转化为 JSON 树

let flatArr = [
{ id: 1, title: "title1", parent_id: 0 },
{ id: 2, title: "title2", parent_id: 0 },
{ id: 3, title: "title3", parent_id: 2 },
{ id: 4, title: "title4", parent_id: 3 },
{ id: 5, title: "title5", parent_id: 4 },
{ id: 6, title: "title6", parent_id: 3 },
];
function arr2json(arr) {
let ans = [];
const map = arr.reduce((pre, cur) => {
pre[cur.id] = Object.assign({}, cur);
return pre;
}, {});

for(const node of arr) {
if(node.parent_id === 0) {
ans.push(map[node.id]);
}
if(node.parent_id in map) {
const parent = map[node.parent_id];

if(parent.children) {
parent.children.push(map[node.id]);
} else {
parent.children = [map[node.id]];
}
}
}
return ans;
}

const json = arr2json(flatArr)
/*
json = [
{ id: 1, title: 'title1', parent_id: 0 },
{
id: 2,
title: 'title2',
parent_id: 0,
children: [
{
id: 3,
title: 'title3',
parent_id: 2,
children: [
{
id: 4,
title: 'title4',
parent_id: 3,
children: [
{
id: 5,
title: 'title5',
parent_id: 4
}
]
},
{ id: 6, title: 'title6', parent_id: 3 }
]
}
]
}
]
*/

JSON 树 转化为 扁平数组

// JSON 树 -> 扁平数组
function json2FlatArr(json) {
return json.reduce((pre, cur) => {
const {id, title, parent_id, children = []} = cur;
pre.push({
id,
title,
parent_id
})
return [...pre, ...json2FlatArr(children)];
}, [])
}
json2FlatArr(json)

SetMapWeakSetWeakMap 的区别

Set:

  1. 成员不能重复;
  2. 只有键值,没有键名,有点类似数组;
  3. 可以遍历,方法有 add、delete、has

WeakSet:

  1. 成员都是对象(引用);
  2. 成员都是弱引用,随时可以消失(不计入垃圾回收机制)。可以用来保存 DOM 节点,不容易造成内存 泄露;
  3. 不能遍历,方法有 add、delete、has;

Map:

  1. 本质上是键值对的集合,类似集合;
  2. 可以遍历,方法很多,可以跟各种数据格式转换;

WeakMap:

  1. 只接收对象为键名(null 除外),不接受其他类型的值作为键名;
  2. 键名指向的对象,不计入垃圾回收机制;
  3. 不能遍历,方法同 get、set、has、delete;

js 中有哪几种内存泄露的情况

js中内存泄露
  1. 意外的全局变量;
  2. 闭包;
  3. 未被清空的定时器;
  4. 未被销毁的事件监听;
  5. DOM 引用;

异步执行顺序

查看下面代码输出顺序:

console.log("start")
setTimeout(() => {
console.log("setTimeout")
},0)

async function asy1() {
console.log("asy1 start")
await asy2()
console.log("asy1 end")
}
async function asy2() {
console.log("asy2 start")
}
asy1()

new Promise((res) => {
console.log('promise')
res();
}).then(() => {
console.log("promise then")
})

console.log("end!")

答案:

start
asy1 start
asy2 start
promise
end!
asy1 end
promise then
setTimeout

jsonxml数据的区别

  1. 数据体积方面:xml是重量级的,json是轻量级的,传递的速度更快些。
  2. 数据传输方面:xml在传输过程中比较占带宽,json占带宽少,易于压缩。
  3. 数据交互方面:jsonjavascript的交互更加方便,更容易解析处理,更好的进行数据交互
  4. 数据描述方面:json对数据的描述性比xml较差
  5. xmljson都用在项目交互下,xml多用于做配置文件,json用于数据交互

delete

delete 操作符用来删除一个对象的属性。

  1. delete在删除一个不可配置的属性时在严格模式和非严格模式下的区别:
    • 在严格模式中,如果属性是一个不可配置(non-configurable)属性,删除时会抛出异常;
    • 非严格模式下返回 false
  2. delete能删除隐式声明的全局变量:这个全局变量其实是 global对象(window)的属性
  3. delete能删除的
    • 可配置对象的属性
    • 隐式声明的全局变量
    • 用户定义的属性
    • ECMAScript 6中,通过 constlet 声明指定的 "temporal dead zone" (TDZ)delete 操作符也会起作用
  4. delete不能删除的
    • 显式声明的全局变量
    • 内置对象的内置属性
    • 一个对象从原型继承而来的属性
  5. delete删除数组元素
    • 当你删除一个数组元素时,数组的 length 属性并不会变小,数组元素变成undefined
    • 当用 delete 操作符删除一个数组元素时,被删除的元素已经完全不属于该数组。
    • 如果你想让一个数组元素的值变为 undefined 而不是删除它,可以使用 undefined 给其赋值而不是使用delete 操作符。此时数组元素是在数组中的
  6. delete 操作符与直接释放内存(只能通过解除引用来间接释放)没有关系