概述
文章目录
- 没什么用的知识又增加了
- 事件轮询
- 一、JavaScript的单线程机制
- 二、任务队列
- 事件队列,事件轮询
- 精准搜索和模糊搜索
- React
- 梳理react
- 一、来自原生js的问题
- 二、React特点
- 三、diff算法
- CSS部分
- 自定义浏览器滚动条外观
- CSS自定义属性
- CSS3新增伪类
- 简单的js实战技巧
- 删除对象的属性
- JavaScript中的this
- web安全问题
- 关于HTML的几个标签
- head标签
- title标签
- meta标签
- base标签
- link标签
- style标签
- script标签
- bgsound
- 浅谈CommonJs和ES Module
- 一、为何要使用模块化
- 二、CommonJs
- 三、ES Module
- 四、CommonJS和ES Module
- Git
- **cherry-pick**
- 其他
- 学点英语
没什么用的知识又增加了
事件轮询
一、JavaScript的单线程机制
单线程机制决定JavaScript在同一时间只能做一件事。JavaScript的单线程机制和它的用途有关,作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?
为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。
二、任务队列
单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就无法执行。
如果排队是因为计算量大,CPU忙不过来,倒也罢了,但是很多时候CPU是闲着的,因为IO设备(输入输出设备)很慢(比如用Ajax操作从网络读取数据),不得不等待结果出来,再往下执行。
JavaScript语言的设计者意识到,这时主线程完全可以不管IO设备,挂起处于等待中的任务,先运行排在后面的任务。等到IO设备返回了结果,再回过头,把挂起的任务继续执行下去。
于是,所有任务被分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
具体来说,异步执行的运行机制如下。(同步执行也是如此,因为它可以被视为没有异步任务的异步执行。)
(1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
(2)主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
(3)一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
(4)主线程不断重复上面的第三步。
事件队列,事件轮询
- 在 JS 的ES6 中新增的任务队列(promise)优先级是在事件循环之上的,事件循环每次 tick 后会查看 ES6 的任务队列中是否有任务要执行,也就是 ES6 的任务队列比事件循环中的任务(事件)队列优先级更高
- 先执行同步代码再执行异步代码,先执行微任务再执行宏任务
- 异步任务队列中也就分成了宏任务和微任务,按照宏任务-微任务-宏任务-微任务。。。执行轮询
- 微任务:process.nextTick(node),Promise,MutationObserver(H5新特性)
- 宏任务:整体代码,setTimeout,setInterval,网络请求,setImmediate(node)
精准搜索和模糊搜索
- 模糊搜索通常用filter实现
- 精准搜索通常用find实现,find方法中找到符合条件的项就不会再继续遍历数组
可选链操作符 :?. 形式,目前处于stage3阶段,尚无法直接使用,可以使用babel转换
该操作符允许读取位于连接对象链深处的属性的值,而不必明确验证链中的每个引用是否有效
空值合并运算符(??) :a??b ,当左侧a为null或者undefined时才会返回右侧的b,否则返回a
React
react类组件和hooks相比较 :
在类组件中,起初组件可能比较简单,但是随着我们后期的不断维护,组件逐渐会被越来越多的逻辑和副作用充斥,而在每个生命周期总可能会有很多不相关的逻辑,而hook能够将组件中互相关联的部分拆分成更小的函数,而不必按照生命周期划分,不同的功能可以放在多个hooks中完成
使用class就意味着要使用和绑定this。去理解JavaScript中的this的工作方式,从概念上看,React组件更像是函数,hooks就完美贴合了这个方向
梳理react
一、来自原生js的问题
- 使用原生DOM的API去操作DOM,方式繁琐且效率较低
- js直接操作DOM,容易引起大量重绘和回流
- 原生js没有组件化概念,代码复用性低,维护效率低,虽然有模块化概念,但是也是对js部分做模块化,并没有对样式和结构部分做拆分,不论是代码量还是维护上面都是一个很大的问题
二、React特点
- 组件化模式,声明式编程,提高开发效率与复用性
- 在React Native中可以用react预发进行Android、iOS移动端的开发
- 使用了虚拟DOM和diff算法,减少了与真实DOM的交互,提高了性能
三、diff算法
虚拟dom中key的作用
当状态中的数据发生改变时,react会根据新数据生成新的虚拟DOM,然后根据新虚拟DOM和旧虚拟DOM按diff算法进行比较,具体规则如下:
- 在旧的DOM中找与新DOM是否有相同的key,如果找到则进一步比对两者内容是否相同,如果也一样,则使用之前的真实DOM,如果内容不一样,则生成新的DOM替换旧的DOM
- 若旧的DOM中没有找到与新DOM中相同的key,就生成新的真实DOM并渲染
使用index作为key可能带来的问题
- 若对数据进行逆序操作可能会造成不必要的DOM更新
- 若结构中包含输入类DOM,可能会产生错误的DOM更新,出现界面异常
建议使用的key
- 使用唯一id
- 自定义一个工具函数生成随机id
- 简单数据类型下也可以使用index
**高阶函数:**如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数。
1.若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数。
2.若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数。常见的高阶函数有:Promise、setTimeout、arr.map()等等
**函数的柯里化:**通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。
单向数据流: 可以认为单向数据流的一个好处在于状态可追溯。例如,在父组件中维护一个状态,传递给子组件,在子组件中能够任意更改这个状态,或者还能够随意更改祖宗组件的状态,那么各组件状态就会变得难以追溯,单向数据流则保证不会被子组件意外修改,如果要修改,只能通过dispatch发送一个action来做全局的修改,再由这个全局的状态分发给子组件,或者调用父组件的方法来修改,这些操作都是可见的容易追溯的。
CSS部分
有关屏幕底部安全区与元素定位的问题
移动端底部是存在一个安全区域的,这个区域是用来执行手机的按键操作,用户无法在这个区域执行交互
css解决方案:移动浏览器将这些安全区域存储在环境变量中,供我们使用
env(safe-area-inset-bottom) // or -top
.btn {
position: fixed;
right: 0px;
left: 0px;
bottom: 0px;
padding-bottom: calc(env(safe-area-inset-bottom) + 20px);
}
自定义浏览器滚动条外观
scroll-behavior
容器滚动行为,让滚动效果更丝滑overscroll-behavior
优化滚动边界,可以帮助我们滚动的穿透
这个 ::-webkit-scrollbar
CSS伪类选择器影响了一个元素的滚动条的样式
该方法尚处于非标准阶段,尽量不要在生产环境中使用它1
该属性下有七个伪类元素提供了对浏览器滚动样式的修改:
::-webkit-scrollbar
— 整个滚动条::-webkit-scrollbar-button
— 滚动条上的按钮 (上下箭头)::-webkit-scrollbar-thumb
— 滚动条上的滚动滑块::-webkit-scrollbar-track
— 滚动条轨道::-webkit-scrollbar-track-piece
— 滚动条没有滑块的轨道部分::-webkit-scrollbar-corner
— 当同时有垂直滚动条和水平滚动条时交汇的部分::-webkit-resizer
— 某些元素的corner部分的部分样式(例:textarea的可拖动按钮),交汇部分
body {
height: 3000px;
}
/* 这个属性是必须要加的,加了之后下面的其他伪类才会生效 */
html::-webkit-scrollbar {
width: 30px;
height: 30px;
}
html::-webkit-scrollbar-thumb {
background: -webkit-gradient(linear, left top, left bottom, from(#ff8a00), to(#da1b60));
background: linear-gradient(to bottom, #ff8a00, #da1b60);
border-radius: 30px;
-webkit-box-shadow: inset 2px 2px 2px rgba(255, 255, 255, 0.25), inset -2px -2px 2px rgba(0, 0, 0, 0.25);
box-shadow: inset 2px 2px 2px rgba(255, 255, 255, 0.25), inset -2px -2px 2px rgba(0, 0, 0, 0.25);
}
html::-webkit-scrollbar-track {
background: linear-gradient(to right, #201c29, #201c29 1px, #100e17 1px, #100e17);
}
CSS自定义属性
CSS自定义属性也叫CSS变量,简单来说就是一种开发者可以自主命名和使用的 CSS 属性。浏览器在处理像 color 、background这样的属性时,需要接收特定的属性值,而自定义属性,在开发者赋予它属性值之前,它是没有意义的,也就是说在我们没定义之前使用是无效的
自定义属性由--
开头,以便浏览器能区分自定义属性和原生属性;同样,只是自定义了属性和属性值,也是无效的
定义:
.customProp{
color:red;
--custom-color:red;/* 此处定义了一个自定义属性,但是是无效的,颜色仍然由前面的color决定,此处只能表示定义了一个自定义属性,而没有使用它 */
--theme-color:blue;
}
使用: 使用var()来实现
:root{
--custom-color:red;
/*定义方式一,定义在文档根元素中:root 选择器匹配文档根元素*/
}
.fatherDiv{
--custom-color:red;
}
.fatherDiv .div{
background:var(--custom-color);/*定义方式二,定义在父元素中,在子元素中使用*/
color:var(--custom-color,grey); /*var具有第二个参数,第二个参数用来做缺省值,当无法获取到第一个参数的自定义属性值的时候读取第二个参数的值*/
}
<body>
<div class="fatherDiv">
fahter div
<div></div>
</div>
</body>
/*自定义属性就是一个变量,也就是说他可以使用在任意的一个选择器和属性里面*/
.div{
background-color:var(--custom-color);
color:var(--custom-color);
}
.div image{
background:var(--custom-color);
}
/*同样的,自定义属性也可以作为缺省值*/
.div{
background:var(--custom-color,var(--theme-color));
}
CSS3新增伪类
查看更多详细CSS3伪类
:is()
减少重复
/* 使用之前 */
.embed .save-button:hover,
.attachment .save-button:hover {
opacity: 1;
}
/* 使用之后 */
:is(.embed, .attachment) .save-button:hover {
opacity: 1;
}
/*额,讲道理,这玩意儿好像用处不是很大,在标准的css代码中或许有用,但是在使用了sass或者less中,则更喜欢使用嵌套,这个伪类就不怎么用得上了*/
:root
选择文档根元素
/*这个很好理解,就是选中文档根元素HTML*/
:root{
color:red;
}
:not()
非指定元素选择
:not(p){/*选中非p标签元素*/
background:red;
}
<div>
<p></p>
<div></div>
<p></p>
<div></div>
</div>
:empty
匹配没有子元素(包括文本节点)的每个元素
p:empty{
background:#fff;
}
简单的js实战技巧
- 使用history.back()创建一个浏览器返回按钮
<button onclick="history.back()">返回</button>
- 数字分隔符
const testNum = 1_000_000_000;
console.log(testNum); // 1000000000
- 事件监听器只运行一次
const element = document.getElementById("btn");
element.addEventListener("click", () => console.log("I run only once"), {
once: true,
});
- console.log()包装变量
const testStr = "abc";
console.log({ testStr });//实际上就是利用一下对象解构
- 从数组中获取最大值/最小值
可以使用
Math.min()
或Math.max()
结合扩展运算符来查找数组中的最小值或最大值。
const numbers = [6, 8, 1, 3, 9];
console.log(Math.max(...numbers)); // 9
console.log(Math.min(...numbers)); // 1
- 复制到剪切板
使用
Clipboard
API 创建“复制到剪贴板”功能
function copyToClipboard(text) {
navigator.clipboard.writeText(text);
}
- 获取鼠标位置
可以使用
MouseEvent
对象下 clientX 和 clientY 的属性值,来获取鼠标的当前位置坐标信息
const element = document.getElementById("btn");// 页面元素
element.addEventListener("mousemove", (e) => {// 给当前页面元素添加鼠标移动事件
console.log(`Mouse X: ${e.clientX}, Mouse Y: ${e.clientY}`);
});
- 缩短数组
直接设置数组的length属性来缩短数组
const arr = [1, 2, 3, 4, 5, 6];
arr.length = 5;
console.log(arr);
- console.table()打印特定形式的表格
语法
console.table(data,[,columns]);
// data:要展示的数据,是一个对象,通常是一个数组对象
// columns: 要展示的列名的数组,该项为可选项
实例展示:
const a = {
a: 123,
b: 456,
};
const b = {
a: "hahah",
b: "lalala",
};
const c = {
a: "hello",
b: "world",
};
console.table([a, b, c], ["a"]);
- 字符串转数字
const str = '123';
console.log(+str) // 123;
- 数字转字符串
const num = 123;
console.log(num+'') // '123';
- 数组中过滤所有的虚值
const arr = [
1,
undefined,
null,
NaN,
2,
null,
"hello world",
true,
3,
false,
];
console.log(arr.filter(Boolean)); // [1, 2, "hello world", true, 3],剔除了NaN,undefined,null,false
- 一个使用includes实践
const myLn = "JavaScript";
const languages = ["Java", "php", "JavaScript","C++"];
// 写法一
if (myLn === "Java" || myLn === "php" || myLn === "JavaScript"||myLn ==="C++") {
// 操作
}
// includes写法
if (languages.includes(myLn)) {
// 操作
}
- console.log()带样式
console.log("%c hello world", "color:orange;font-size:20px");
- 一行代码生成随机字符串
const str = Math.random().toString(36).substr(2, 10);
// Math.random()用来生成[0,1)的随机数
// 掉用toString方法转换成36进制,转换36进制中将会包含0-9,a-z
// 这样生成的数组是类似于0.p428dqe1rhc这样的
// 再用substr截取后面的十位作为随机字符
- 检测是否打开caps lock(大写锁定)
const eleBtn = document.getElementById('int');
eleBtn.addEventListener('keyup', function (event) {
if (event.getModifierState('CapsLock')) {
// CapsLock 已经打开了
console.log('okk');
}
});
删除对象的属性
要删除某一个对象的某个属性,使用delete来实现:
const obj = { a: 10 };
delete obj.a;
console.log(obj);// {}
JavaScript中的this
- 普通函数中,this指向window
function a() {
console.log(this);
}
a();
// 这里实际上就相当于是window调用了函数a, window.a();所以这里的this也指向window
- 有事件源的情况下,指向事件源本身
document.onclick = function () {
console.log(this);
};
// 这里的函数如果使用箭头函数,则会再次指向window,具体可了解箭头函数的相关特性
- 定时器中,指向window,ES6后箭头函数下指向其父级
function test() {
setTimeout(function () {
console.log(this);
}, 0);
}
test();
如果setTimeout中使用箭头函数,则指向其父级:
document.onclick = function () {
setTimeout(() => {
console.log(this);
}, 0);
};
- 对象中,this指向其对象本身
const obj = {
a: function () {
console.log(this);
},
};
obj.a();// {a: ƒ}
// 如果改为箭头函数,同样指向window
web安全问题
-
SQL注入
- SQL注入攻击的核心在于让Web服务器执行攻击者期望的SQL语句,以便得到数据库中的他想要的数据或者对数据库进行读取、修改、删除、插入等操作,达到其目的。
- SQL注入的常规套路在于将SQL语句放置于Form表单或请求参数之中提交到后端服务器,而后端服务器如果未做输入安全校验,直接将变量取出进行数据库查询,则极易中招
-
XSS攻击
-
XSS全称跨站脚本攻击(Cross Site Scripting),为了与重叠样式表CSS区分,因此缩写XSS
-
XSS攻击的核心是将可执行的前端脚本代码(一般为JavaScript)植入到网页中,通俗来说就是攻击者想让你的浏览器执行他写的JS代码
-
反射型
- 攻击者将JS代码作为请求参数放置URL中,诱导用户点击
http://localhost:3000/test?name=<script>alert("How are you!")</script>
- 用户点击后,该JS代码作为请求参数传给Web服务器后端
- 后端服务器没有检查过滤,简单处理后放入网页正文中返回给浏览器
- 浏览器解析返回的网页,中招
-
存储型
- 攻击者网页回帖,帖子中包含JS脚本
- 回帖提交服务器后,存储至数据库
- 其他网友查看帖子,后台查询该帖子的回帖内容,构建完整网页,返回浏览器
- 该网友浏览器渲染返回的网页,中招
-
-
CSRF攻击(跨站请求伪造)
- 其核心思想在于,在打开A网站的情况下,另开Tab页面打开恶意网站B,此时在B页面的“唆使”下,浏览器发起一个对网站A的HTTP请求
- 危害性在于:这个HTTP请求不是用户主动意图,而是B“唆使的”,如果是一个危害较大的请求操作(发邮件?删数据?等等)那就麻烦了;因为之前A网站已经打开了,浏览器存有A下发的Cookie或其他用于身份认证的信息,这一次被“唆使”的请求,将会自动带上这些信息,A网站后端是无法分清这是否是用户的真实意愿的
-
DDOS攻击(DDoS全称Distributed Denial of Service:分布式拒绝服务攻击,是拒绝服务攻击的升级版。拒绝攻击服务顾名思义,让服务不可用。常用于攻击对外提供服务的服务器)
- 攻击者不断地提出服务请求,让合法用户的请求无法被及时处理,这就是 DoS 攻击
- 攻击者使用多台计算机或者计算机集群进行 DoS 攻击,就是 DDoS 攻击
- 在早期互联网技术还没有那么发达的时候,发起DoS攻击是一件很容易的事情:一台性能强劲的计算机,写个程序多线程不断向服务器进行请求,服务器应接不暇,最终无法处理正常的请求,对别的正常用户来说,看上去网站貌似无法访问,这便是拒绝服务了
- 后来随着技术对发展,现在的服务器早已不是一台服务器那么简单,你访问一个www.baidu.com的域名,背后是数不清的CDN节点,数不清的Web服务器
- 技术从来都是一柄双刃剑,分布式技术既可以用来提供高可用的服务,也能够被攻击方用来进行大规模杀伤性攻击。攻击者不再局限于单台计算机的攻击能力,转而通过成规模的网络集群发起拒绝服务攻击
-
DNS劫持
- 当今互联网流量中,以HTTP/HTTPS为主的Web服务产生的流量占据了绝大部分。Web服务发展的如火如荼,这背后离不开一个默默无闻的大功臣就是域名解析系统
- 如果没有DNS,我们上网需要记忆每个网站的IP地址而不是他们的域名,这简直是灾难,好在DNS默默在背后做了这一切,我们只需要记住一个域名,剩下的交给DNS来完成
- DNS提供服务用来将域名转换成IP地址,然而在早期协议的设计中并没有太多考虑其安全性,对于查询方来说:我去请求的真的是一个DNS服务器吗?是不是别人冒充的?查询的结果有没有被人篡改过?这个IP真是这个网站的吗?
- DNS协议中没有机制去保证能回答这些问题,因此DNS劫持现象非常泛滥,从用户在地址栏输入一个域名的那一刻起,一路上的凶险防不胜防:
- 本地计算机中的木马修改hosts文件
- 本地计算机中的木马修改DNS数据包中的应答
- 网络中的节点(如路由器)修改DNS数据包中的应答
- 网络中的节点(如运营商)修改DNS数据包中的应答
- 后来,为了在客户端对收到对DNS应答进行校验,出现了DNSSEC技术,一定程度上可以解决上面的部分问题。但限于一些方面的原因,这项技术并没有大规模用起来,尤其在国内,鲜有部署应用
- 再后来,以阿里、腾讯等头部互联网厂商开始推出了httpDNS服务,来了一招釜底抽薪,虽然这项技术的名字中还有DNS三个字母,但实现上和原来但DNS已经是天差地别,通过这项技术让DNS变成了在http协议之上的一个应用服务
-
JSON劫持
- JSON是一种轻量级的数据交换格式,而劫持就是对数据进行窃取(或者应该称为打劫、拦截比较合适。恶意攻击者通过某些特定的手段,将本应该返回给用户的JSON数据进行拦截,转而将数据发送回给恶意攻击者,这就是JSON劫持的大概含义。一般来说进行劫持的JSON数据都是包含敏感信息或者有价值的数据。
-
暴力破解
- 这个一般针对密码而言,弱密码(Weak Password)很容易被别人(对你很了解的人等)猜到或被破解工具暴力破解
- 解决方案:
- 增加密码复杂度
- 限制尝试次数
- 。。。
-
HTTP报头追踪漏洞
- HTTP/1.1(RFC2616)规范定义了 HTTP TRACE 方法,主要是用于客户端通过向 Web 服务器提交 TRACE 请求来进行测试或获得诊断信息
- 当 Web 服务器启用 TRACE 时,提交的请求头会在服务器响应的内容(Body)中完整的返回,其中 HTTP 头很可能包括 Session Token、Cookies 或其它认证信息。攻击者可以利用此漏洞来欺骗合法用户并得到他们的私人信息
- 解决方案:禁用 HTTP TRACE 方法
-
信息泄露
- 由于 Web 服务器或应用程序没有正确处理一些特殊请求,泄露 Web 服务器的一些敏感信息,如用户名、密码、源代码、服务器信息、配置信息等
- 注意的点:应用程序报错时,不对外产生调试信息 过滤用户提交的数据与特殊字符 保证源代码、服务器配置的安全
-
目录遍历漏洞
- 攻击者向 Web 服务器发送请求,通过在 URL 中或在有特殊意义的目录中附加 …/、或者附加 …/ 的一些变形(如 … 或 …// 甚至其编码),导致攻击者能够访问未授权的目录,以及在 Web 服务器的根目录以外执行命令
-
命令执行漏洞
- 命令执行漏洞是通过 URL 发起请求,在 Web 服务器端执行未授权的命令,获取系统信息、篡改系统配置、控制整个系统、使系统瘫痪等
-
文件上传漏洞
- 如果对文件上传路径变量过滤不严,并且对用户上传的文件后缀以及文件类型限制不严,攻击者可通过 Web 访问的目录上传任意文件,包括网站后门文件(webshell),进而远程控制网站服务器
- 防护:在开发网站及应用程序过程中,需严格限制和校验上传的文件,禁止上传恶意代码的文件 限制相关目录的执行权限,防范 webshell 攻击
-
其他漏洞
- SSLStrip 攻击
- OpenSSL Heartbleed 安全漏洞
- CCS 注入漏洞
- 证书有效性验证漏洞
-
业务漏洞
- 一般业务漏洞是跟具体的应用程序相关,比如参数篡改(连续编号 ID / 订单、1 元支付)、重放攻击(伪装支付)、权限控制(越权操作)等
-
框架或应用漏洞
- WordPress 4.7 / 4.7.1:REST API 内容注入漏洞
- Drupal Module RESTWS 7.x:Remote PHP Code Execution
- SugarCRM 6.5.23:REST PHP Object Injection Exploit
- Apache Struts:REST Plugin With Dynamic Method Invocation Remote Code Execution
- Oracle GlassFish Server:REST CSRF
- QQ Browser 9.6:API 权限控制问题导致泄露隐私模式
- Hacking Docker:Registry API 未授权访问
关于HTML的几个标签
head标签
-
head
标签与html
标签,body
标签一样是文档必须的元素 -
head
标签用于定于文档头部信息,它是所有头部元素的容器。head
中的元素可以引用脚本、指示浏览器在哪里找到样式表、提供元信息等等 -
文档的头部描述了文档的各种属性和信息,包括文档的标题、在 Web 中的位置以及和其他文档的关系等。绝大多数文档头部包含的数据都不会真正作为内容显示给读者
-
下面这些标签可用在
head
部分:base
,link
,meta
,script
,style
, 以及title
-
注意:
应该把head
标签放在文档的开始处,紧跟在html
后面,并处于body
标签或frameset
标签之前
title标签
title
定义文档的标题,它是head
部分中唯一必需的元素。浏览器会以特殊的方式来使用标题,设置的内容不会显示在页面中,通常把它放置在浏览器窗口的标题栏或状态栏上,如设置为空标题展示当前页面的地址信息- 当把文档加入用户的链接列表或者收藏夹或书签列表时,标题将成为该文档链接的默认名称
- dir属性
- 规定元素中内容的文本方向
rtl
、ltr
- 规定元素中内容的文本方向
- lang属性
- 规定元素中内容的语言代码
meta标签
meta
元素往往不会引起用户的注意,但是meta
对整个网页有影响,对网页能否被搜索引擎检索,和在搜索中的排名起着关键性的作用meta
有个必须的属性content
用于表示需要设置的项的值meta
存在两个非必须的属性http-equiv
和name
, 用于表示要设置的项- 比如
<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">
,设置的项是Content-Security-Policy
设置的值是upgrade-insecure-requests
-
http-equiv属性
-
http-equiv
一般设置的都是与http
请求头相关的信息,设置的值会关联到http头部。也就是说浏览器在请求服务器获取html
的时候,服务器会将html
中设置的meta
放在响应头中返回给浏览器。常见的类型比如content-type
,expires
,refresh
,set-cookie
,window-target
,charset
,pragma
等等-
content-type
- 比如:
<meta http-equiv="content-type" content="text/html charset=utf8">
可以用来声明文档类型,设置字符集,content-type
几乎所有的属性都可以在meta
中进行设置
content-type: text/html charset=utf8
- 比如:
-
expires
- 用于设置浏览器的过期时间, 其实就是响应头中的expires属性
<meta http-equiv="expires" content="31 Dec 2021"> // expires:31 Dec 2008
-
refresh
- 该种设定表示5秒自动刷新并且跳转到指定的网页。如果不设置url的值那么浏览器则刷新本网页
<meta http-equiv="refresh" content="5 url=http://www.baidu.com">
-
window-target
- 强制页面在当前窗口以独立页面显示, 可以防止别人在框架中调用自己的页面
<meta http-equiv="window-target" content="_top'>
-
pragma
- 禁止浏览器从本地计算机的缓存中访问页面的内容
<meta http-equiv="pragma" content="no-cache">
-
-
-
name属性
name
属性主要用于描述网页,与对应的content
中的内容主要是便于搜索引擎查找信息和分类信息用的, 用法与http-equiv
相同,name
设置属性名,content
设置属性值
-
author
author
用来标注网页的作者
<meta name="author" content="jiangxin_max">
-
description
description
用来告诉搜素引擎当前网页的主要内容,是关于网站的一段描述信息
<meta name="description" content="这是HTML">
-
keywords
keywords
设置网页的关键字,来告诉浏览器关键字是什么。是一个经常被用到的名称。它为文档定义了一组关键字。某些搜索引擎在遇到这些关键字时,会用这些关键字对文档进行分类
<meta name="keywords" content="Hello world">
-
generator
- 表示当前
html
是用什么工具编写生成的,并没有实际作用,一般是编辑器自动创建的
<meta name="generator" content="vscode">
- 表示当前
-
revised
- 指定页面的最新版本
<meta name="revised" content="V2,2015/10/1">
-
robots
- 告诉搜索引擎机器人抓取哪些页面,
all / none / index / noindex / follow / nofollow
<meta name="robots" content="all">
all
:文件将被检索,且页面上的链接可以被查询;none
:文件将不被检索,且页面上的链接不可以被查询;index
:文件将被检索;follow
:页面上的链接可以被查询;noindex
:文件将不被检索,但页面上的链接可以被查询;nofollow
:文件将不被检索,页面上的链接可以被查询
- 告诉搜索引擎机器人抓取哪些页面,
-
scheme属性
scheme
属性用于指定要用来翻译属性值的方案。此方案应该在由head
标签的profile
属性指定的概况文件中进行了定义。html5
不支持该属性
base标签
base
标签定义了文档的基础url
地址,在文档中所有的相对地址形式的url
都是相对于这里定义的url
而言的。为页面上的链接规定默认地址或目标
<base href="http://www.w3school.com.cn/api/" target="_blank" />
- href
- href
是必选属性,指定了文档的基础
url地址。例如,如果希望将文档的基础URL定义为https://www.abc.com
,则可以使用如下语句:<base href="http://www.abc.com">
如果文档的超链接指向hello.html
,则它实际上指向的是如下url
地址:`https://www.abc.com/welocme.html
- href
- target
- 定义了当文档中的
链接
点击后的打开方式_blank
,_self
,_parrent
,_top
- 定义了当文档中的
link标签
link
用于引入外部样式表,在html
的头部可以包含任意数量的link
,link
标签有以下常用属性
<link type="text/css" rel="stylesheet" href="github-markdown.css">
- type
- 定义包含的文档类型,例如
text/css
- 定义包含的文档类型,例如
- rel
- 定义
html
文档和所要包含资源之间的链接关系,可能的值有很多,最为常用的是stylesheet
,用于包含一个固定首选样式的表单
- 定义
- href
- 表示指向被包含资源的
url
地址
- 表示指向被包含资源的
style标签
用于编写HTML内部样式表的标签
script标签
加载javascript
脚本的标签。加载的脚本会被默认执行。默认情况下当浏览器解析到script
标签的时候会停止html
的解析而开始加载script
代码并且执行
<script src="script.js"></script>
-
type
<script type="text/javascript"> //指示脚本的MIME类型
-
async
- 规定异步执行脚本,仅适用于通过
src
引入的外部脚本。设置的async
属性的script
加载不会影响后面html
的解析,加载是与文档解析同时发生的。加载完成后立即执行。执行过程会停止html
文档解析
<script async src="script.js"></script>
- 规定异步执行脚本,仅适用于通过
-
charset
- 规定在外部脚本文件中使用的字符编码
<script type="text/javascript" src="script.js" charset="UTF-8"></script>
-
defer
- 规定是否对脚本执行进行延迟,直到页面加载为止。设置了
defer
属性的script
不会阻止后面html
的解析,加载与解析是共同进行的,但是script
的执行要在所有元素解析完成之后,DOMContentLoaded
事件触发之前完成
<script defer src="script.js"></script>
- 规定是否对脚本执行进行延迟,直到页面加载为止。设置了
-
src
// 外部脚本地址,无需赘述 <script src="script.js"></script>
-
language
- 规定脚本语言,与``type```功能类似,不建议使用该字段
bgsound
网站背景音乐
<bgsound src="music.mp3" autostart="true" loop="5">
- src
- 表示背景音乐的
url
值
- 表示背景音乐的
- autostart
- 是否自动播放
ture
自动播放,false
不播放,默认为false
- 是否自动播放
- loop
- 是否重复播放,值为数字或者
infinite
,表示重复具体次或无限次
- 是否重复播放,值为数字或者
浅谈CommonJs和ES Module
一、为何要使用模块化
早期 JavaScript 开发很容易存在全局污染和依赖管理混乱问题。这些问题在多人开发前端应用的情况下变得更加棘手和频繁。
-
全局污染
在多人开发的条件下,如果没有模块化,出现全局污染是很容易的一件事,js的内部变量是可以互相污染的。比如A开发了一个a.js的文件,B开发了一个b.js的文件,他们均在各自的文件中声明了一个name的变量,A的name是一个字符串,B的name是一个函数,他们均进行了多次调用,结果B在调用时发现自己的name变成了一个字符串,从而导致了一系列问题。在实际的项目开发中,可以使用匿名函数自执行的方式,形成独立的块级作用域解决这个问题。
(function (){ function name(){ //...do something } })()
而在实际开发中情况会更加复杂,我们并不能保证类似问题都能妥善解决,因此不使用模块化开发很容易暴露出很多问题
-
依赖管理
<body> <script src="./a.js"></script> <script src="./b.js"></script> <script src="./c.js"></script> </body>
依赖管理也是一个严重的问题。如上,正常情况下,执行 js 的先后会按照 script 标签排列的前后顺序来执行。那么如果这三个 js 之间有依赖关系,那么它们又应该如何处理呢?
- 加入我们在三个js文件中都定义了一个公共的方法:func1,func2,func3
- 那么我们能在下层的js中调用上层js的公共方法,但是却不能在上层js中调用下层定义的方法
- 此时我们就需要模块化来解决这个问题了
二、CommonJs
Commonjs
的提出,弥补了 Javascript 对于模块化,没有统一标准的缺陷。nodejs 借鉴了 Commonjs
的 Module ,实现了良好的模块化管理。
目前CommonJS的主要应用场景:
- 服务端nodejs是使用CommonJS的代表
- 客户端browserify使用CommonJs实现
webpack
打包工具对 CommonJS 的支持和转换;使得前端应用可以在编译之前,使用 CommonJS 进行开发
- CommonJs使用原理
- 在
commonjs
中每一个 js 文件都是一个单独的模块,我们称之为 module - 该模块中,包含 CommonJS 规范的核心变量: exports、module.exports、require
- exports 和 module.exports 负责对模块的内容进行导出
- require 函数可以帮助我们导入其他模块(自定义模块、系统模块、第三方库模块)中的内容
- 在
commonJS实现原理
首先每个模块文件上存在 module
,exports
,require
三个变量,然而这三个变量是没有被定义的,但是我们可以在 Commonjs 规范下每一个 js 模块上直接使用它们。在 nodejs 中还存在 __filename
和 __dirname
变量。
module
记录当前模块信息require
引入模块的方法exports
当前模块导出的属性- 在 Commonjs 规范下模块中,会形成一个包装函数,我们写的代码将作为包装函数的执行上下文,使用的
require
,exports
,module
本质上是通过形参的方式传递到包装函数中的
-
require文件加载流程
以nodejs作参考:
const fs = require('fs') // 核心模块 - nodejs底层模块 const myName = require('./index.js') //文件模块 - 我们自己编写的模块 const crypto = require('crypto-js') // 第三方自定义模块 - 通过npm下载的第三方模块
当 require 方法执行的时候,接收的唯一参数作为一个标识符 ,Commonjs 下对不同的标识符,处理流程不同,但是目的相同,都是找到对应的模块
require加载标识符原则
nodejs
中对标识符的处理原则:- 首先像 fs ,http ,path 等标识符,会被作为 nodejs 的核心模块
./
和../
作为相对路径的文件模块,/
作为绝对路径的文件模块- 非路径形式也非核心模块的模块,将作为自定义模块
核心模块处理:
核心模块的优先级仅次于缓存加载,在
Node
源码编译中,已被编译成二进制代码,所以加载核心模块,加载过程中速度最快路径形式的模块文件处理:
以
./
,../
和/
开始的标识符,会被当作文件模块处理。require()
方法会将路径转换成真实路径,并以真实路径作为索引,将编译后的结果缓存起来,第二次加载的时候会更快**自定义模块处理:**自定义模块,一般指的是非核心的模块,它可能是一个文件或者一个包,它的查找会遵循以下原则:
- 在当前目录下的
node_modules
目录查找 - 如果没有,在父级目录的
node_modules
查找,如果没有在父级目录的父级目录的node_modules
中查找 - 沿着路径向上递归,直到根目录下的
node_modules
目录 - 在查找过程中,会找
package.json
下 main 属性指向的文件,如果没有package.json
,在 node 环境下会以此查找index.js
,index.json
,index.node
-
require模块引入与处理
CommonJS 模块同步加载并执行模块文件,CommonJS 模块在执行阶段分析模块依赖,采用深度优先遍历(depth-first traversal),执行顺序是父 -> 子 -> 父
require加载原理
module
:在 Node 中每一个 js 文件都是一个 module ,module 上保存了 exports 等信息之外,还有一个loaded
表示该模块是否被加载- false表示为加载
- true表示已加载
Module
:以 nodejs 为例,整个系统运行之后,会用Module
缓存每一个模块加载的信息require避免重复加载
首先加载之后的文件的
module
会被缓存到Module
上,比如一个模块已经 require 引入了 a 模块,如果另外一个模块再次引用 a ,那么会直接读取缓存值 module ,所以无需再次执行模块。 -
require动态加载
require 可以在任意的上下文,动态加载模块
require 本质上就是一个函数,那么函数可以在任意上下文中执行,来自由地加载其他模块的属性方法
三、ES Module
Nodejs
借鉴了 Commonjs
实现了模块化 ,从 ES6
开始, JavaScript
才真正意义上有自己的模块化规范
Es Module 的产生有很多优势,比如:
- 借助 `Es Module` 的静态导入导出的优势,实现了 `tree shaking`
- `Es Module` 还可以 `import()` 懒加载方式实现代码分割
在 Es Module
中用 export
用来导出模块,import
用来导入模块
但是 export
配合 import
会有很多种组合情况:
-
导出 export 和导入 import
-
export 导出,import 导入
export导出:
const str = 'abc' export { str }
import导入:
import { str } from './a.js'
-
默认导出 export default
导出:
const str = 'abc' export default str;
导入:
import ms form './a.js'
export default any
导入 module 的默认导出。any
可以是函数,属性方法,或者对象- 对于引入默认导出的模块,
import anyName from 'moduleName'
, anyName 可以是自定义名称
-
-
混合导入/导出
ES6 module 可以使用 export default 和 export 导出多个属性
导出:
export const authorName = 'jiangxin_max' export default function testName (){ console.log('my name is Lucy.') }
导入方式一:
import theName , { authorName as bookAuthor } from './a.js'
导入方式二:
import theName, * as MsAuthor from './a'
ES Module特性
-
静态语法
ES6 module 的引入和导出是静态的,
import
会自动提升到代码的顶层 ,import
,export
不能放在块级作用域或条件语句中function testName(){ import name from './a.js' export const myName = 'jiangxin_max' } // error isShow && export const name = 'jiangxin_max'; // error import 'abc' from './a.js' ;// error let name = 'jiangxin_max' import 'default' + name from './b.js'
-
执行特性
ES6 module 和 Common.js 一样,对于相同的 js 文件,会保存静态属性
但是与 Common.js 不同的是 ,
CommonJS
模块同步加载并执行模块文件,ES6 模块提前加载并执行模块文件,ES6 模块在预处理阶段分析模块依赖,在执行阶段执行模块,两个阶段都采用深度优先遍历,执行顺序是子 -> 父 -
导出绑定
不能修改import导入的属性
a.js中定义导出:
export let num = 1 export const addNumr = ()=>{ num++ }
b.js中使用:
import { num , addNum } from './a' num = 2 // 使用报错
属性绑定
import { num , addNum } from './a' console.log(num) // num = 1 addNum() console.log(num) // num = 2
- 使用 import 被导入的模块运行在严格模式下
- 使用 import 被导入的变量是只读的,可以理解默认为 const 装饰,无法被赋值
- 使用 import 被导入的变量是与原变量绑定/引用的,可以理解为 import 导入的变量无论是否为基本类型都是引用传递
-
四、CommonJS和ES Module
Commonjs 总结
- CommonJS 模块由 JS 运行时实现
- CommonJs 是单个值导出,本质上导出的就是 exports 属性
- CommonJS 是可以动态加载的,对每一个加载都存在缓存,可以有效的解决循环引用问题
- CommonJS 模块同步加载并执行模块文件
ES Module 总结
- ES6 Module 静态的,不能放在块级作用域内,代码发生在编译时
- ES6 Module 的值是动态绑定的,可以通过导出方法修改,可以直接访问修改结果
- ES6 Module 可以导出多个属性和方法,可以单个导入导出,混合导入导出
- ES6 模块提前加载并执行模块文件
- ES6 Module 导入模块在严格模式下
- ES6 Module 的特性可以很容易实现 Tree Shaking 和 Code Splitting
Tree Shaking 是一种通过消除最终文件中未使用的代码来优化体积的方法
Git
摘抄于微信公众号: Vue中文社区
详细页面地址
-
基本概念
-
Git的优势
Git是一个分布式代码管理工具,在讨论分布式之前避免不了提及一下什么是中央式代码管理仓库
- 中央式:所有的代码保存在中央服务器,所以提交必须依赖网络,并且每次提交都会带入到中央仓库,如果是协同开发可能频繁触发代码合并,进而增加提交的成本和代价。最典型的就是svn
- 分布式:可以在本地提交,不需要依赖网络,并且会将每次提交自动备份到本地。每个开发者都可以把远程仓库clone一份到本地,并会把提交历史一并拿过来。代表就是Git
-
文件状态
在Git中文件大概分为三种状态:已修改(modified)、已暂存(staged)、已提交(committed)
- 修改:Git可以感知到工作目录中哪些文件被修改了,然后把修改的文件加入到modified区域
- 暂存:通过add命令将工作目录中修改的文件提交到暂存区,等候被commit
- 提交:将暂存区文件commit至Git目录中永久保存
-
commit节点
在Git中每次提交都会生成一个节点,而每个节点都会有一个哈希值作为唯一标示,多次提交会形成一个线性节点链(不考虑merge的情况)
-
HEAD
HEAD是Git中非常重要的一个概念,你可以称它为指针或者引用,它可以指向任意一个节点,并且指向的节点始终为当前工作目录,换句话说就是当前工作目录(也就是你所看到的代码)就是HEAD指向的节点
HEAD是Git中非常重要的一个概念,你可以称它为指针或者引用,它可以指向任意一个节点,并且指向的节点始终为当前工作目录,换句话说就是当前工作目录(也就是你所看到的代码)就是HEAD指向的节点
-
远程仓库
虽然Git会把代码以及历史保存在本地,但最终还是要提交到服务器上的远程仓库。通过clone命令可以把远程仓库的代码下载到本地,同时也会将提交历史、分支、HEAD等状态一并同步到本地,但这些状态并不会实时更新,需要手动从远程仓库去拉取
通过远程仓库为中介,你可以和你的同事进行协同开发,开发完新功能后可以申请提交至远程仓库,同时也可以从远程仓库拉取你同事的代码
因为你和你的同事都会以远程仓库的代码为基准,所以要时刻保证远程仓库的代码质量,切记不要将未经检验测试的代码提交至远程仓库
-
-
分支
-
什么是分支?
分支也是Git中相当重要的一个概念,当一个分支指向一个节点时,当前节点的内容即是该分支的内容,它的概念和HEAD非常接近同样也可以视为指针或引用,不同的是分支可以存在多个,而HEAD只有一个。通常会根据功能或版本建立不同的分支
分支有什么用?
举个例子:你们的 App 经历了千辛万苦终于发布了v1.0版本,由于需求紧急v1.0上线之后便马不停蹄的开始v1.1,正当你开发的兴起时,QA同学说用户反馈了一些bug,需要修复然后重新发版,修复v1.0肯定要基于v1.0的代码,可是你已经开发了一部分v1.1了,此时怎么搞?
此时当然是做分支,一个分支保存你当前开发中的代码,一个分支用来修复v1.0的代码,完成之后合并这两个分支的代码即可。并且修复线上只需要提交v1.0分支代码即可
当在某个节点创建一个分支后,并不会把该节点对应的代码复制一份出来,只是将新分支指向该节点,因此可以很大程度减少空间上的开销。一定要记着不管是HEAD还是分支它们都只是引用而已,量级非常轻
-
-
命令详解
-
提交相关
前面我们提到过,想要对代码进行提交必须得先加入到暂存区,Git中是通过命令 add 实现
添加某个文件到暂存区:
Git add 文件路径
添加所有文件到暂存区:
Git add .
同时Git也提供了撤销工作区和暂存区命令
撤销工作区改动:
git checkout -- 文件名
清空暂存区:
git reset HEAD文件名
将改动文件加入到暂存区后就可以进行提交了,提交后会生成一个新的提交节点:
git commit -m '提交的备注信息'
-
分支相关
创建分支:
Git branch 分支名
切换分支:
Git checkout 分支名
创建一个新分支后并切换到新分支:
Git checkout -b 分支名
删除分支:
为了保证仓库分支的简洁,当某个分支完成了它的使命后应该被删除。比如前面所说的单独开一个分支完成某个功能,当这个功能被合并到主分支后应该将这个分支及时删除
Git checkout -d 分支名
-
合并相关
关于合并的命令是最难掌握同时也是最重要的。我们常用的合并命令大概有三个merge、rebase、cherry-pick
merge:
merge是最常用的合并命令,它可以将某个分支或者某个节点的代码合并至当前分支
Git merge 分支名/节点哈希值
rebase:
rebase也是一种合并指令:
Git rebase 分支名/节点哈希值
与merge不同的是rebase合并看起来不会产生新的节点(实际上是会产生的,只是做了一次复制),而是将需要合并的节点直接累加
rebase相比于merge提交历史更加线性、干净,使并行的开发流程看起来像串行,更符合我们的直觉。既然rebase这么好用是不是可以抛弃merge了?
merge优缺点:
- 每个节点都是严格按照时间排列。当合并发生冲突时,只需要解决两个分支所指向的节点的冲突即可
- 合并两个分支时大概率会生成新的节点并分叉,久而久之提交历史会变成一团乱麻
rebase优缺点:
- 优点:会使提交历史看起来更加线性、干净
- 缺点:虽然提交看起来像是线性的,但并不是真正的按时间排序,当合并发生冲突时,理论上来讲有几个节点rebase到目标分支就可能处理几次冲突
cherry-pick
cherry-pick的合并不同于merge和rebase,它可以选择某几个节点进行合并
Git cherry-pick 节点哈希值
-
-
回退相关
分离HEAD
在默认情况下HEAD是指向分支的,但也可以将HEAD从分支上取下来直接指向某个节点,此过程就是分离HEAD。
git checkout 节点哈希值 //也可以直接脱离分支指向当前节点 git checkout --detach
由于哈希值是一串很长很长的乱码,在实际操作中使用哈希值分离HEAD很麻烦,所以Git也提供了HEAD基于某一特殊位置(分支/HEAD)直接指向前一个或前N个节点的命令
//HEAD分离并指向前一个节点 git checkout 分支名/HEAD^ ------- //HEAD分离并指向前N个节点 git checkout 分支名~N
将HEAD分离出来指向节点有什么用呢?举个例子:如果开发过程发现之前的提交有问题,此时可以将HEAD指向对应的节点,修改完毕后再提交,此时你肯定不希望再生成一个新的节点,而你只需在提交时加上–amend即可
git commit --amend
回退
回退场景在平时开发中还是比较常见的,比如你巴拉巴拉写了一大堆代码然后提交,后面发现写的有问题,于是你想将代码回到前一个提交,这种场景可以通过reset解决
//回退N个提交 git reset HEAD~N
-
远程相关
当我们接触一个新项目时,第一件事情肯定是要把它的代码拿下来,在Git中可以通过clone从远程仓库复制一份代码到本地
Git clone 仓库地址
clone不仅仅是复制代码,它还会把远程仓库的引用(分支/HEAD)一并取下保存在本地
并不是存在服务器上的才能称作是远程仓库,你也可以clone本地仓库作为远程,当然实际开发中我们不可能把本地仓库当作公有仓库,说这个只是单纯的帮助你更清晰的理解分布式
fetch
说的通俗一点,fetch命令就是一次下载操作,它会将远程新增加的节点以及引用(分支/HEAD)的状态下载到本地
Git fetch 远程仓库地址/分支名
pull
pull命令可以从远程仓库的某个引用拉取代码
Git pull 远程分支名
其实pull的本质就是fetch+merge,首先更新远程仓库所有状态到本地,随后再进行合并。合并完成后本地分支会指向最新节点
另外pull命令也可以通过rebase进行合并
Git pull --rebase
push
push命令可以将本地提交推送至远程
Git push 远程分支名
如果直接push可能会失败,因为可能存在冲突,所以在push之前往往会先pull一下,如果存在冲突本地解决。push成功后本地的远程分支引用会更新,与本地分支指向同一节点
总结
- 不管是HEAD还是分支,它们都只是引用而已,引用+节点是 Git 构成分布式的关键
- merge相比于rebase有更明确的时间历史,而rebase会使提交更加线性应当优先使用
- 通过移动HEAD可以查看每个提交对应的代码
- clone或fetch都会将远程仓库的所有提交、引用保存在本地一份
- pull的本质其实就是fetch+merge,也可以加入–rebase通过rebase方式合并
其他
清除蓝梦记事本在鼠标右键的快捷绑定
- 卸载蓝梦,卸载后右键还在
- 查找注册表:计算机HKEY_LOCAL_MACHINESOFTWAREClassesAllFilesystemObjects,删除这个目录下的它的绑定文件
学点英语
AA制里的AA,是什么意思
- AA = Algebraic Average
- algebraic /ˌældʒɪ’breɪɪk/ adj. 代数的,关于代数学的
意思是“平均到单位人数上,分摊”
- AA=All Average
意思是“全部平均”,按人头平均分担账单
- AA=All Apart
意思是“全部分开”,各自付账
A rainy day:雨天;不如意的日子,穷困的日子
pork: 猪肉
beef: 牛肉
veal: 小牛肉
mutton: 羊肉
lamb: 羔羊,小羊
脚注:
相关信息可查看:https://developer.mozilla.org/zh-CN/docs/Web/CSS/::-webkit-scrollbar ↩︎
最后
以上就是超级糖豆为你收集整理的前端基础(一)的全部内容,希望文章能够帮你解决前端基础(一)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复