shiiiiiiji / blog Goto Github PK
View Code? Open in Web Editor NEW拾迹说
拾迹说
对应“js高程ch3”
区分大小写
ECMAScript的变量是松散类型的(可以用来保存任何类型的数据),每一个变量仅仅是一个用于保存值的占位符而已。
变量的定义与初始化
undefined
var
操作符定义的变量将成为定义该变量的作用域中的局部变量var
操作符定义并初始化的变量为全局变量(不推荐这种方式)ECMAScript 数据类型
ECMAScript 不支持任何创建自定义类型的机制,所有值最终都将是上述6种数据类型之一。
typeof
操作符(不是函数!!!)
操作数 | 结果 |
---|---|
未定义变量/未初始化变量/Undefined类型值(undefined) | "undefined" |
Boolean类型值 | "boolean" |
Number类型值 | "number" |
String类型值 | "string" |
Null类型值(null)/Object类型值(除Function) | "object" |
Function | "function" |
Undefined 类型
Null 类型
Boolean 类型
类型 | 为true值 | 为false值 |
---|---|---|
Boolean 类型 | true | false |
Undefined 类型 | - | false |
Null 类型 | - | false |
Number 类型 | 非零数字值(包括无穷大) | 0 和 NaN |
String 类型 | 非空字符串 | ""(空字符串) |
Object 类型 | 任何对象 | - |
Number 类型
Number() - 用于任何数据类型
parseInt() - 用于字符串类型
parseFloat() - 用于字符串类型
String 类型
Object 类型
区分大小写。
标识符:指变量、函数、属性的名字,或者函数的参数。命名要求:
注释:C风格
关键字和保留字
ECMAScript的变量是松散类型的(可以用来保存任何类型的数据),每一个变量仅仅是一个用于保存值的占位符而已。
变量的定义与初始化
undefined
var
操作符定义的变量将成为定义该变量的作用域中的局部变量var
操作符定义并初始化的变量为全局变量(不推荐这种方式)ECMAScript 数据类型
ECMAScript 不支持任何创建自定义类型的机制,所有值最终都将是上述6种数据类型之一。(ES6新定义了Symbol类型、Set类型、Map类型)
typeof
操作符(不是函数!!!)
| 未定义变量/未初始化变量/Undefined类型值(undefined) | "undefined" |
| Boolean类型值 | "boolean" |
| Number类型值 | "number" |
| String类型值 | "string" |
| Null类型值(null)/Object类型值(除Function) | "object" |
| Function | "function" |
Number() - 用于任何数据类型
parseInt() - 用于字符串类型
parseFloat() - 用于字符串类型
js高程(第三版)实现源码
function getQueryStringArgs(){
// 取得查询字符串并去掉开头的问号
var qs = (location.search.length > 0 ? location.search.substring(1) : ""),
// 保存数据的对象
args = {},
// 取得每一项
items = qs.length ? qs.split("&") : [],
item = null,
name = null,
value = null,
// 在 for 循环中使用
i = 0,
len = items.length;
// 逐个将每一项添加到 args 对象中
for(i = 0; i < len; i++) {
item = items[i].split("=");
name = decodeURIComponent(item[0]);
value = decodeURIComponent(item[1]);
if(name.length){
args[name] = value;
}
}
return args;
}
JavaScript 最初的一个应用,就是分担服务器处理表单的职责,打破处处依赖服务器的局面。
<form>
元素HTMLFormElement
类型对象<!-- generic submit button -->
<input type="submit" value="Submit Form">
<!-- custom submit button -->
<button type="submit">Submit Form</button>
<!-- image button -->
<input type="image" src="graphic.gif">
以这种方式提交表单时,浏览器会在将请求发送给服务器之前触发 submit
事件。 => 我们有机会验证表单数据,据决定是否允许表单提交(阻止这个事件的默认行为就可以取消表单提交)
var form = document.getElementById("myForm");
EventUtil.addHandler(form, "submit", function(event){
// 取得事件对象
event = EventUtil.getEvent(event);
// 阻止默认事件
EventUtil.preventDefault(event);
});
submit()
方法也可以提交表单重复提交表单:
<!-- generic reset button -->
<input type="reset" value="Reset Form">
<!-- custom reset button -->
<button type="reset">Reset Form</button>
var form = document.getElementById(“myForm”);
//reset the form
form.reset();
与调用 submit() 方法不同,调用 reset() 方法会像单击重置按钮一样触发 reset 事件。
EventUtil.addHandler(textbox, "keypress", function(event){
event = EventUtil.getEvent(event);
var target = EventUtil.getTarget(event);
var charCode = EventUtil.getCharCode(event);
if (!/\d/.test(String.fromCharCode(charCode)) && charCode > 9 && !event.ctrlKey){
EventUtil.preventDefault(event);
}
});
又称为 WYSIWYG(What You See Is What You Get,所见即所得)
使用 contenteditable 属性
cross-document messaging
XDM
postMessage() 方法与 window 上的 message 事件
不降低同源策略安全性的前提下,在来自不同域的文档间传递消息
实现一个拖拽 demo
hashChange 事件,可以知道 URL 的参数什么时候发生了变化。
通过状态管理 API,能够在不加载页面的情况下改变浏览器的URL。=> history.pushState(),接收三个参数:状态对象、新状态的标题和可选的相对 URL。
history.pushState({name: 'Nicholas'}, 'Nicholas page', 'nicholas.html');
执行 pushState() 方法后,新的状态信息就会被加入历史状态栈,而浏览器地址栏也会变成新的相对 URL。
但是,浏览器并不会真的向浏览器发送请求,即使状态改变之后查询 location.href 也会返回与地址栏中相同的地址。
且,pushState() 会创建新的历史状态,按下“后退”,会触发 window 对象的 popState 事件(其中的 state 属性,包含这当初以第一参数传递给 pushState 的状态对象)
得到这个状态对象后,必须把页面重置为状态对象中的数据表示的状态(浏览器不会自动做这些)。浏览器加载的第一个页面没有状态,因此单击“后退”按钮返回浏览器加载的第一个页面时,event.state 值为 null
要更新当前状态,可以调用 replaceState(),调用这个方法不会在历史状态栈中创建新状态,只会重写当前状态。
历史状态管理可以让我们不必卸载当前页面即可修改浏览器的历史状态栈。有了这种机制,用户就可以通过“后退”和“前进”按钮在页面状态间切换,而这些状态完全由 JavaScript 进行控制。
最近在调试一个非常诡异的 bug ,在 PC 和安卓下都是正常的,但是发现在 iOS 却出现问题。代码如下:
<div class="wrapper">
<iframe src="https://www.cnblogs.com/DSC1991/p/8665891.html"></iframe>
</div>
.wrapper{
width: 100%;
height: 200px;
}
.wrapper iframe{
}
发现iframe竟然不受父容器的高度限制,撑开了父容器,导致滑动UI显示异常。
在父容器应用下面CSS便显示正常:
-webkit-overflow-scrolling: touch;
overflow-y: scroll;
本机环境:Window 7
其实因为antd-mobile依赖了node-sass
和node-gyp
包,安装 node-sass 的正确姿势,正确安装后一般都没什么问题。
但是在本机还遇到一下问题,将解决方法记录下来:
解决方案:安装Python后执行如下命令(替换为自己电脑的路径):
npm config set python C:\Programs\Python27\python.exe
解决方案:下载.Net Framework后安装后重启电脑生效。(仅安装这个没用)
解决方案:参照npm install socket.io 提示缺少“VCBuild.exe”,一定要装VS C++吗?
看答案里有一个比较简单的方法是:
nodejs/node-gyp#307 (comment)
npm install --global --production windows-build-tools
看到报错觉得可能是node_modules
的问题,然后就;
rm -rf node_modules
成功啦!不容易~
MAC大法好!
作为前端UI组件库,从样式角度去看,应当满足两方面要求:一致性和可定制[1]。
其实这两点也非常好理解,一致性保证了组件库视觉上保持一致,而不是东拼西凑,而且说得高大上一点可能还有规范可循。而可定制就需要组件库暴露接口,供开发者配置形成自己风格的组件库。
但是具体一致表现在哪些方面呢?对于设计师而言,会很清楚,但是对于我们前端开发人员而言,具体指的是哪些东西呢?我们又如何把这些东西转化为代码呢?这部分具体见设计规范部分。
根据可定制的粒度大小,可以分为组件层面的可定制和整套组件库的主题定制。有组件使用经验的同学都知道,使用具体组件时我们可以传入某些参数或主题参数,组件就可以呈现不同的表现。另外,一些有名的组件库也都提供了主题定制的相关方法,如 antd-mobile 、Vant 和 Element ,尤其是 Element ,提供了多种主题定制的方法。
前面提到的一致性是由“设计规范”来保证的,其实这一块涉及到的内容非常多。可能在我们眼里就是组件库里的那套看似杂乱无序
的通用变量(设计规范 ≠ 通用变量),但是其实里面还是有一些套路的,也建议多多和设计师沟通,产生思维碰撞,你会发现一些平时写代码过程中不一样的思维。在我和设计师沟通的过程中,这点体验非常深。
那和组件库密切相关的设计规范体现在哪些方面呢?前几天饿了么前端架构师 林溪 在《Vue组件库建设实践》分享里提到有以下内容:
其实上面有一个关键词: 统一 。 我们如何保证样式统一?没错,就是刚才提到的通用变量(但我仍然不会认同“设计规范 = 通用变量”,很难表达出这种感觉),以 antd-mobile 为例,具体包含以下内容[2]:
其实理解了设计规范,我们再来反过来理解组件库里面的通用变量就会感觉其实变量之间其实也是存在某种内在体系的,在写具体组件的样式时就更会或者更能遵守这套规范,而不是胡乱定义了。
相关扩展资料:
主题定制是大多数组件库都会提供的一个核心样式相关的功能。但是具体解决方案有许多,但无非有两种解决方式:
具体可参照知名组件库做法:
不过,对于企业内部组件库而言,这一块其实要求并不强,相反对于一套组件库提供多套 配色 需求更高一点。这里指的配色和主题定制还是有区别的,因为配色要求不同“皮肤”的样式需要同时存在,而主题定制相当于就只有一套配色。
而如果采用多种配色的话,增加一套配色文件也可以达到“主题定制”的功能。
除了设计规范里的相关样式外,其实还包含但不限于如下通用/复用样式:
这里不仅仅组件库,一般项目中也会有这方面的通用样式,相信大家都明白,这里就不展开介绍了。但是这也很重要,是组件库样式基础的一部分!
下面是借鉴知名组件库后设计的一个样式代码结构,供参考,供拍砖。
style
├── core
| ├── animation.scss
| ├── color-card.scss
| ├── hairline.scss
| ├── mixins.scss
| └── normalize.scss
├── themes
| ├── skin
| | ├── _brown.scss
| | ├── _green.scss
| | ├── _pink.scss
| | ├── ……
| | └── _white.scss
| └── default.scss
├── core.scss
└── index.scss
另外,为了开发方便,组件相关样式变量都放在了组件代码目录,虽然会给后期维护(增加“配色”时需要更新每个组件样式特定代码),但是这种情况甚少。不过通用型组件库会将其写在通用变量中以方便主题定制粒度到组件层面。
还需要考虑的一些样式相关的问题:
另外,其实组件库有通用型组件库和业务型组件库区分,一般有名的组件库都属于前者,很多解决方案不一定适合企业内部的业务型组件库,因此在设计时也需要充分考虑业务需要,找到适合自己的最/较优解。
原文发表在《前端晚自修》:【第18期】组件库通用样式设计总结.md
首先,对于this的理解,我个人看到有两种说法:
常规情况下,我们一般都认为this的引用有如下几种情况:
其实,还有一些特殊情况:
刚才,群里有一位同学提出这样一个问题:
var x = 1;
var obj = {
x: 2,
y: function(){
console.log(this.x);
}
}
setTimeout(obj.y, 1000);
这个问题其实是考查的是js的异步执行
的问题,不深入的去解释的话,js高程(p203)对超时调用的函数特别强调:
超时调用的代码都是在全局作用域中执行的,因此函数中this的值在非严格模式下指向window对象,在严格模式下指向undefined
因此,就认定this指向的是window,因此结果是1,当然正确结果也是1。
这里obj.y不是调用关系,而是赋值,传递给超时函数作为形参。
最开始我却认为这和异步执行没关系,即使直接不用超时函数,obj.y();
,这个结果应该也是1(最简单的js代码,啪啪打脸),而且有理有据,obj.y仅仅只是一个函数的名称而已,而且在全局作用域下执行的,因此结果应当是1,甚至群里许多人都被我带偏了,认为我说的对。
但是其实不然,我也不知道为什么我这个解释为什么错了(哈哈哈),obj.y();
这个非常常规的对象函数调用结果是2。
首先,函数执行的this值,是指向函数据以执行的环境对象(js高程p114),但是如何确定是哪个环境对象呢?
由此,进一步我提出了下面问题,怎么解释下面4段代码:
(function(){return obj.y})()(); // 1
var c = obj.y; c(); // 1
(obj.y)(); // 2
(function(){return this.x})(); // 1
The Browser Object Model,浏览器对象模型
BOM 核心对象是 window ,表示浏览器的一个实例。
所有在全局作用域中声明的变量、函数都会变成 window 对象的属性和方法。
定义全局变量 vs 在 window 对象上定义属性
[[Configurable]]
特性值为false)如果页面中包含框架,则每个框架都有自己的 window 对象。并且保存在 frames 集合中。
需要注意的是,在使用框架的情况下,浏览器中会存在多个Global对象,在每个框架中定义的全局变量会自动成为该框架中 window 对象的属性。由于每个 window 对象都包含原生类型的构造函数,因此每个框架都有一套自己的构造函数,这些构造函数一一对应,但并不相等。=>这个问题会影响到对跨框架传递的对象使用 instanceof 操作符。
确定和修改 window 对象位置的属性和方法。(跨浏览器需要做好兼容性)
var leftPos = (typeof window.screenLeft == 'number') ? window.screenLeft : window.screenX;
var topPos = (typeof window.screenTop == 'number') ? window.screenTop : window.screenY;
跨浏览器确定窗口大小同样不简单。
window.innerWidth
window.innerHeight
window.outerWidth
window.outerHeight
浏览器窗口本身尺寸
页面视图容器尺寸
视口(viewport)大小
(window.)document.documentElement.clientWidth
(window.)document.documentElement.clientHeight
(window.)document.body.clientWidth
(window.)document.body.clientHeight
对于移动设备,window.innerWidth 和 window.innerHeight 保存着可见视口,也就是屏幕上可见页面区域的大小。(移动 IE 浏览器不支持,但可以通过 document.documentElement.clientWidth 和 document.documentElement.clientHeight 提供相同的信息。)随着页面的缩放,这些值也会相应变化。
在其他移动浏览器中,document.documentElement 度量的是布局视口,即渲染后页面的实际大小(与可见视口不同,可见视口只是整个页面中的一小部分)。移动 IE 浏览器把布局视口的信息保存在 document.body.clientWidth 和 document.body.clientHeight 中。这些值不会随着页面缩放变化。
https://quirksmode.org/mobile/viewports2.html
指向新窗口的引用 = window.open(url, name, features, replace)
JavaScript 是单线程语言,但它允许通过设置超时值和间歇时间值来调度代码在特定的时刻执行。
window.setTimeout(),接受两个参数:
原因如下:
调用 setTimeout() 之后,会返回一个数值 ID,表示超时调用。这个超时调用 ID 是计划执行代码的唯一标识符,可以用于取消超时调用。要取消尚未执行的超时调用计划,可以调用clearTimeout()
方法并将相应的超时调用 ID 作为参数传递给它。
超时调用的代码都是在全局作用域下执行的,因此函数中 this 的值在非严格模式下指向 window 对象,在严格模式下是 undefined。
间歇调用与超时调用类似,按照指定的时间间隔重复执行代码,直至间歇调用被取消或者页面被卸载。
window.setInterval(),接受参数与 setTimeout() 相同。同样,调用 setInterval() 方法会返回一个间歇调用 ID ,可用于在将来某个时刻取消间歇调用,可以调用clearInterval()
方法并将相应的间歇调用 ID 作为参数传递给它。
var num = 0;
var max = 10;
var intervalId = null;
function incrementNumber() {
num++;
// 如果执行次数达到了 max 设定的值,则取消后续尚未执行的调用
if (num == max) {
clearInterval(intervalId);
alert("Done");
}
}
intervalId = setInterval(incrementNumber, 500);
取消间歇调用的重要性要>>>高于取消超时调用,因为在不加干涉的情况下,间歇调用将会一直执行到页面卸载。
使用超时调用来模拟间歇调用是一种最佳模式。(在开发环境下,很少使用真正的间歇调用,原因是后一个间歇调用可能会在前一个间歇调用结束之前启动)
var num = 0;
var max = 10;
function incrementNumber() {
num++;
// 如果执行次数未达到 max 设定的值,则设置另一次超时调用
if (num < max) {
setTimeout(incrementNumber, 500);
} else {
alert("Done");
}
}
setTimeout(incrementNumber, 500);
通过这几个方法打开的对话框都是同步和模态的,即显示这些对话框时代码会停止执行,而关掉这些对话框代码又会恢复执行。
两个异步显示的对话框:
提供了与当前窗口中加载的文档有关的信息,还提供了一些导航功能。事实上,loaction 对象是很特别的一个对象,既是 window 对象的属性,也是 document 对象的属性=> window.location 和 document.location 引用的是同一个对象。
window.location === document.location; // true
location 对象的用处不只表现在它保存着当前文档的信息,还表现在它将 URL 解析为独立的的片段。
function getQueryStringArgs(){
// 取得查询字符串并去掉开头的问号
var qs = (location.search.length > 0 ? location.search.substring(1) : ''),
args = {}, // 保存数据的对象
items = qs.length ? qs.split('&') : [], // 取得每一项
item = null,
name = null,
value = null,
// 在 for 循环中使用
i = 0,
len = items.length;
for(i = 0; i < len; i++){
item = items[i].split('=');
name = decodeURIComponent(item[0]);
value = decodeURIComponent(item[1]);
if(name.length){
args[name] = value;
}
}
return args;
}
修改 location 对象的其他属性也可以改变当前加载的页面。每次修改 location 的属性(hash除外),页面都会以新 URL 重新加载。(都会在浏览器的历史记录中生成一条新纪录,用户单击“后退”按钮都会导航到前一个页面)
location.replace()
不会在历史记录中生成新记录。
location.reload()
重新加载当前显示的页面。如果调用 reload() 时不传递任何参数,页面就会以最有效的方式重新加载,也就是说,如果页面自上次请求以来并没有改变过,页面就会从浏览器缓存中重新加载。如果要强制从服务器重新加载,需要传递true,location.reload(true)
。
位于 reload() 之后的代码可能会也可能不会执行,取决于网络延迟或系统资源等因素,所以最好将**reload()**放在代码的最后一行。
location.reload(); // 重新加载(有可能从缓存中加载)
location.reload(true); // 重新加载(从服务器重新加载)
识别客户端浏览器的事实标准。navigator.userAgents
保存着与客户端显示器有关的信息,一般只用于站点分析
保存着用户上网的历史记录。
每个浏览器窗口、每个标签页乃至每个框架,都有自己的 hostory 对象与特定的 window 对象关联。
处于安全考虑,开发人员无法直接获取用户浏览的 URL ,但借由用户访问过的页面列表,同样可以在不知道实际 URL 的情况下实现后退和前进。
在业务过程中,经常会收到UI这样的要求:
UI:这里的banner图后台会配置好,我们给他们固定尺寸(比例)的图片,你这里要根据保证宽度自适应,而且长宽比要保持750*180的比例。
FE:好的,没问题
可是,在实现过程中,如果设置:
img{
width: 100%;
height: ?
}
高度如何设置呢,24%
?——不对呀,高度一般是参照父容器元素的height,宽度虽然自适应了,但是怎么让高度是参照宽度的比例来设置呀?
后来查资料过程中发现,padding属性如果给定百分比值时,是参照包含容器宽度的:
The size of the padding as a percentage, relative to the width of the containing block.
参见https://developer.mozilla.org/en-US/docs/Web/CSS/padding
就可以以下面方式来实现:
.banner-item{
width: 100%;
height: 0;
padding-bottom: 24%;
background: url('https://dummyimage.com/750x180/ddd/fff') no-repeat center;
}
这篇写的比较细,主要是针对同类业务一个比较细的总结,肯定有更好的实现方案,而且也有很多需要改进的地方。
瀑布式加载(也称分页加载)是移动端页面一种比较常见的列表呈现方式,之前大致总结过在实现过程中需要注意和考虑的一些点:前端实现瀑布流列表考虑点总结 。
简单说来,即初始加载第一页数据,当用户往上滑动屏幕时,出现“正在加载中”文案,并异步请求第二页数据,请求完成后隐藏“正在加载中”文案,继续滑动时如果发现所有数据全部请求完了,就显示“亲,就这么多了”,并且用户再上滑时不会再触发请求。同理,当用户滑到列表最顶部时,出现“下拉刷新”文案,拉取最新数据,覆盖或拼接到页面最顶部。
具体下来,其实里面有很多细节需要考虑:
对于细节点来说,其实可以理解为列表处于某种状态,而在不同的状态下每一块的UI展示和以及状态间的逻辑维护是比较麻烦的,在使用jQuery或者React实现时,因为对页面的控制权比较大,可以借助在页面上获取的信息能够比较直观和直接维护,但是在小程序环境下,需要完全从数据的角度去维护页面的状态,是比较复杂的。
在支付宝小程序下,用到的一些组件和方法如下:
另外,当列表中某一项的图片加载失败时,如何将其设置为默认图片:这在H5上是很好做到的,我们只要在每一个img标签上绑定onerror事件,触发时将当前img标签的src属性修改为默认图片即可。但在小程序中是不存在DOM概念或者说很难操作页面上的DOM(亲测,查询DOM特别慢),而必须通过data(类似于react中的state)来管理,但是页面列表是不固定的,当触发onerror时如何修改相应的data看起来是不可能完成的。后来换了个思路,设定一个空的图片加载异常状态数组,由其和真实的listData共同来确定每一个image组件的src属性,一旦触发onerror事件,就给数组push一个指定条目(一般是id或者code)的标记,这样就能够准确替换出错的图片了。
函数声明的一个重要特征就是函数声明提升(function declaration hoisting),执行代码之前会先读取函数声明。
函数表达式常见的语法形式:创建一个(匿名)函数并将其赋给变量
var fn = function(){
// 函数体
}
匿名函数(拉姆达函数),匿名函数的name属性是空字符串。
(function(){}).name === '';
var fn = function(){};
fn.name === 'fn';
函数可作为值赋给变量。
递归函数是在一个函数通过名字调用自身的情况下构成的。
arguments.callee —— 指向正在执行的函数的指针(严格模式会报错)
命名函数表达式。
var factorial = (function f(num){
if(num < 1){
return 1;
}else{
return num * f(num-1);
}
});
闭包是指有权访问另一个函数作用域中的变量的函数。——《js高程》
Closures are functions that have access to variables from another function’s scope.
闭包是函数和声明该函数的词法环境的组合。——《MDN》
A closure is the combination of a function and the lexical environment within which that function was declared.
JS变量作用域:使用的是静态作用域。静态作用域在编译阶段就能确定变量的引用。和程序定义的位置有关,和代码执行的顺序无关。(词法作用域,也叫静态作用域,它的作用域是指在词法分析阶段就确定了,不会改变。动态作用域是在运行时根据程序的流程信息来动态确定的,而不是在写代码时进行静态确定的。词法作用域中使用的域,是变量在代码中声明的位置所决定的。)
JS使用词法环境来管理静态作用域。什么是词法环境?=>简单说,就是描述环境的一个对象。那如何描述每个环境呢?
词法环境 vs 执行环境(js高程)?应该是一个概念。执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为。每个执行环境都有一个与之关联的变量对象。环境中定义的所有变量和函数都保存在这个对象中(虽然代码中无法访问,但在处理数据时解析器会使用到)
全局执行环境是最外围的一个执行环境。根据ECMAScript实现所在的宿主环境不同,表示执行环境的对象也不一样。=>在Web浏览器中,全局执行环境被认为是window对象,因此所有全局变量和函数都是作为window对象的属性和方法创建的。
某个执行环境中的所有代码执行完毕后,该环节被销毁,保存在其中的所有变量和函数定义也随之销毁,全局执行环境直到应用程序退出——例如关闭网页/浏览器时才会被销毁
每个函数都有自己的执行环境。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。而在函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境。ECMAScript程序中的执行流正由这个方便的机制控制着。
当代码在一个环境中执行时,会创建变量对象的一个作用域链(scope chain)。作用域链的用途,是保证对执行环境有权访问的所有变量和函数的有序访问。作用域链的前端,始终都是当前执行的代码所在环境的变量对象。如果这个环境是函数,则将其活动对象作为变量对象。活动对象在最开始时只包含一个变量,即arguments对象(这个对象在全局环境中是不存在的)。作用域链的下一个变量对象来自包含(外部)环境,层层延续到全局执行环境;全局执行环境的变量对象始终都是作用域链中的最后一个对象。
标识符解析是沿着作用域链一级一级地搜索标识符的过程。始终从作用域链的前端开始,然后逐级往后回溯,直到找到为止,如果找不到,通常会报错。
var color = "blue";
function changeColor(){
var anotherColor = "red";
function swapColors(){
var tempColor = anotherColor;
anotherColor = color;
color = tempColor;
//color, anotherColor, and tempColor are all accessible here
}
//color and anotherColor are accessible here, but not tempColor
swapColors();
}
//only color is accessible here
changeColor();
内部环境可以通过作用域链访问所有的外部环境,但外部环境不能访问内部环境中的任何变量和函数。
这些环境之间的联系是线性的、有次序的。每个环境都可以向上搜索作用域链,以查询变量和函数名。但不能向下。
函数参数也被当做变量来对待,其访问规则与执行环境中的其他变量相同。
虽然执行环境的类型总共只有两种:(全局和局部(函数)),但是有一些情况可以延长作用域链:会把指定的对象添加到作用域链前端。
没有块级作用域,即花括号封闭起来的代码块没有自己的作用域,在ECMAScript中就是没有自己的执行环境。如果有块级作用域的话,块级代码执行完毕后,会销毁其变量对象。但在js中没有块级作用域,更没有变量对象(相当于还是在“外部”作用域中)。
执行环境 === 作用域 === 词法环境
作用域链就是将作用域链接起来
JavaScript中的函数会形成闭包。闭包是由函数以及创建该函数的词法环境组合而成。这个环境包含了这个闭包创建时所能访问的所有局部变量。
创建闭包的常用方式,就是在一个函数内部创建另一个函数:
function createComparsionFunction(propertyName){
return function(object1, object2){
var value1 = object1[propertyName];
var value2 = object2[propertyName];
if(value1 < value2){
return -1;
}else if(value1 > value2){
return 1;
}else{
return 0;
}
}
}
详细解析如下:即使内部函数(一个匿名函数)被返回了,而且在其他地方调用,但仍然可以访问变量propertyName。之所以还能访问这个变量,是因为内部函数的作用域链中包含 createComparsionFunction 函数的作用域。
这样作用域链难道会分叉吗?不是说是一个栈吗?环境有栈,作用域链为什么没有?=>作用域链是执行环境的,保存在[[Scope]]中。
如何创建作用域链以及作用域链有什么作用。=>彻底理解闭包。
当某一个函数被调用时,会创建一个执行环境及相应的作用域链。然后使用arguments和其他命名参数的值来初始化函数的活动对象(与执行环境对应)。在作用域链中,外部函数的活动对象始终处于第二位,层层回溯直至全局执行环境的变量对象。
在函数执行过程中,为读取和写入变量的值,就需要在作用域链中查找变量。
function compare(v1, v2){
if(v1 < v2) return -1;
else if(v1 > v2) return 1;
else return 0;
}
var res = compare(5, 10);
以上代码先定义了compare函数,然后又在全局作用域中调用了它。
当调用compare函数时,会创建一个包含arguments、v1和v2的活动对象。
全局执行环境的变量对象(包含res和compare)在compare执行环境的作用域链中则处于第二位。如下图所示:执行环境、作用域链、变量对象(活动对象)的关系。
后台的每个执行环境都有一个变量对象。全局环境的变量对象始终存在,而像compare函数这样的局部环境的变量对象,则只有函数执行的过程中存在。
在创建compare函数时,会创建一个预先包含全局变量对象的作用域链,这个作用域链被保存在内部的**[[Scope]]属性**中。
当调用compare函数时,会为函数创建一个执行环境,然后通过复制函数的[[Scope]]属性中的对象构建起执行环境的作用域链。此后,又有一个活动对象(在此作为变量对象使用)被创建并被推入执行环境作用域链的前端。
对于这个例子中compare函数的执行环境而言,其作用域链中包含两个变量对象:本地活动对象和全局变量对象。显然,作用域链本质上是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。
无论什么时候在函数中访问一个变量时,都会从作用域链中搜索具有相应名字的变量。一般来讲,当函数执行完毕后,局部活动对象就会被销毁,内存中仅保存全局作用域(全局执行环境的变量对象)。
但是,闭包的情况有所不同:
**在另一个函数内部定义的函数会将包含函数(即外部函数)的活动对象添加到它的作用域链中。**因此,在 createComparsionFunction 函数内部定义的匿名函数的作用域链中,实际上将会包含外部函数 createComparsionFunction 的活动对象。在匿名函数从 createComparsionFunction 中被返回后,它的作用域链被初始化为包含 createComparsionFunction 函数的活动对象和全局变量对象。
实际上,外部函数的活动对象只有在其执行时才会生成,所以闭包如果要包含外部函数的作用域(即其活动对象),外部函数必定执行过。但内部函数的作用域链是在定义时就已经确定了(静态作用域)。
这样,匿名函数就可以访问在 createComparsionFunction 中定义的所有变量。更为重要的是,createComparsionFunction 函数在执行完毕后,其活动对象也不会被销毁,因为匿名函数的作用域链仍然在引用这个活动对象。换句话说,当 createComparsionFunction 函数返回后,其执行环境的作用域链会被销毁,但它的活动对象仍然会留在内存中;直至匿名函数被销毁后, createComparsionFunction 函数的活动对象才会被销毁。
// 创建函数
var compareNames = createComparisonFuncion('name');
// 调用函数
var result = compareNames({name: 'zs'},{name: 'ls'});
// 解除对匿名函数的引用(以便释放内存)
compareNames = null;
创建的比较函数被保存在变量compareNames中。而通过将compareNames设置为等于null解除该函数的引用,就等于通知垃圾回收例程将其清除。随着匿名函数的作用域链被销毁,其他作用域(除了全局作用域)也都可以安全的销毁了。
下图展示了调用 compareNames 函数的过程中产生的作用域链之间的关系:
由于闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存。主要是由于闭包除了包含自身的作用域链,还会引用包含外部包含函数的作用域,返回后如果不手动解除对其的引用,就会一直占用内存。
上面提到了标识符解析是沿着作用域链一级一级地搜索标识符的过程。具体到闭包内,保存的是整个变量对象,而不是某个特殊的变量。
一个经典的案例:
function createFunctions(){
var result = new Array();
for(var i=0;i<10;i++){
result[i] = function(){
return i;
}
}
return result; // 返回一个函数数组,每个函数的作用域链中都保存着createFunctions()函数的活动对象
}
通过创建另一个匿名函数并理解执行强制让闭包的行为符合预期:
function createFunctions(){
var result = new Array();
for(var i=0;i<10;i++){
result[i] = function(num){
return function(){
return num;
}
}(i); // 创建一个自执行匿名函数并将其赋给数组
}
return result;
}
由于函数参数是按值传递的,所有就会将变量i的当前值赋值给参数num。
脑洞时刻:如果num是引用类型呢?
function createFunctions(){
var result = new Array();
var obj = {};
for(var i=0;i<10;i++){
obj[i] = i;
result[i] = function(num){
return function(){
return num;
}
}(obj);
}
return result;
}
结果并没有达到预期,当在意料之内,因为按值传递时,传的是引用类型值的地址,在闭包里引用的直接上层变量对象虽然不同,但是里面obj变量的值却引用的是同一个地址。
其实,这里说明的一个核心问题在于:闭包,保存的是整个变量对象(的引用),而不是某个特殊的、具体的变量。
上下文是什么意思?上下文 vs 执行上下文?
理解 JavaScript 作用域
this对象是在 运行时 基于函数的执行环境绑定的:
每个函数在被调用时都会自动取得两个特殊的量:this和arguments,内部函数在搜索这两个变量时,只会搜索到其活动对象为止,因此永远不可能直接访问外部函数中的这两个变量。(因为活动对象中一定会有这两个变量的定义,即一定会找得到)
不过,把外部作用域中的this对象保存在一个闭包能够访问到的变量里,就可以让闭包访问该对象了。arguments同理,甚至于如果内部作用域和外部作用域变量同名时,都可以用这个方法。
var name = "window";
var object = {
name: "My Object",
getName: function(){
var that = this;
return function(){
return that.name;
}();
}
}
注意下面三段代码 => 语法的细微变化,都有可能意外改变this的值
object.getName(); // 普通调用
(object.getName)(); // 因为object.getName和(object.getName)的定义是相同的,所有this的值得到了维持(why?)
(object.getName = object.getName)(); // 因为执行了一条赋值语句,然后再调用赋值后的结果。因为这个赋值表达式的值是函数本身,所以this的值不能得到维持(why?)
()
这个运算符在js里到底作用是什么?有什么影响吗?
因为闭包会引用包含函数的整个活动对象。不过可以手动对部分变量置为null的方式解除其中内存占用大的变量的内存占用,而取其中有用的部分存储在内存中。
js不会告诉多次声明了同一个变量,这种情况下,后续的声明会忽略,只执行其中的变量初始化。
用作块级作用域(通常也称为私有作用域)的匿名函数的语法表示如下:
(function(){
// 这里是块级作用域
})();
以上代码定义并立即调用了一个匿名函数。将函数声明包含在一对圆括号中,表示它实际上是一个函数表达式,而紧跟其后的圆括号会立即调用这个函数。
这样定义的私有作用域,会在代码执行结束后被销毁。
通过匿名函数创建的闭包,可以减少闭包占用的内存问题,因为没有指向匿名函数的引用。只要函数执行完毕,就可以立即销毁其作用域链了。
在很多编程语言中,可以使用public、private、protected等修饰符来设置成员和方法的访问权限,但在js中,严格来讲没有私有成员的概念,所有对象属性都是公开的。不过,也没有类的概念——构造函数。
不过类似的有私有变量的概念,任何在函数中定义的变量,都可以认为是私有变量,因为不能在函数外部直接访问这些变量。私有变量包括函数的参数、局部变量和在函数内部定义的其他函数。
不过,我们可以利用闭包创建用于访问私有变量的共有方法。有权访问私有变量和私有函数的共有方法称为特权方法。有如下两种方式创建特权方法:
1、在构造函数内部定义
// 模拟private和public
function MyObject(){
// 私有变量和私有函数
var privateVariable = 10;
function privateFunction(){
return false;
}
// 特权方法
this.publicMethod = function(){
privateVariable++;
return privateFunction();
}
}
特权方法作为闭包有权访问在构造函数中定义的所有变量和函数。创建MyObject实例后,除了使用publicMethod()这一个途径外,没有任何办法可以直接访问privateVariable和privateFunction。
另外,需要注意的是:函数的内部变量只有当其被调用时才会存在,调用结束一般会立即销毁。但其实本质上每次调用相当于重新创建了一个内部对象,即使上一次函数环境和变量对象未被销毁。因此,上面的函数作为构造函数被调用时,每次调用就相当于创建了一个新的函数执行环境和变量对象,因为返回的闭包被新创建的对象实例所引用导致在调用结束后其变量对象无法被销毁(保存在了内存中,或者叫维护了一个私有作用域,只有这个闭包/公开方法可以访问)。=>私有变量在每一个实例中都不相同,因为构造函数的执行环境会重新创建,更何况是特权方法的执行环境。
使用私有和特权成员,可以隐藏那些不应该被直接修改的数据。
脑洞时间:访问器属性属于创建了一个私有属性吗?(why?)
但使用构造函数模式定义特权方法有一个缺点就是:每次重新调用构造函数都会重新创建构造函数内部定义的方法。
2、通过私有作用域定义
=>静态私有变量。
(function(){
// 私有变量和私有函数
var privateVariable = 10;
function privateFunction(){
return false;
}
MyObject = function(){}; // 没加var,MyObject是全局变量
// 公有/特权方法
MyObject.prototype.publicMethod = function(){
privateVariable++;
console.log(privateVariable);
return privateFunction();
}
})();
这种模式创建了一个私有作用域,在其中,首先定义了私有变量和私有函数,然后定义了构造函数及其公有方法(且公有方法是在原型上定义的)。
需要注意的是,上面直接使用函数表达式定义构造函数,而不是函数声明(会定义在局部环境上),而且没有使用var
,因此定义的构造函数是全局环境上的(初始化未经声明的变量,总是会创建一个全局变量)。
其实,上面代码等同于:
var MyObject = function(){}; // 没加var,MyObject是全局变量
// 或者
function MyObject(){};
(function(){
// 私有变量和私有函数
var privateVariable = 10;
function privateFunction(){
return false;
}
// 公有/特权方法
MyObject.prototype.publicMethod = function(){
privateVariable++;
console.log(privateVariable);
return privateFunction();
}
})();
这种模式特殊之处,在于私有变量和函数是由实例共享的。且因为特权方法是在原型中定义,因此所有实例都使用同一函数(而特权方法因为在私有作用域内定义的,形成闭包,能够保存着对包含作用域的引用)。
两种方式,完全可以结合使用,视具体需求而定。
另外,对于私有变量,因为多了中间一层作用域,导致查找变量时多查找了一个层次,会在一定程度上影响查找速度。=>闭包和私有变量的一个显明的不足之处。
使用闭包可以用于为自定义类型创建私有变量和特权方法,但模块模式则是为单例创建私有变量和特权方法。所谓单例,指的是只有一个实例的对象,而在js中,一般是以对象字面量的方式来创建单例对象的。
???一个实例的对象???对象本身不就是一个实例吗?
var singleton = {
name: value,
method: function(){}
}
模块模式通过为单例添加私有变量和特权方法能够使其得到增强。
var singleton = function(){
// 私有变量和私有函数
var privateVariable = 10;
function privateFunction(){
return false;
}
// 特权/公有方法和属性
return {
publicProperty: true,
publicMethod: function(){
privateVariable++;
return privateFunction();
}
}
}(); // 立即执行匿名函数,返回一个对象
从本质上讲,这个返回的对象字面量定义的是单例的公共接口。这种模式在需要对单例进行某种初始化,同时又需要维护其私有变量时是非常有用的。简言之,如果必须创建一个对象并以某些数据对其进行初始化,同时还要公开一些能够访问这些私有数据的方法,那么就可以使用模块模式。
这种模式创建的单例是Object类型。(因为字面量创建的对象返回)
适合那些单例必须是某种类型的实例,同时还必须添加某些属性和(或)方法来对其增强的情况。
var singleton = function(){
// 私有变量和私有函数
var privateVariable = 10;
function privateFunction(){
return false;
}
// 创建对象
var object = new CustomType();
// 添加特权/公有属性和方法
object.publicProperty = true;
object.publicMethod = function(){
privateVariable++;
return privateFunction();
}
// 返回这个对象
return object;
}();
在js中,函数表达式非常有用,使用时无需对函数命名,从而实现动态编程。匿名函数,也成为拉姆达函数。
闭包的作用:
描述了属性(property)的各种特征。
var book = {
_year: 2004,
edition: 1
};
Object.defineProperty(book, "year", {
get: function () {
return this._year;
},
set: function (newValue) {
if (newValue > 2004) {
this._year = newValue;
this.edition += newValue - 2004;
}
}
});
book.year = 2005;
alert(book.edition); //2
画布
<canvas id="drawing" width=" 200" height="200">A drawing of something.</canvas>
要在这块画布上绘图,需要取得绘图上下文(对象) => getContext()
var drawing = document.getElementById("drawing");
// 确定浏览器支持 <canvas> 元素
if (drawing.getContext){
var context = drawing.getContext("2d"); // 获得绘图上下文对象
// ...
}
使用 toDataURL() 方法,可以导出 元素上绘制的对象。
// 取得图像的数据 URI
var imgURI = drawing.toDataURL('image/png');
// 显示图像
var image = document.createElement('img');
image.src = imgURI;
document.body.appendChild(image);
需要注意的是:
使用 2D 绘图上下文提供的方法,可以绘制简单的 2D 图像,如矩形、弧线、路径等。
2D 上下文的坐标开始于 元素的左上角,原点坐标是(0, 0)。基本绘图操作:填充和描边,其结果取决于:
属性值可以是字符串、渐变对象或模式对象,默认值“#000000”,设置后所有涉及描边和填充的操作都将使用这两个样式,直至重新设置这两个值。
矩形是唯一一种可以直接在 2D 上下文中绘制的形状。这三个方法都能接收 4 个参数:矩形的 x 坐标、y 坐标、宽度、高度(单位:像素)
// 绘制红色矩形
context.fillStyle = "#ff0000"; context.fillRect(10, 10, 50, 50);
// 绘制半透明的蓝色矩形
context.fillStyle = "rgba(0,0,255,0.5)"; context.fillRect(30, 30, 50, 50);
// 绘制红色描边矩形
context.strokeStyle = "#ff0000";
context.strokeRect(10, 10, 50, 50);
// 绘制半透明的蓝色描边矩形
context.strokeStyle = "rgba(0,0,255,0.5)";
context.strokeRect(30, 30, 50, 50);
context.clearRect(40, 40, 10, 10);
2D 上下文支持很多在画布上绘制路径的方法,通过路径可以创造出复杂的形状和线条。
绘制路径,首先必须调用 beginPath() 方法,表示要开始绘制新路径,然后再通过调用下列方法来实际绘制路径:
绘制路径后,接下来有几种可能的选择:
在 2D 绘图上下文中,路径是一种主要的绘图方式,因为路径能为要绘制的图形提供更多的控制。由于路径的使用很频繁,可以使用 isPointInPath(x, y) 方法在路径关闭之前确定画布上的某一点是否位于路径上。
if (context.isPointInPath(100, 100)){
alert("Point (100, 100) is in the path.");
}
绘制时钟表盘(不带数字) demo:
var drawing = document.getElementById(“drawing”);
//make sure <canvas> is completely supported if (drawing.getContext){
var context = drawing.getContext(“2d”);
//start the path
context.beginPath();
//draw outer circle
context.arc(100, 100, 99, 0, 2 * Math.PI, false);
//draw inner circle
context.moveTo(194, 100);
context.arc(100, 100, 94, 0, 2 * Math.PI, false);
//draw minute hand
context.moveTo(100, 100);
context.lineTo(100, 15);
//draw hour hand
context.moveTo(100, 100);
context.lineTo(35, 100);
//stroke the path
context.stroke();
}
接受 4 个参数:要绘制的文本字符串、x 坐标、y 坐标、最大像素宽度(可选)。另外,这两个方法以下列 3 个属性为基础:
measureText() 方法
drawImage(),注意跨域限制。
CanvasRenderingContext2D.drawImage()
模式就是重复的图像,可以用来填充或描边图形。
笔者注:渐变和模式都可用来填充或描边图形
getImageData() 取得原始图像数据,返回 ImageData 的实例
小程序自发布始,外界就一直声称前端开发工程师将大有可为,“替H5、换Android、灭iOS”,口号一个喊得比一个响。作为初入前端的新人,当时早已热血沸腾、心中暗喜,自觉前方一片坦途,正欲摩拳擦掌,干出一番大事业来。
但已一年有余,各类小程序相继发布,心中也曾冒出各种想法和idea,却始终停留在“臆想”阶段,实际却未曾踏出半步(我眼中的「小程序」)。等过了一段时间,便自己找了理由否定掉了。导致到如今,对于小程序的开发文档都还未研究透彻,对于小程序的认识也是人云亦云,没有深刻的认识。
简单列举一下曾经萌发而未实现过的想法:
- 活动发布
- 共享同城快递
- 流程
- 技术图书漂流
- 小说明
- 基于LBS的买卖社群
- ...
感觉程序员,包括我自己(无冒犯之意),都有一颗“改变世界”的心,自觉无所不能,天生骄傲。见身边不平之事,几句代码便能扭转乾坤,如若不能就自我安慰,投入到无穷的debug之中,对于最初的梦想只能一直搁浅。当然,这只是我对自己长期以来拖延的一种解读,大牛不在之列。
细思原由,眼高手低,基础松散。万丈高楼平地起,总是不能一步登天的。
近期,有两件事算是坚持做了一段时间,感觉不错。一件是17年初参与过21天的“前端早读汇”活动(参与“前端早读汇”有感),一件事前两周坚持了21天的“减肥训练营”。网上流传着一个理论,说21天坚持做同一件事,就能养成一个习惯,可是通过两次实践,感觉并没有真正持续的坐下来,不过如果再多来几个21天不同的实践,真的可能会改变我的心性吧。
自从第一次“前端早读汇”活动,还记得当时就有一个想法在我脑海中一闪而过,就是上面提到的“技术图书漂流”。当时还和@情封沟通过,也和前端早读课的读者群里反馈,大家都觉得不错,从而还成立了杭州的前端早读课分舵。但是因为整件事情一直停留在最初的想法阶段,导致后期根本没办法执行,再加上一些不可控因素,便不了了之。
但历经半年多,这个想法还停留在我脑子里,而且再加上还想好好脚踏实地的去坚持做一件类似于“21天”的事情,这个想法最近一直在我脑海里不停浮现。
具体说来,是因为作为程序员,其实我们都会去买一些纸质技术书,但是因为业务忙加上一有问题就会寻求网络搜索解决方案(当然最新的技术或者第一手文档还是网络资源比较及时),纸质书一直都是我们垫电脑的最佳工具。但是,技术书一般定价也比较高,实属资源浪费。而且,看书这种方式更多也是读书时期多采用的学习方法,一般都会在理论上反复斟酌,再加上老师的讲解和同学的讨论,对于知识的理解也相对比较深入。反观如今,网络发达、资源泛滥的时代,对于很多事物,经常是不求甚解,浮躁,肤浅。
所以,就想通过这么一个形式,来表达一种“格物致知”的态度,对待生活如此,对待技术更是如此。这便是“不二书汇”产生的来由。
随着小程序的流行,大家都能大概理解“小程序”和传统“程序”之间的差别,其“无需下载”、“近原生体验”等核心能力深受大家追捧。但在使用过程中,其中一些细微的{差别}却不得不需要我们重新去理解和思考小程序的定位。
本文着重讲解小程序提供的“跳转H5”能力。也是最容易被大家误解和滥用的一个功能。
微信小程序和支付宝小程序都提供了“跳转H5”的能力,但是和传统意义上理解的{跳转}并不一致。在传统意义上的跳转,跳转到App或者是跳转H5,跳转过去了可能就和{自己手动打开一个App或者H5}的表现差不多。
但是小程序并非如此,在小程序里跳转H5,本质上是由小程序自身提供的一个叫做<web-view />
组件实现的:
支付宝小程序的<web-view />
:https://docs.alipay.com/mini/component/web-view
微信小程序的<web-view />
:https://developers.weixin.qq.com/miniprogram/dev/component/web-view.html
我们都知道,对于H5,我们一般都是通过浏览器{在微信和支付宝里打开也是一种特殊的“浏览器”}打开的,虽然有些差异,但是之间都还是传统的浏览器打开。
但是,对于小程序来说,提供的<web-view />
组件只是小程序的一部分。虽然也类似“浏览器”,但是并不提供大部分浏览器的一些功能,而且更受到小程序的一些限制。
打开的H5,仍然还是在小程序的内部,从打开H5的那一刻起,包括在H5内部的点击跳转,都仅仅被认为是小程序的一个页面而已。点击左上角返回,是返回到小程序的上一级页面。
因为实现原理不同,所以在小程序内“跳转H5”还是存在很多限制的,以“支付宝小程序”为例,特此总结如下几条供参考:
综上,其实小程序对于H5的限制还是非常大的,而且小程序和H5之前通信并不友好。
从技术角度上来看,小程序提供的跳转H5功能,目前来看仅适用于一些展示型的、静态的H5页面,否则,在使用过程中可能会出现很多意想不到的bug。
能力检测(又称特性检测):目标不是识别特定的浏览器,而是识别浏览器的能力。
// 能力检测基本模式
if(object.propertyInQuestion){
// 使用 object.propertyInQuestion
}
更严谨的能力检测:typeof
function isSortable(object){
return typeof object.sort == "function";
}
怪癖检测:识别浏览器的特殊行为
用户代理检测:检测用户代理字符串来确定实际使用的浏览器。在每一次 HTTP 请求过程中,用户代理字符串是作为响应首部发送的,而且该字符串可以通过 JavaScript 的 navigator.userAgent
属性访问。
用户代理字符串的历史:HTTP 规范(包括1.0和1.1版)明确规定,浏览器应该发送简短的用户代理字符串,指明浏览器的名称和版本号。
今天基于iconfont封装Icon组件的时候,当引入字体样式后,webpack一直报错,如下:
@font-face {font-family: "ytui-icon";
src: url('./iconfont.eot?t=1531298415150'); /* IE9*/
src: url('./iconfont.eot?t=1531298415150#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('data:application/x-font-woff;charset=utf-8;base64,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') format('woff'),
url('./iconfont.ttf?t=1531298415150') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/
url('./iconfont.svg?t=1531298415150#ytui-icon') format('svg'); /* iOS 4.1- */
}
ERROR in ./~/css-loader!./~/postcss-loader!./~/sass-loader!./src/Icon/index.scss
Module not found: Error: Can't resolve './iconfont.eot?t=1531298415150' in 'D:\Dev\yt\Nodejs\npm\YTUI\src\Icon'
@ ./~/css-loader!./~/postcss-loader!./~/sass-loader!./src/Icon/index.scss 6:86-127 6:162-203
@ ./src/Icon/index.scss
@ ./src/Icon/index.js
@ ./src/index.js
后来查阅资料时发现webpack不能识别css内的相对路径(js可以),将引用路径改成这样就可以:
@font-face {font-family: "ytui-icon";
src: url('~/iconfont.eot?t=1531298415150'); /* IE9*/
src: url('~/iconfont.eot?t=1531298415150#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('data:application/x-font-woff;charset=utf-8;base64,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') format('woff'),
url('~/iconfont.ttf?t=1531298415150') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/
url('~/iconfont.svg?t=1531298415150#ytui-icon') format('svg'); /* iOS 4.1- */
}
就打包成功了,webpack: Compiled successfully.
参考资料:
在业务工作中,如果你需要封装一个组件,你肯定会遇到这样的场景:组件的默认的配置对象defaultOption,需要与暴露出去的api进行合并,保证用户自定义的值能覆盖默认配置,但未定义的值取默认配置。其实,这本质上就是需要把defaultOption对象与外部传入的配置对象进行合并。我们经常会看到两种写法:Object.assign
和$.extend
,那这两者有没有区别呢?
首先呈现官方文档:Object.assign()、jQuery.extend()。
Object.assign()
Object.assign(target, ...sources)
,Returns: Object
$.extend()
jQuery.extend( target [, object1 ] [, objectN ] )
, Returns: Object
jQuery.extend( [deep ], target, object1 [, objectN ] )
, If true, the merge becomes recursive(递归) (aka. deep copy,深拷贝).
The Object.assign() method only copies enumerable and own properties from a source object to a target object.
$.extends(): Merge the contents of two or more objects together into the first object.
两者区别其实不大:
true
可实现deep merge,而Object.assign只会copies that reference value
。var opts = Object.assign( {}, defaults, options );
// 或者
var opts = $.extend( {}, defaults, options );
if (typeof Object.assign != 'function') {
// Must be writable: true, enumerable: false, configurable: true
Object.defineProperty(Object, "assign", {
value: function assign(target, varArgs) { // .length of function is 2
'use strict';
if (target == null) { // TypeError if undefined or null
throw new TypeError('Cannot convert undefined or null to object');
}
var to = Object(target);
for (var index = 1; index < arguments.length; index++) {
var nextSource = arguments[index];
if (nextSource != null) { // Skip over if undefined or null
for (var nextKey in nextSource) {
// Avoid bugs when hasOwnProperty is shadowed
if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
to[nextKey] = nextSource[nextKey];
}
}
}
}
return to;
},
writable: true,
configurable: true
});
}
deep merge
function isObject(item) {
return (item && typeof item === 'object' && !Array.isArray(item));
}
function mergeDeep(target, ...sources) {
if (!sources.length) return target;
const source = sources.shift();
if (isObject(target) && isObject(source)) {
for (const key in source) {
if (isObject(source[key])) {
if (!target[key]) Object.assign(target, { [key]: {} });
mergeDeep(target[key], source[key]);
} else {
Object.assign(target, { [key]: source[key] });
}
}
}
return mergeDeep(target, ...sources);
}
(本篇完)
在学习js原型继承时,有如下代码:
function SuperType(){
this.property = true;
}
SuperType.prototype.getProperty = function(){
return this.property;
}
function SubType(){
this.subProperty = false;
}
SubType.prototype = new SuperType();
SubType.prototype.getSubProperty = function(){
return this.subProperty;
}
var instance = new SubType();
然后,再执行instance instanceof SubType
时,得到的结果的true。
最开始了解instanceof
操作符时,会认为这个操作符的含义是是否是XX的实例
的含义。
但在理解js的原型继承时,构造函数(引用类型)的原型对象被重写为另外一个引用类型(构造函数)的实例,这里新的原型对象并没有包含指向构造函数的constructor属性
,所以我就会误认为“新创建的实例并不是原引用类型(构造函数)的实例”,认为上面应该返回false。
这样的理解其实是片面的,虽然在代码原理层面,原型链和原构造函数已经没有了任何关系(因为重写的原型对象没有构造函数属性了)。但是,继承的含义却保证了“实例是原引用类型(构造函数)的实例”。
再从instanceof操作符
的原理上看,深入可参见这篇文章JavaScript instanceof 运算符深入剖析,可以了解到其本质含义是:“沿着原型链来查找是否包含指定构造函数(引用类型)的原型对象”,而构造函数到原型对象的单向引用还是保持的。
综上,instance instanceof SubType
返回true就不难理解了。
iPhone X 适配理论上需要在 UI 预先适配的基础上再做前端页面的适配,以保证 iPhone X 下的最佳体验。但因为项目复杂,涉及改动点较多,可暂时采用此方案。
局限性主要体现在fixed
元素无法使用通用的样式适配。且涉及到是否吸底
,吸底元素是否可以简单移动位置还是需要增加元素高度又能保证中间内容看起来“垂直居中”。
针对前面4个适配点,给出适配方案如下:
1.修改 meta 标签中,加入viewport-fit=cover
,使在 iPhone X 下页面全屏覆盖,并且后面相应的 hack 属性能生效。
<meta name="viewport" content="...,viewport-fit=cover">
2.修改 body 内边距。在通用 CSS 中添加下面代码。其中constant
和env
是 iOS 10+ 上才能识别的 CSS 方法,safe-area-inset-bottom
是底部的安全距离,一般是34px
(防止 iPhone X Plus 之类的,还是用个统一的官方变量比较好)(注意:样式需要放到最后,保证不会被覆盖)
@supports (padding-bottom: constant(safe-area-inset-bottom)) {
body {
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
}
3.针对fixed
定位的元素,因为其是相对于浏览器窗口
进行定位的,所以 body 的内容区域无法限制其位置,需要调整其位置。采用以下通用 js 解决。(注意:需放置页面最后,保证页面元素已渲染到页面,用户能感知到页面中 fixed 定位元素会在显示后往上移一段,至于是否会遮挡到中间滚动块还待观察,因此建议在项目允许的情况下,对fixed
元素最好用 CSS 做个性化适配)
// 底部安全距离
const IPHONEX_BOTTOM_HEIGHT = 34;
// 可添加到通用UA判断中
function isIphoneX(){
return /iphone/gi.test(window.navigator.userAgent) && (screen.height == 812 && screen.width == 375)
}
if(isIphoneX()){
$('*').filter(function(index, item){
return $(item).css('position') === 'fixed';
}).map(function(index, item){
var itemTop = $(item).position().top;
if(itemTop > IPHONEX_BOTTOM_HEIGHT){
$(item).css('top', itemTop - IPHONEX_BOTTOM_HEIGHT);
}
});
}
随着移动设备性能的增强,“瀑布流H5”是移动端当前比较流行的一种产品形态,但是在实现过程中,特总结需要注意几点内容:
page_size
+page_num
id
+page_num
近期切图时,遇到一个问题,在使用flex布局时,容器内子元素获得的宽度,当其设置word-space:no-wrap
会失效。
在网上查了查资料,说是会打乱flex布局
:具体原因是设置了word-space:no-wrap
后元素就没办法伸缩了。
解决办法是:
将子元素的width
设置为0
设置0后就有了固定尺寸就可以收缩了
关于display:flex碰上white-space nowrap的问题
JavaScript (web)主要包含以下内容
ECMAScript - 核心
宿主环境:不仅提供基本的ECMAScript实现,同时也会提供该语言的扩展(如DOM、BOM等),以便语言与环境之间的对接交互。
DOM - 文档对象模型
文档对象模型(DOM,Document Object Model)是针对XML但经过扩展用于HTML的应用程序编程接口(API,Application Programming Interface)。
DOM级别与W3C标准。
BOM标准?
<script>
元素**, 使用<script>
元素的方式有两种:<script>
指定type属性
为text/javascript
,然后把js代码直接放入元素内即可src属性
为外部js文件链接执行顺序:
defer属性
或async属性
,否则浏览器会按照**<script>
元素在页面中出现的顺序**对它们依次解析<script>
标签的位置:一般应当放在<body>
内容的后面(用户体验)
尽量使用外部文件引入的方式,而不要嵌入代码的原因:
对象遍历在实际业务上应用倒不多(个人感觉),但是其实对于深入理解和操作对象还是非常重要的。
The Document Object Model,文档对象模型。描绘了一个层次化的节点树。
DOM 可以将任何 HTML 或 XML 文档描绘成一个由多层节点构成的结构。
节点分为几种不同的类型,表示文档中不同的信息/标记。每个节点都拥有各自的特点、数据和方法。节点与其他节点存在某种关系,构成了层次。而所有页面标记则表现为一个以特定节点为根节点的树形结构。
基类型
每个节点都有一个 nodeType 属性,用于表明节点的类型。
childNodes 属性,其中保存着一个 NodeList 对象:
parentNode 属性,指向文档树中的父节点
previousSibling 和 nextSibling 属性,可以访问同一列表的其他节点,没有值为 null
firstChild 和 lastChild 属性。父节点的属性,分别指向其 childNodes 列表中的第一个和最后一个节点,即:
someNode.firstNode === someNode.childNodes[0]; // true
someNode.lastNode === someNode.childNodes[someNode.childNodes.length - 1]; // true
hasChildNodes()
ownerDocument 属性:指向表示整个文档的文档节点
因为关系节点都是只读的,所以 DOM 提供了一些操作节点的方法。
appendChild(),用于向 childNodes 列表的末尾添加一个节点,返回新增的节点。如果已经是文档的一部分了,结果就是将该节点从原来的位置转移到新位置。
insertBefore(),将节点放在 childNodes 列表中某个特定的位置上,接受两个参数:要插入的节点和作为参照的节点,插入节点会变成参照节点的前一个同胞节点(previousSibling),同时被返回。如果参照节点是 null,则与 appendChild() 执行相同的操作。
replaceChild(),接受两个参数:要插入的节点和要替换的节点。插入节点的所有关系指针都会从被替换节点复制过来,技术上,被替换节点仍然还在文档中,但在文档中已经没有了位置。
removeChild(),要移除的节点,返回被移除的节点。同样还在文档中,但没有位置了
注意:上面四个方法操作的都是某个节点的子节点,使用必须先取得父节点。如果在不支持子节点的节点上调用了这些方法,将会导致错误发生。
cloneNode(),用于创建调用这个方法的节点的一个完全相同的副本,接受一个布尔值参数,表示是否执行深复制(是否复制子节点树)。复制后返回的节点副本属于文档所有,但并没有为它指定父节点。注意:cloneNode() 不会复制添加到 DOM 节点中的 JavaScript 属性
normalize(),唯一作用处理文档树中的文本节点。
Document 类型表示文档。
在浏览器中,document 对象是 HTMLDocument (继承自 Document) 类型的一个实例,表示整个 HTML 页面。
documetElement 属性:始终指向 HTML 页面中的 <html>
元素。
document.documentElement === document.childNodes[0]; // true
body 属性:直接指向 HTML 页面中的 <body>
元素。
文档信息:
查找元素(返回结果都是“动态”集合):
// 取得文档中的所有元素
document.getElementsByTagName("*");
特殊集合:
文档写入:输入流写入到网页中。
<!-- 在页面呈现过程中直接向其中输出内容 -->
<script type="text/javascript">
// 利用write/writeln动态包含外部资源
document.write("<script type=\"text/javascript\" src=\"file.js\">" + "<\/script>");
</script>
<!-- 页面加载完全之后执行write会重写整个页面内容 -->
所有 HTML 元素都由 HTMLElement 类型表示(或子类型),HTMLElement 类型直接继承于 Element 类型,并添加了一些属性:id、title、lang、dir、className等(可通过对象属性访问和设置特性值)。
nodeName属性、tagName属性(注意大小写)
操作特性:
data-
前缀以便验证对象属性名 vs ...Attribute()
element.attributes 属性,包含一个 NamedNodeMap,也是一个“动态”集合。
创建元素:document.createElement()
元素的子节点:element.getElementsByTagName(),搜索起点是当前元素
nodeValue 和 data 属性
length 属性
在向 DOM 文档中插入文本之前,先对其进行 HTML 编码的一种有效方式。(修改文本节点时,会经过 HTML 编码)
创建文本节点:document.createTextNode(),作为参数的文本也将按照 HTML/XML 格式进行编码
规范化文本节点:element.normalize()(将相邻文本节点合并,在父元素上调用)
分隔文本节点:splitText()
在所有节点类型中,只有 DocumentFragment 在文档中没有对应的标记。“轻量级”的文档,可以包含和控制节点,但不会像完整的文档那样占用额外的资源。
document.createDocumentFragment()
文档片段继承了 Node 的所有方法,通常用于执行那些针对文档的 DOM 操作。如果将文档中的节点添加到文档片段中,就会从文档树中移除该节点,也不会从浏览器中再看到该节点,添加到文档片段中的新节点同样也不属于文档树。可以通过 appendChild/insertBefore 将文档片段中内容添加到文档中。在将文档片段作为参数传递给这两个方法时,实际只会将文档片段的所有子节点添加到相应位置,文档片段本身永远不会成为文档树的一部分。(可以避免多次直接操作DOM)
var fragment = document.createDocumentFragment();
var ul = document.getElementById("myList");
var li = null;
for (var i=0; i < 3; i++){
li = document.createElement("li");
li.appendChild(document.createTextNode("Item " + (i+1)));
fragment.appendChild(li);
}
ul.appendChild(fragment);
元素的特性在 DOM 中以 Attr 类型表示。
不建议直接访问特性节点,实际上使用getAttribute()、element.setAttribute()、element.removeAttribute()方法更加方便。
页面加载时不存在,但在将来的某一时刻通过修改 DOM 动态添加的脚本。创建动态脚本的两种方式:插入外部文件和直接插入 JavaScript 代码。
动态加载的外部 JavaScript 文件能够立即运行。
<script type="text/javascript" src="client.js"></script>
创建这个节点的 DOM 代码:
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = 'client.js';
document.body.appendChild(script); // 在执行最后一行代码把<script>元素添加到页面中之前,是不会下载外部文件的。
但是浏览器目前没有标准方式知道脚本加载完成。
动态直接插入 js 代码:
var script = document.createElement('script');
script.type = "text/javascript";
script.appendChild(document.createTextNode("function sayHi(){alert('hi');}"));
// script.text = "function sayHi(){alert('hi');}"; // IE 中
document.body.appendChild(script);
var link = document.createElement("link");
link.rel = "stylesheet";
link.type = "text/css";
link.href = "style.css";
var head = document.getElementsByTagName("head")[0];
head.appendChild(link);
加载外部样式的过程是异步的。
NodeList、NamedNodeMap、HTMLCollection,都是“动态”集合
理解 DOM 的关键,就是理解 DOM 对性能的影响。DOM 操作往往是 JavaScript 程序中开销最大的部分,而因访问 NodeList 导致的问题最多,因为 NodeList 对象都是“动态”的,每次访问都会运行一次查询。=>尽量减少 DOM 操作。
HEX模式和RGB模式
color0: #5FD9CD__RGB(95,217,205)
color1: #EAF786__RGB(234,247,134)
color2: #FFB5A1__RGB(255,181,161)
color3: #B8FFB8__RGB(184,255,184)
color4: #B8F4FF__RGB(184,244,255)
HEX模式和RGB模式
color0: #B8FFEA__RGB(184,255,234)
color1: #FFEBEF__RGB(255,235,239)
color2: #C9FFFC__RGB(201,255,252)
color3: #FFFCE6__RGB(255,252,230)
color4: #F6EBFC__RGB(246,235,252)
Source:http://www.peise.net/
white-space: nowrap;
(否则会换行)JavaScript 与 HTML 之间的交互是通过事件实现的。事件就是文档或浏览器窗口中发生的一些特定的交互瞬间。可以使用 侦听器(或处理事件) 来预订事件,以便事件发生时执行相应的代码。——“观察者模式”
事件流:描述的是从页面中接收事件的顺序。
DOM事件流(规范):“DOM2级事件”规定的事件流包括三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段。首先发生的是事件捕获,为截获事件提供了机会,然后是具体的目标接收到事件,最后一个阶段是冒泡阶段,可以在这个阶段对事件作出响应。
事件就是用户或浏览器自身执行某种动作,而响应某个事件的函数就叫做事件处理程序(或事件侦听器),为事件指定处理程序的方式有好几种:
可以包含要执行的具体动作,也可以调用在页面其他地方定义的脚本。事件处理程序中的代码在执行时,有权访问全局作用域中的任何代码。通过局部变量 event,可以直接访问事件对象,无需定义,也不用从函数的参数列表中读取。函数内部,this 值等于事件的目标元素
缺点:
- 存在时差问题,HTML 元素一出现就可能会触发事件,但事件处理程序有可能尚不具备执行条件,需要做好容错处理(try...catch)
- HTML 与 JavaScript 代码紧密耦合
- http://www.jibbering.com/faq/names/event_handler.html
每个元素(包括 window 和 document)都有自己的事件处理程序属性,这些属性通常全部小写,将这些属性的值设置为一个函数,就可以指定事件处理程序。使用 DOM 0 级方法指定的事件处理程序被认为是元素的方法,因此,这时候的事件处理程序是在元素的作用域中运行,换句话说,程序中的 this 引用当前元素。
var btn = document.getElementById("myBtn");
btn.onclick = function(){
alert(this.id); //"myBtn"
};
以这种方式添加的事件处理程序会在事件流的冒泡阶段被处理。
删除通过 DOM 0 级方法指定的事件处理程序的方法,将事件处理程序属性的值设为 null 即可:
btn.onclick = null;
(如果使用 HTML 指定事件处理程序,那么在 js 中元素事件处理程序属性的值就是一个包含着在同名 HTML 属性中指定的代码的函数。将属性值置为 null,也可以删除使用 HTML 指定的事件处理程序)
定义了两个方法,用于处理指定和删除事件处理程序:addEventListener()
和removeEventListener()
,所有 DOM 节点中都包含这两个方法,并且都接受 3 个参数:要处理的事件名、作为事件处理程序的函数和一个布尔值。布尔值为true:表示在捕获阶段调用事件处理程序;布尔值为false:表示在冒泡阶段调用事件处理程序。
事件处理程序也在在其依附的元素的作用域中运行的。使用 DOM 2 级方法可以添加多个事件处理程序(按照添加顺序触发)。
通过 addEventListener() 添加的事件处理程序只能使用 removeEventListener() 来移除,移除时传入的参数与添加事件处理程序时使用的参数相同=>匿名函数无法移除
大多数情况下,都是将事件处理程序添加到事件流的冒泡阶段,这样可以最大限度地兼容各种浏览器(why???,现代浏览器应当都能处理了吧)。最好只在需要 在事件到达目标之前截获它的时候 将事件处理程序添加到捕获阶段,如果不是特别需要,不建议在事件捕获阶段注册事件处理程序。(why???)
attachEvent()
和detachEvent()
https://github.com/jquery/jquery/blob/899c56f6ada26821e8af12d9f35fa039100e838e/src/event.js#L196
在触发 DOM 上的某个事件时,会产生一个事件对象 event,包含着所有与事件有关的信息。
DOM 中的事件对象。兼容 DOM 的浏览器会将一个 event 对象传入到事件处理程序中,无论指定事件处理程序时使用什么方法(DOM 0 级/DOM 2 级),都会传入 event 对象。在通过 HTML 特性指定事件处理程序时,变量 event 中保存着 event 对象。
var btn = document.getElementById("myBtn");
btn.onclick = function(event){
alert(event.type); //"click"
};
btn.addEventListener("click", function(event){
alert(event.type); //"click"
}, false);
<input type="button" value="Click Me" onclick="alert(event.type)"/>
event 对象包含与创建它的特定事件有关的属性和方法,触发的事件类型不一样,可用的属性和方法也不一样。
bubbles
:Boolean,只读,表明事件是否冒泡cancelable
:Boolean,只读,表明是否可以取消事件的默认行为currentTarget
:Element,只读,其事件处理程序当前正在处理事件的那个元素。defaultPrevented
:Boolean,只读,为 true 表示已经调用了 preventDefault()。(DOM 3 级事件中新增)detail
:Integer,只读,与事件相关的细节信息eventPhase
:Integer,只读,调用事件处理程序的阶段:1-捕获阶段,2-“处于目标”阶段,3-冒泡阶段preventDefault()
:Function,只读,取消事件的默认行为,前提是cancelable 属性为 true,才可以使用这个方法stopImmediatePropagation()
:Function,只读,取消事件的进一步捕获或冒泡,同时阻止其他任何事件处理程序被调用。(DOM 3 级事件中新增)stopPropagation()
:Function,只读,取消事件的进一步捕获或冒泡,前提是**bubbles 属性为 true **,才可以使用这个方法target
:Element,只读,事件的目标trusted
:Boolean,只读,为 true 表示事件是浏览器生成的,为 false 表示事件是由开发人员通过 JavaScript 创建的。(DOM 3 级事件中新增)type
:String,只读,被触发的事件的类型view
:AbstractView,只读,与事件关联的抽象视图,等同于发生事件的 window 对象在事件处理程序内部,对象 this 始终等于 currentTarget 的值,而 target 则只包含事件的实际目标。如果直接将事件处理程序指定给了目标元素,则 this、currentTarget、target包含相同的值。
var btn = document.getElementById("myBtn");
btn.onclick = function(event){
alert(event.currentTarget === this); //true
alert(event.target === this); //true
};
document.body.onclick = function(event){
alert(event.currentTarget === document.body); //true
alert(this === document.body); //true
alert(event.target === document.getElementById("myBtn")); //true
};
在需要通过一个函数处理多个事件时,可以使用 type 属性:
var btn = document.getElementById("myBtn");
var handler = function(event){
switch(event.type){
case "click":
alert("Clicked");
break;
case "mouseover":
event.target.style.backgroundColor = "red";
break;
case "mouseout":
event.target.style.backgroundColor = "";
break;
}
};
btn.onclick = handler;
btn.onmouseover = handler;
btn. onmouseout = handler;
要阻止特定事件的默认行为,可以使用 preventDefault() 方法。例如链接的默认行为就是在被单击时会导航到其 href 特性指定的 URL。如果你想组织链接导航这一默认行为,那么通过链接的 onclick 事件处理程序可以取消。只有 cancelable 属性设置为 true 的事件,才可以使用 preventDefault() 来取消其默认行为。
var link = document.getElementById("myLink");
link.onclick = function(event){
event.preventDefault();
};
stopPropagation() 方法用于立即阻止事件在 DOM 层次中的传播,即取消进一步的事件捕获或冒泡。
eventPhase 属性,可以用来确定事件当前正位于事件流的哪个阶段。
var btn = document.getElementById("myBtn");
btn.onclick = function(event){
alert(event.eventPhase); //2
};
document.body.addEventListener("click", function(event){
alert(event.eventPhase); //1
}, true);
document.body.onclick = function(event){
alert(event.eventPhase); //3
};
梳理下相关概念:
捕获阶段
、处于目标阶段
、冒泡阶段
,以click事件为例,当触发时,会经历document - html - body - ... - 具体元素 - ... - body - html - document,可以理解为在事件流的“流动”过程中,每经过一个节点都可以在上面做事情(类似于钩子函数的概念),而这个就是事件处理函数,而在这个过程中,每个节点都可以注册一个函数(当事件流到达节点时,就会触发函数的执行)。只有在事件处理程序执行期间,event 对象才会存在;一旦事件处理程序执行完成,event 对象就会被销毁
IE 中的事件对象与跨浏览器的事件对象。(暂不深入研究)
function isValidIdCardNo(cardNo) {
var reg = /(^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$)|(^[1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{2}$)/;
return reg.test(cardNo);
}
通过 mac 自带的 ruby 套件一键安装 homebrew
:
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
homebrew 官网:https://brew.sh/
// 安装 Node
brew install node
Ajax: A New Approach to Web Applications
Asynchronous JavaScript + XML
能够向服务器请求额外数据而无需卸载页面 => 改变 Web 诞生以来一直沿用的“单击,等待”的交互模式。
浏览器发起请求的几种方式(有什么区别):
浏览器获取数据的方式:
XHR 的出现将浏览器原生的通信能力提供给了开发人员。其实有很多中方法可以实现这种浏览器和服务器的通信:隐藏的框架或内嵌框架、Java Applet、Flash等
new XMLHttpRequest()
function createXHR() {
if (typeof XMLHttpRequest != "undefined") {
return new XMLHttpRequest();
} else if (typeof ActiveXObject != "undefined") {
if (typeof arguments.callee.activeXString != "string") {
var versions = ["MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0", "MSXML2.XMLHttp"],
i, len;
for (i = 0, len = versions.length; i < len; i++) {
try {
new ActiveXObject(versions[i]);
arguments.callee.activeXString = versions[i];
break;
} catch (ex) {
// 跳过
}
}
}
return new ActiveXObject(arguments.callee.activeXString);
} else {
throw new Error("No XHR object available.");
}
}
var xhr = createXHR();
xhr.open()
接受三个参数(要发送请求的类型(get/post等)、请求的URL、表示是否异步发送请求的布尔值)
xhr.open("get", "example.php", false);
// 1. URL 相对于执行代码的当前界面
// 2. 调用 open() 方法并不会真正发送请求,而只是启动一个请求以备发送
注意跨域
XHR.send()
接受一个参数(作为请求主体发送的数据,没有必须传入 null。因为这个参数对于有些浏览器是必须的。调用 send() 之后,请求就会被分配到服务器)
xhr.send(null);
如果请求是同步的,那代码会等到服务器响应之后再继续执行。收到响应后,响应的数据会自动填充 XHR 对象的属性,相关属性有:
在接收到响应后,第一步是检查 status 属性,以确定响应已经成功返回。一般来说,可以将 HTTP 状态代码为 200 作为成功的标志。此时,responseText 属性内容已经就绪。此外,状态代码为 304 表示请求的资源并没有被修改,可以直接使用浏览器中缓存的版本。当然,也意味着响应是有效的:
xhr.open("get", "example.txt", false);
xhr.send(null);
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
alert(xhr.responseText);
} else {
alert("Request was unsuccessful: " + xhr.status);
}
如果请求是异步的(大多数情况下),此时可以检测 XHR 对象的 readyState 属性,该属性表示请求/响应过程的当前活动阶段。
**只要 readyState 属性的值变化时,都会触发一次 readystatechange 事件。**可以利用这个事件来检测每次状态变化后 readyState 的值。不过必须在调用 open() 之前指定 readystatechange 事件处理程序才能确保跨浏览器兼容性。
var xhr = createXHR();
xhr.onreadystatechange = function(){
if (xhr.readyState == 4){ // 没有使用 this,避免产生错误
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
alert(xhr.responseText);
} else {
alert("Request was unsuccessful: " + xhr.status);
}
}
};
xhr.open("get", "example.txt", true);
xhr.send(null);
以上利用 DOM 0 级方法为 XHR 对象添加了时间处理程序,原因是并非所有浏览器都支持 DOM 2 级方法。
另外,在 接收到响应之前 还可以调用 abort() 方法来取消异步请求。
xhr.abort();
调用这个方法之后,XHR 对象会停止触发事件,而且也不再允许访问任何与响应有关的对象属性。在终止请求之后,还应该对 XHR 对象进行 解引用 操作。
由于内存原因,不建议重用 XHR 对象。
每个 HTTP 请求和响应都会带有响应的头部信息。
默认情况下,在发送 XHR 请求的同时,还会发送下列头部信息。
setRequestHeader() 可以设置自定义的请求头部信息,接受两个参数:头部字段的名称和头部字段的值。要成功发送请求头部信息,必须在调用 open() 方法之后且调用 send() 方法之前调用 setRequestHeader():
var xhr = createXHR();
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {
alert(xhr.responseText);
} else {
alert("Request was unsuccessful: " + xhr.status);
}
}
};
xhr.open("get", "example.php", true);
xhr.setRequestHeader("MyHeader", "MyValue");
xhr.send(null);
服务器在接收到这种自定义的头部信息之后,可以执行响应的后续操作。尽量不要重写默认的头部信息,有的浏览器不允许这样做。
getResponseHeader() 方法并传入头部字段名称,可以取得相应的响应头部信息,而调用 getAllResponseHeaders() 方法则可以取得一个包含所有头部信息的长字符串。
var myHeader = xhr.getResponseHeader("MyHeader");
var allHeaders = xhr.getAllResponseHeaders();
GET 是最常见的请求类型,最常用于向服务器查询某些信息。必要时,可以将查询字符串参数追加到 URL 的末尾,以便将信息发送给服务器。对 XHR 而言,位于传入 open() 方法的 URL 末尾的查询字符串必须经过正确的编码才行。
使用 GET 请求经常会发生的一个错误,就是查询字符串的格式有问题。查询字符串中的每个参数的名称和值都必须使用 encodeURIComponent()
进行编码,然后才能放到 URL 的末尾;而且所有 名-值对儿 都必须由和号(&)分隔。
xhr.open("get", "example.php?name1=value1&name2=value2", true);
function addURLParam(url, name, value){
url += (url.indexOf("?") == -1 ? "?" : "&");
url += encodeURIComponent(name) + "=" + encodeURIComponent(value);
return url;
}
var url = "example.php";
// 添加参数
url = addURLParam(url, "name", "Nicholas");
url = addURLParam(url, "book", "Professional Javascript");
// 初始化请求
xhr.open("get", url, false);
你能举出多少种使用 GET 请求的场景吗?
使用频次仅次于 GRT 的是 POST 请求,通常用于向服务器发送应该被保存的数据。POST 请求应该把数据作为请求的主体提交,而 GET 请求传统上不是这样。POST 请求的主体可以包含非常多的数据。
(GET VS POST 区别)
(?有种错觉是 POST 比 GET 更常用,是因为很多操作其实本质上都是 GET 请求,如:访问服务器的第一个请求(可能是直接访问,也可能是跳转,但 SPA 可能就第一个页面是 GET 请求,后面只是改变了 URL)、各种外部资源的引用(带有 src 属性的元素,img/link/script/audio/video/iframe/...)、主动发起的 GET 请求(Ajax 等)...)
xhr.open("post", "example.php", true);
xhr.send(); // 发送 POST 请求的第二步就是向 send() 方法传入某些数据
注意:
function submitData(){
var xhr = createXHR();
xhr.onreadystatechange = function(){
if(xhr.readyState == 4){
if((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
alert(xhr.responseText);
}else{
alert("Request was unsuccessful: " + xhr.status);
}
}
}
xhr.open("post", "postexample.php", true);
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
var form = document.getElementById("user-info");
xhr.send(serialize(form));
}
表单数据的序列化 => XMLHttpRequest 2 级定义了 FormData 类型,可直接传入 XHR 的 send() 方法。
XHR 对象的 timeout 属性,表示请求在等待响应多少毫秒之后就终止。超时规定事件内浏览器还没有接收到响应,就会触发 timeout 事件,进而会调用 onetimeout 事件处理程序。
var xhr = createXHR();
xhr.onreadystatechange = function(){
if(xhr.readyState == 4){
try{
if((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
alert(xhr.responseText);
}else{
alert("Request was unsuccessful: " + xhr.status);
}
}catch(ex){
// 假设由 ontimeout 事件处理程序处理
// 当超时时,请求会自动终止,会调用 ontimeout 事件处理程序
// 但此时 readyState 可能已经改变为 4 了 => 会调用 onreadystatechange 事件处理程序
// 但在超时终止请求之后再访问 status 属性,会导致错误 => try-catch
}
}
}
xhr.open("get", "timeout.php", true);
xhr.timeout = 1000; // 将超时设置为 1 秒
xhr.ontimeout = function(){
alert("Request did not return in a second.");
};
xhr.send(null);
重写 XHR 响应的 MIME 类型。
因为返回响应的 MIME 类型决定了 XHR 对象如何处理它,所以提供一种方法能够重写服务器返回的 MIME 类型很有用。但调用此方法必须在 send() 方法之前,才能保证重写响应的 MIME 类型。
var xhr = createXHR();
xhr.open("get", "text.php", true);
xhr.overrideMimeType("text/xml");
xhr.send(null);
Poogress Event 规范定义了与客户端服务器通信有关的事件,后来也被其他 API 借鉴。
loadstart -> progress -> ... -> progress -> error/abort/load -> loadend
每个请求都从触发 loadstart 事件开始,接下来是一或多个 progress 事件,然后触发 error、abort 或 load 事件中的一个,最后以触发 loadend 事件结束。
load 事件理论是可替代 readystatechange 事件(没必要检查 readyState 属性了),但因为并非所有浏览器都为这个事件实现了适当的事件对象(event,其 target 属性指向 XHR 对象实例),所以还是需要使用 XHR 对象变量。
var xhr = createXHR();
xhr.onload = function(){ // 只要浏览器接收到服务器的响应,不管其状态如何,都会触发 load 事件。
// 但必须要检查 status 属性,才能确定数据是否真的已经可用了
if((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
alert(xhr.responseText);
}else{
alert("Request was unsuccessful: " + xhr.status);
}
}
xhr.open("get", "altevents.php", true);
xhr.send(null);
progress 事件,会在浏览器接收新数据期间周期性触发。
onprogress 事件处理程序会接收到一个 event 对象,其 target 属性是 XHR 对象,但包含着三个额外的属性:
创建进度指示器:
var xhr = createXHR();
xhr.onload = function(){
if((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
alert(xhr.responseText);
}else{
alert("Request was unsuccessful: " + xhr.status);
}
}
xhr.onprogress = function(event){ // 必须在调用 open() 方法之前添加 onprogress 事件处理程序
var divStatus = document.getElementById('status');
if(event.lengthComputable){
divStatus.innerHTML = "Received " + event.position + " of " + event.totalSize + " bytes";
}
}
xhr.open("get", "altevents.php", true);
xhr.send(null);
何谓“跨域”?
通过 XHR 实现 Ajax 通信的一个主要限制,来源于跨域安全策略。默认情况下,XHR 对象只能访问与包含它的页面位于同一个域的资源(从字面上来看,很容易认为跨域是由服务端控制?但其实不然)。这种安全策略可以预防某些恶意行为,但是实现合理的跨域请求对开发某些浏览器应用程序也是至关重要的。
CORS(Cross-Origin Resource Sharing,跨域资源共享)是 W3C 的一个工作草案,定义了在必须访问跨源资源时,浏览器与服务器应该如何沟通。CORS 背后的基本**,就是使用自定义的 HTTP 头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功,还是应该失败。(能否实现一个不受“同源策略”限制的浏览器?)
比如一个简单的使用 GET 或 POST 发送的请求,它没有自定义的头部,而主体内容是 text/plain
,在发送改请求时,需要给它附加一个额外的 Origin 头部,其中包含请求页面的源信息(协议、域名和端口),以便服务器根据这个头部信息来决定是否给予响应。
Origin: http://www.nczonline.net
如果服务器认为这个请求可以接受,就在 Access-Control-ALlow-Origin
头部中回发相同的源信息(如果是公共资源,可以回发“*”
)
Access-Control-ALlow-Origin: http://www.nczonline.net
如果没有这个头部,或者有这个头部但源信息不匹配,浏览器就会驳回请求。
跨域 XHR 对象有一些限制(主要为了安全考虑):
由于无论同源请求还是跨源请求都使用相同的接口,因此对于本地资源,最好使用相对 URL,在访问远程资源时再使用绝对 URL,这样做能消除歧义,避免出现限制访问头部或本地 cookie 信息等问题。
Prefighted Requests:CORS 通过一种叫做 Prefighted Requests 的透明服务器验证机制支持开发人员使用自定义的头部、GET 或 POST 之外的方法,以及不同类型的主体内容。
带凭据的请求:默认情况下,跨源请求不提供凭据(cookie、HTTP 认证及客户端 SSL 证明等)。通过将 withCredentials 属性设置为 true,可以指定某个请求应该发送凭据,并以下面的 HTTP 头部来响应:
Access-Control-Allow-Credentials: true
如果发送的是带凭据的请求,但是服务器的响应中没有包含这个头部,那么浏览器就不会把响应交给 JavaScript,于是:
另外,服务器还可以在 Prefight 响应中发送这个 HTTP 头部,表示允许源发送带凭据的请求。
跨浏览器的 CORS:其实浏览器对 CORS 的支持成都并不都一样,但所有浏览器都支持简单的(非 Prefighte 和不带凭据的)请求。因此有必要实现一个跨浏览器的方案,检测 XHR 是否支持 CORS 的最简单方式,就是检查是否存在 withCredentials 属性,再结合检测 XDomainRequest 对象是否存在(IE),就可以兼顾所有浏览器了(?此处存疑,不知当前是什么情况)。
在 CORS 出现以前,要实现跨域 Ajax 通信颇费周折,开发人员想出一些办法,利用 DOM 中能够执行跨域请求的功能,在不依赖 XHR 对象的情况下也能发送某种请求。
虽然 CORS 技术已经无处不在,但开发人员自己发明的这些技术仍然被广泛使用,毕竟这样不需要修改服务器端代码。
<img>
标签。
图像 Ping
是与服务器进行,简单、单向的跨域通信的一种方式。请求的数据是通过查询字符串形式发送的,而响应可以是任意内容,但通常是像素图或 204 响应。通过图像 Ping,浏览器得不到任何具体的数据,但通过侦听 load 和 error 事件,它能知道响应是什么时候接收到的。
var img = new Image();
img.onload = img.onerror = function(){
alert("Done!");
}
img.src = "http://www.example/com/test?name=Nicholas";
图像 Ping 最常用于跟踪用户点击页面或动态广告曝光次数。
图像 Ping 有两个主要的缺点:
因此,图像 Ping 只能用户浏览器与服务器间的单向通信。
JSONP 是 JSON with padding(填充式 JSON 或参数式 JSON)的简写,是应用 JSON 的一种新方法。JSONP 看起来与 JSON 差不多,只不过 是被包含在函数调用中的 JSON:
callback({"name": "Nicholas"});
JSONP 由两部分组成:
// 典型的 JSONP 请求
http://freegeoip.net/json?callback=handleResponse // 请求一个 JSONP 地理定位服务
通过查询字符串来指定 JSONP 服务的回调函数是很常见的,这里指定的回调函数的名字叫 handleResponse()。
JSONP 是通过动态<script>
元素来使用的,使用时可以为 src 属性指定一个跨域 URL。(这里的 script 元素与 img 元素类似,都有能力不受限制地从其他域加载资源)
因为 JSONP 是有效的 JavaScript 代码,所以在请求完成后,即在 JSONP 响应加载到页面以后,就会立即执行。
function handleResponse(response){
alert("You are at IP address " + response.ip + ", which is in " + response.city + ", " + response.region_name);
}
var script = document.createElement("script");
script.src = "http://freegeoip.net/json?callback=handleResponse";
document.body.insertBefore(script, document.body.firstChild);
JSONP 与图像 Ping相比,优点在于能够直接访问响应文本,支持在浏览器与服务器之间双向通信。
不足在于:
更高级的 Ajax 技术(或者称之为“服务器推送”),Ajax 是一种从页面像服务器请求数据的技术,而 Comet 则是一种服务器向页面推送数据的技术。
实现方式:长轮询 和 流
SSE,Service-Sent Events
目标是在一个单独的持久连接上提供全双工、双向通信。
纯文本 —— 复杂数据序列化
对于未被授权系统有权访问某个资源的情况,称之为 CSRF(Cross-Site Request Forgery,跨站点请求伪造)。
为确保 XHR 访问的 URL 安全,通行的做法就是:
Ajax 是无需刷新页面就能够从服务器取得数据的一种方法,关于 Ajax:
同源策略是对 XHR 的一个主要约束,为通信设置了“相同的域,相同的端口,相同的协议”。
试图访问上述限制之外的资源,都会引发安全错误,除非采用被认可的跨域解决方案——CORS(大部分浏览器通过 XHR 对象原生支持 CORS),图像 Ping 和 JSONP 是另外跨域通信的技术。
对应“js高程ch4”
基本类型
值在内存中占据固定大小的空间,保存在栈内存中引用类型
值是对象,保存在堆内存中(a.可以动态改变属性)。保存引用类型值的变量实际上保存的是一个指向该对象的指针,而不是对象本身。(b.具体差异在值复制过程中表现尤为明显。)执行环境及作用域
自动垃圾收集
function createPerson(name){
var localPerson = new Object();
localPerson.name = name;
return localPerson;
}
var globalPerson = createPerson("Nicholas");
// 手工接触 globalPerson 的引用
globalPerson = null;
JavaScript Object Notation,JavaScript 对象表示法。是一种数据格式。
JSON 是 JavaScript 的一个严格的子集,利用了 JavaScript 中的一些模式来表示结构化数据。
JSON 的语法可以表示以下三种类型的值:
5
true
null
"Hello, World!"
JSON 字符串必须使用双引号。
JSON 中对象的属性需要加引号。
{
"name": "Nicholas",
"age": 30
}
[
25,
"hi",
true
]
[
{
"title": "Professional JavaScript",
"author": "Nicholas C. Zakas",
"edition": 1
},
{
"title": "Professional JavaScript",
"author": "Nicholas C. Zakas",
"edition": 1
},
{
"title": "Professional JavaScript",
"author": "Nicholas C. Zakas",
"edition": 1
},
{
"title": "Professional JavaScript",
"author": "Nicholas C. Zakas",
"edition": 1
}
]
对象和数组通常是 JSON 数据格式的最外层形式,利用它们能够创造各种各样的数据结构。
JSON 数据结构 能够很方便的解析为 有用的 JavaScript 对象。
JSON 对象(不支持的浏览器,可以使用一个 shim:https://github.com/douglascrockford/JSON-js)
ECMAScript 5 定义了一个原生的 JSON 对象,可以用来将对象序列化为 JSON 字符串或者将 JSON 数据解析为 JavaScript 对象。JSON 对象有两个方法分别用来实现上述两项功能,这两个方法都有一些选项,通过它们可以改变过滤的方法,或者改变序列化的过程。
把 JavaScript 对象序列化为 JSON 字符串。
var book = {
title: "Professional JavaScript",
authors: [
"Nicholas C. Zakas"
],
edition: 3,
year: 2011
}
var jsonText = JSON.stringify(book);
默认情况下,输出的 JSON 字符串不包含任何空格字符或缩进。
jsonText 中的字符串:
{"title":"Professional JavaScript","authors":["Nicholas C. Zakas"],"edition":3,"year":2011}
在序列化 JavaScript 对象时,所有函数及原型成员都会被有意忽略,不体现在结果中。,值为 undefined 的任何属性也都会被跳过。结果中最终都是值为有效 JSON 数据类型的实例属性。
JSON.stringify() - JavaScript | MDN
// JSON.stringify(value[, replacer[, space]])
// 1. 过滤结果
// 数组
var book = {
title: "Professional JavaScript",
authors: [
"Nicholas C. Zakas"
],
edition: 3,
year: 2011
};
var jsonText = JSON.stringify(book, ["title", "edition"]);
// jsonText
// {"title":"Professional JavaScript","edition":3}
// 函数
var jsonText = JSON.stringify(book, function(key, value){
switch(key){
case "authors":
return value.join(',');
case "year":
return 5000;
case "edition":
return undefined;
default:
return value;
}
});
// jsonText
// {"title":"Professional JavaScript","authors":"Nicholas C. Zakas","year":5000}
// 2. 字符串缩进
var jsonText = JSON.stringify(book, null, 4);
// jsonText
// {
// "title": "Professional JavaScript",
// "authors": [
// "Nicholas C. Zakas"
// ],
// "edition": 3,
// "year": 2011
// }
var jsonText = JSON.stringify(book, null, " - -");
// jsonText
// {
// - -"title": "Professional JavaScript",
// - -"authors": [
// - - - -"Nicholas C. Zakas"
// - -],
// - -"edition": 3,
// - -"year": 2011
// }
toJSON() 方法:有时候,JSON.stringify() 还是不能满足对某些对象进行自定义序列化的需求。=>在这些情况下,可以给对象定义 toJSON() 方法,返回其自身的 JSON 数据格式。
var book = {
title: "Professional JavaScript",
authors: [
"Nicholas C. Zakas"
],
edition: 3,
year: 2011,
toJSON: function(){
return this.title;
}
};
var jsonText = JSON.stringify(book);
// jsonText
// "Professional JavaScript"
可以让 toJSON() 方法返回任何值,如果返回 undefined => 1. 如果包含它的对象嵌入在另一个对象中,会导致它的值变成 null, 2. 如果它是顶级对象,那序列化返回的结果是 undefined。
toJSON() 作为函数过滤器的补充,理解 序列化(JSON.stringify)的内部顺序 十分重要:
把 JSON 字符串解析为原生 JavaScript 值。将 JSON 字符串直接传递给 JSON.parse() 就可以得到相应的 JavaScript 值。但注意:序列化前和解析后的对象是相互独立、没有任何关系的对象。如果传给 JSON.parse() 的字符串不是有效的 JSON,会抛出错误(一般会结合 try-catch)。
JSON.parse() - JavaScript | MDN
var book = {
title: "Professional JavaScript",
authors: [
"Nicholas C. Zakas"
],
edition: 3,
year: 2011,
releaseDate: new Date(2011, 11, 1)
};
var jsonText = JSON.stringify(book);
var bookCopy = JSON.parse(jsonText, function(key, value){
if(key == "releaseDate"){
return new Date(value);
}else{
return value;
}
});
alert(bookCopy.releaseDate.getFullYear()); // 2011
在实现轮播效果的时候,往往会用到一些库,例如比较强大的[Swiper](http://3.swiper.com.cn/)
。但是如果要基于React实现的话,往往会第一时间想去找找是否有相应的组件库支持,例如antd
的[Carousel组件](https://mobile.ant.design/components/carousel-cn/)
,但是在使用过程中,还是会遇到各种各样的bug,这里暂时不展开。最终,采用基于swiper在react中实现轮播效果。
if(this.indexSwiper){
this.indexSwiper.destroy(true,true);
this.indexSwiper = null;
}
this.indexSwiper = new Swiper('.banner-recommend', {
loop: true,
autoplay: 3000,
speed: 800,
autoplayDisableOnInteraction: false,
pagination: '.banner-recommend .swiper-pagination',
onInit: (swiper) => {
if(this.state.bannerList.length > 1){
$(".banner-recommend .swiper-pagination").show();
}else{
swiper.stopAutoplay();
swiper.params.onlyExternal = true;
$(".banner-recommend .swiper-pagination").hide();
}
}
});
<section className="swiper-container banner-recommend">
<div className="swiper-wrapper">
{
bannerList.map((banner) =>
<section className="banner-item swiper-slide">
<img src={banner.pictureUrl} alt=""/>
</section>
)
}
</div>
<div className="swiper-pagination"></div>
</section>
根据Swiper文档,http://3.swiper.com.cn/api/Images/2015/0308/213.html,加上相应属性如下:
lazyLoading: true
<section className="banner-item swiper-slide" >
<section className="swiper-lazy" data-background={banner.pictureUrl}></section>
</section>
我们知道React是数据驱动的,而懒加载效果除了依赖数据外,还依赖具体的触发条件的,例如首次出现在屏幕中,具体的实现方式一般都是设置一个data-src
属性,当达到触发条件时,将元素的src属性赋值为data-src的值。但是在React中,如果修改状态,仅仅会修改data-src属性值,并不能修改能够真正决定显示的src属性的值,这样就会出现当修改了轮播的数据,页面上显示的轮播图片并没有改变的bug,但其实data-src的值已经更新了。
在更新banner数据时,先将其置空,以达到想要的初始化效果。
this.setState({
bannerList: {}, // 解决修改banner源与懒加载冲突bug
}, ()=>{
that.setState({
bannerList: getData.content || []
}, ()=>{
that.initBanner();
// **此处隐藏一个bug**,具体说明见楼下
});
});
try{
// 可能会导致错误的代码
} catch(error){
// 在错误发生时怎么处理
}
如果在 try 块中的任何代码发生了错误,就会立即退出代码执行过程,然后接着执行 catch 块。此时 catch 块会接收到一个包含错误信息的对象。
无论如何都会执行(无论 try 或 catch 语句块中包含什么代码——甚至 return 语句)
function testFinally(){
try{
return 2; // 会被忽略
} catch(error){
return 1;
} finally {
return 0; // 函数最终返回 0
}
}
throw,用于随时抛出自定义错误。在遇到 throw 操作符时,代码会立即停止执行(其他语言也可能会这样吗?)。仅当有 try-catch 语句捕获到被抛出的值时,代码才会继续执行。
function CustomError(message){
this.name = 'CustomError';
this.message = message;
}
CustomError.prototype = new Error();
throw new CustomError('My message');
要针对函数为什么会执行失败给出更多信息(=>便于调试,查找错误来源),抛出自定义错误是一种很方便的方式。应该在出现某种特定的已知错误条件,导致函数无法正常执行时抛出错误。换句话说,浏览器会在某种特定的条件下执行函数时抛出错误。
开发过程中,重点关注函数和可能导致函数执行失败的因素,良好的错误处理机制应该可以确保代码中只发生你自己抛出的错误。
抛出错误(throw)与捕获错误(try-catch):
任何没有通过 try-catch 处理的错误都会触发 window 对象的 error 事件。在任何 web 浏览器中,onerror 时间处理程序都不会创建 event 对象,但它可以接收三个参数:错误消息、错误所在的 URL 和行号。
要指定 onerror 时间处理程序,必须使用如下所示的 DOM 0 级技术:
window.onerror = function(message, url, line){
alert(message);
}
只要发生错误,无论是不是浏览器生成的,都会触发 error 事件,并执行这个事件处理程序。然后,浏览器默认的机制发挥作用,像往常一样显示出错误信息。可以在事件处理程序中返回 false,可以阻止浏览器报告错误的默认行为。
window.onerror = function(message, url, line){
alert(message);
return false; // 这样实际上就充当了整个文档中的 try-catch 语句,可以捕获所有无代码处理的运行时错误
}
图像页支持 onerror 事件,只要图像的 src 特性中的 URL 不能返回可以被识别的图像格式,就会触发 error 事件。
记录和监控系统 => 分析错误模式,追查错误原因,同时帮助确定错误会影响到多少用户。
错误处理的核心,是首先要知道代码里会发生什么错误。
由于 JavaScript 是松散类型的,而且也不会验证函数的参数,因此错误只会在代码运行期间出现。(why?)
一般来说,需要关注三种错误:
以上错误会 在特定的模式下 或者 没有对值进行足够的检查的情况下发生。
类型转换错误:发生在使用某个操作符,或者使用其他可能会自动转换值的数据类型的语言结构时。
数据类型错误:松散类型 => 在使用变量和函数参数之前,不会对它们进行比较以确保它们的数据类型正确。
通信错误:随着 Ajax 编程的兴起 => Web APP 在其生命周期内动态加载信息或功能。与服务器之间的任何一次通信,都有可能会产生错误。
非致命错误
致命错误
function logError(sev, msg){
var img = new Image();
img.src = "log.php?sev=" + encodeURIComponent(sev) + "&msg=" + encodeURIComponent(msg);
}
只要是使用 try-catch 语句,就应该把相应错误记录到日志中。
function log(message) {
var console = document.getElementById("debuginfo");
if (console === null) {
console = document.createElement("div");
console.id = "debuginfo";
console.style.background = "#dedede";
console.style.border = "1px solid silver";
console.style.padding = "5px";
console.style.width = "400px";
console.style.position = "absolute";
console.style.right = "0px";
console.style.top = "0px";
document.body.appendChild(console);
}
console.innerHTML += "<p>" + message + "</p>";
}
function assert(condition, message){
if (!condition){
throw new Error(message);
}
}
function divide(num1, num2){
assert(typeof num1 == "number" && typeof num2 == "number",
"divide(): Both arguments must be numbers.");
return num1 / num2;
}
避免浏览器响应 JavaScript 错误的方法:
另外,对任何 Web 应用程序都应该分析可能的错误来源,并制定处理错误的方案:
现在每天都会做的一件事情,就是抓取早读课的最新推送,然后 push 到 FeZaoDuKe-Collection 仓库中。
做法其实很傻,就是手动在 Node 环境中运行负责“抓取”的 js 文件,存到本地。然后再次手动使用 Git 命令提交。
其实,操作很简单,但是就想着能不能更简单一点,终极目标是:早读课文章推送后,立马能够自动同步到我的仓库中
但是,里面具体的很多细节还有待研究(虽然大神一下就能实现,这里先留个坑,有时间再来解决,主要是定时任务这块还不知道怎么实现)
“自动抓取”和“自动提交”通过以下 bat 命令实现:
// 自动抓取
node getToDayNews.js
// 自动提交
git add README.md
git commit -m ":memo:update"
git push origin master
这里,执行 git push 命令时可能每次都会弹出需要登录的提示或者直接超时报错,可以采用 RSA 的方式保持登录态。
引用类型
引用的值(即对象)是引用类型的实例。
new
操作符后跟一个构造函数来创建。出于创建新对象的目的而定义的函数
。Object 类型
new
操作符后跟 Object 构造函数Array 类型
创建数组实例的方法
数组大小
数组检测
转换方法
栈方法(LIFO)
队列方法(FIFO)
重排序方法
0,a比b“大”:b在a前(换位置)
操作方法
var colors = [“red”, “green”, “blue”];
var removed = colors.splice(0,1); //remove the first item
alert(colors); //green,blue
alert(removed); //red - one item array
removed = colors.splice(1, 0, “yellow”, “orange”); //insert two items at position 1
alert(colors); //green,yellow,orange,blue
alert(removed); //empty array
removed = colors.splice(1, 1, “red”, “purple”); //insert two values, remove one
alert(colors); //green,red,purple,orange,blue
alert(removed); //yellow - one item array
位置方法
迭代方法
var numbers = [1,2,3,4,5,4,3,2,1];
var everyResult = numbers.every(function(item, index, array){
return (item > 2);
});
alert(everyResult); //false
var someResult = numbers.some(function(item, index, array){
return (item > 2);
});
alert(someResult); //true
var numbers = [1,2,3,4,5,4,3,2,1];
var filterResult = numbers.filter(function(item, index, array){
return (item > 2);
});
alert(filterResult); //[3,4,5,4,3]
var numbers = [1,2,3,4,5,4,3,2,1];
var mapResult = numbers.map(function(item, index, array){
return item * 2;
});
alert(mapResult); //[2,4,6,8,10,8,6,4,2]
var numbers = [1,2,3,4,5,4,3,2,1];
numbers.forEach(function(item, index, array){
//do something here
});
归并方法
var values = [1,2,3,4,5];
var sum = values.reduce(function(prev, cur, index, array){
return prev + cur;
});
alert(sum); //15
var values = [1,2,3,4,5];
var sum = values.reduceRight(function(prev, cur, index, array){
return prev + cur;
});
alert(sum); //15
Date类型
RegExp类型
Function类型
基本包装类型
// 对于这段代码
var s1 = 'test';
var s2 = s1.substring(2);
// 其中第2句在执行过程中,对应可以细分为:
var sTemp = new String('test'); // 创建String类型的一个实例
var s2 = sTemp.substring(2); // 在实例上调用指定的方法
sTemp = null; // 销毁这个实例
new Object('test')
,传入null和undefined会返回空对象单体内置对象
Math.max.apply(Math, array)
,这样就可以找到数组的最大/小值引用类型小结
距离活动页正式上线还有2天,心情比较复杂,需要写点东西沉淀下来。
大概十几天前,产品妹子和运营妹子把我拉到了一个小黑屋,一脸坏笑地对我说,“母亲节快要到了,我们有一个活动页需要你支持一下”。我心想,“随便找个{易企秀}啊、{云凤蝶}啥的搭一下不就完了嘛,还要我来写一个?”。本着{能推就推}的原则,我也就暂且坐下来听听她们的需求细节。运营妹子说,“来,我给你看一个H5页面示例”,反手就拿出来了一个网易的活动链接,深夜,男同事问我睡了吗……,“我们就想做成这样子的一个效果,网上的那些模板我们都看过了,都做不了,所以就只要请大神帮帮忙”。哟呵,这首先拿了个大厂的案例给我来了个下马威,马上又大神大神的叫着,你说这我要是告诉人家“我不会”是多尴尬呀。“这很简单,不就一个H5吗,最多两个小时就给你搞定,不过你们得先把效果、图片啥的都准备好”
需求大概确定了,运营妹子扔过来一个DEMO链接,“差不多就是这个样子了”。
首先很明确这是一个独立的分页H5,每一页上会有一些动画或者视频,然后加上一个加载页,大概也就这些东西。
<!DOCTYPE html>
<html>
<head>
</head>
<body id="wrapper">
<section id="container">
<section id="main-page-0" class="main-page loading current-page" data-index='0'></section>
<section id="main-page-1" class="main-page" data-index='1'></section>
<section id="main-page-2" class="main-page" data-index='2'></section>
<section id="main-page-3" class="main-page" data-index='3'></section>
<section id="main-page-4" class="main-page" data-index='4'></section>
<section id="main-page-5" class="main-page" data-index='5'></section>
</section>
</body>
</html>
display:block
,其他所有页面都display:none
.current-page
表示当前显示页面方案一:最开始想到的方案
.main-page{
width: 100vw;
height: 100vh;
}
感觉这样挺好,但是因为vw
和vh
对于部分机型尚不兼容,所以不轻易采用这种写法。
方案二:后来看了一下{易企秀}的实现方案
body{
width: 100%;
min-height: 100%;
position: fixed;
}
.main-page{
width: 100%;
height: 100%;
}
用户在滑动时,需要隐藏当前页面,显示上/下一页。借鉴项目以前的代码:需要在页面初始化后调用initTouch()给body绑定touch相关事件
var $body = document.getElementsByTagName('body')[0]; // body
// 页面触摸事件
var touchStartListener = function (event) {
var touches = event.targetTouches;
if (touches.length == 1) {
this.x = touches[0].clientX;
this.y = touches[0].clientY;
}
}
var touchMoveListener = function (event) {
if(!isAllowTouch) return false;
var touches = event.targetTouches;
if (touches.length == 1) { //一个手指在屏幕上
var x1 = touches[0].clientX, //移动到的坐标
y1 = touches[0].clientY;
var deltaY = y1 -this.y;
var deltaX = x1 -this.x;
if ((Math.abs(deltaY) < Math.abs(deltaX)) && (deltaX < 0) &&((x1 + 20) < this.x)){
// 左滑
}
if ((Math.abs(deltaY) < Math.abs(deltaX)) && (deltaX > 0) &&((x1 - 20) > this.x)){
// 右滑
}
if ((Math.abs(deltaY) > Math.abs(deltaX)) && (deltaY < 0) &&((y1 + 20) < this.y)){
// 上滑
}
if ((Math.abs(deltaY) > Math.abs(deltaX)) && (deltaY > 0) &&((y1 - 20) > this.y)){
// 下滑
}
}
}
var touchEndListener = function (event) {}
var touchStartListenerThrottle = _throttled(touchStartListener, 50);
var touchMoveListenerThrottle = _throttled(touchMoveListener, 50);
var touchEndListenerThrottle = _throttled(touchEndListener, 50);
var initTouch = function () {
$body.addEventListener("touchstart", touchStartListenerThrottle);
$body.addEventListener("touchmove", touchMoveListenerThrottle);
$body.addEventListener("touchend", touchEndListenerThrottle);
}
var removeTouch = function () {
$body.removeEventListener("touchstart", touchStartListenerThrottle);
$body.removeEventListener("touchmove", touchMoveListenerThrottle);
$body.removeEventListener("touchend", this.touchEndListenerThrottle);
}
我们是用touchstart
和touchmove
/touchend
来模拟滑动事件,理论上touchmove会持续(节流函数下会隔一段时间)触发,因此在实际情况下一次滑动仅且只能翻一页。节流函数如下:
// 节流函数
function _throttled(func, wait, options){
// throttled节流
var timeout, context, args, result
var previous = 0
if (!options) options = {}
var later = function() {
previous = options.leading === false ? 0 : Date.now()
timeout = null
result = func.apply(context, args)
if (!timeout) context = args = null
}
var throttled = function() {
var now = Date.now()
if (!previous && options.leading === false) previous = now
var remaining = wait - (now - previous)
context = this
args = arguments
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout)
timeout = null
}
previous = now
result = func.apply(context, args)
if (!timeout) context = args = null
} else if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, remaining)
}
return result
}
throttled.cancel = function() {
clearTimeout(timeout)
previous = 0
timeout = context = args = null
}
return throttled;
}
每一页全屏实现了,但是如何实现每一屏内容自适应。
首先将屏幕分为三层:背景底层(纯色)、背景层、内容层(图片、文字、动画、视频等)。
background-color
background-size:cover
,有些则主要贴底background-position:bottom center
,代码如下:#main-page-3{
background-image: url('../imgs/m/p1_2_3_shadow.png'), url('../imgs/m/p3_vision.png'), url('../imgs/m/p3_bg.png');
background-size: cover, 100% auto, cover;
background-repeat: no-repeat, no-repeat, no-repeat;
background-position: center, bottom center, center;
}
.main-page
绝对定位显示即可理论情况下我们是根据整个H5页面资源的加载情况来输出加载进度,但是实际情况下是很难准确的获取到这些信息的,因此大多数情况下的H5加载都是模拟的,这次的H5因为涉及到视频的播放,所以可以认为视频加载完成,则页面就加载完成,而在此之前都随机增长:
// 随机生成加载进度,模拟视频加载
function _randomProgress(initValue, step, duration, maxValue){
var num = initValue;
var tid = setTimeout(function () {
num += parseInt(Math.random()*step);
if(num >= maxValue){
num = maxValue;
clearTimeout(tid);
tid = null;
$('#loading-progress').text(num);
}else{
_randomProgress(num, step, duration,maxValue);
}
$('#loading-progress').text(num);
}, duration);
}
首先我用了一个全局变量currentPageIndex
来定位当前页面,当触发页面滑动时,首先隐藏当前页面,改变currentPageIndex
的值,显示当前页面,显示当前页面的动画。
其次可以明确的是页面切换和动画是逻辑上独立的两个过程,但是在实际情况中,两者是存在回调的关系的,例如页面切换后,就需要执行当前页面的动画,动画结束定格几秒后,就需要进行页面切换。其中前面还可以通过手动触发翻页来完成。
// 页面切换动效
var handlePageSwitch = function (step) {
if((currentPageIndex <= 1 && step === -1) || (currentPageIndex === 5 && step === 1)) return false; // 限制页面滑动边界条件
isAllowTouch = false;
var $currentPage = $('#main-page-'+currentPageIndex);
$currentPage.hide();
currentPageIndex = currentPageIndex + step;
pageAnimate(step);
}
// 页面动画
var pageAnimate = function (step) {
var fadeInTime = 500;
var $currentPage = $('#main-page-'+currentPageIndex);
console.log('P' + (currentPageIndex-step) +'-P' + currentPageIndex);
switch('' + currentPageIndex + step){
// 0->1
case '11':
$currentPage.fadeIn(fadeInTime, function () {
video.play();
isAllowTouch = true; // 测试
});
break;
// 1->2
case '21':
video.currentTime = 0;
video.pause();
$currentPage.addClass('scale-page');
$currentPage.fadeIn(fadeInTime, function () {
console.log(1);
setTimeout(function () {
$('#main-page-2 .words').fadeIn();
}, 256)
isAllowTouch = true;
});
break;
// 2->3
case '31':
$('#main-page-2 .words').hide();
$currentPage.removeClass('scale-page');
$currentPage.fadeIn(fadeInTime, function () {
$('#main-page-3 .words').fadeIn();
isAllowTouch = true;
});
break;
// 3->4
case '41':
$('#main-page-3 .words').hide();
$currentPage.fadeIn(fadeInTime, function () {
isAllowTouch = true;
});
break;
// 4->5
case '51':
$currentPage.fadeIn(fadeInTime, function () {
isAllowTouch = true;
});
break;
// 5->4
case '4-1':
$currentPage.show();
setTimeout(function () {
isAllowTouch = true;
}, fadeInTime);
break;
// 4->3
case '3-1':
$currentPage.show();
setTimeout(function () {
$('#main-page-3 .words').fadeIn();
isAllowTouch = true;
}, fadeInTime);
break;
// 3->2
case '2-1':
$('#main-page-3 .words').hide();
$currentPage.removeClass('scale-page');
$currentPage.show();
setTimeout(function () {
setTimeout(function () {
$('#main-page-2 .words').fadeIn();
}, 256)
isAllowTouch = true;
}, fadeInTime);
break;
// 2->1
case '1-1':
$('#main-page-2 .words').hide();
$currentPage.show();
video.play();
setTimeout(function () {
isAllowTouch = true;
}, fadeInTime);
break;
default:
}
}
这里在页面动画中,我将各种具体情况都分条进行了处理,即“P0-P1,P1-P2,P2-P3,……,P5-P4,……”。所以当进入某一页时,需要把前一页的动画隐藏。即页面之前的动画处理逻辑耦合了。所以当需求发生变化的时候,例如页面动画时间变一变、渐入时间变一变,就很难维护,逻辑很混乱。
梳理后,代码如下:
// 页面切换
// @params step 1-前进;0-后退
var _pageChange = function(step){
if((currentPageIndex <= 1 && step === 0) || (currentPageIndex === 5 && step === 1)) return false; // 限制页面滑动边界条件
isAllowTouch = false; // Step0:页面滑动状态设为false
// var pageFlag = '' + currentPageIndex + step;
$currentPage().hide(); // Step1:上一个页面直接消失
_initPageAnimation(); // Step2:初始化当前页面内容及动画
currentPageIndex += step ? 1 : -1; // Step3:页面索引改变
$body.style.backgroundColor = bgColorArr[currentPageIndex]; // Step4:修改页面背景色
$currentPage().fadeIn(pageSwitchFadeInTime, function () { // Step5:页面渐入或+缩放动画
if(currentPageIndex === 1 && step) audio.play();
_pageAnimation(); // Step6:页面动画
isAllowTouch = true; // Step7:页面滑动状态设为true
});
}
只需管理当前页面的动画效果,增加一个页面动画初始化的逻辑:
// 初始化页面动画/页面回复
var _initPageAnimation = function () {
$('.init').hide();
video.currentTime(0);
video.pause();
}
// 页面动画
var _pageAnimation = function () {
if(currentPageIndex != 0 && currentPageIndex != 5) $('#arrow').show(); // 左滑箭头显示
switch(currentPageIndex){
case 0:
break;
case 1:
$('#arrow').show();
video.play();
break;
case 2:
$('#main-page-2 .init').fadeIn(wordFadeInTime, function () {
if(currentPageIndex != 2) return false;
setTimeout(function () {
if(currentPageIndex != 2) return false;
_pageChange(1);
}, wordInAndPageChangeTime);
});
break;
case 3:
$('#main-page-3 .init').fadeIn(wordFadeInTime, function () {
if(currentPageIndex != 3) return false;
setTimeout(function () {
if(currentPageIndex != 3) return false;
_pageChange(1);
}, wordInAndPageChangeTime);
});
break;
case 4:
$('#main-page-4 .init').fadeIn(wordFadeInTime, function () {
if(currentPageIndex != 4) return false;
setTimeout(function () {
if(currentPageIndex != 4) return false;
_pageChange(1);
}, wordInAndPageChangeTime);
});
break;
case 5:
$('#arrow-up').show();
break;
default:
}
}
编码前没能做好设计!后期有时间考虑借助面向对象**实现
因为之前从未接触过H5下视频播放的问题,但是一经手才发现处理好视频真不是一件容易的事情。
方案一:使用原始video标签
在分析上面提到的那个网易的H5页面时,发现他用的也是原生的video标签(现在想来,如果利用了什么库,最后呈现在浏览器的也可能是video标签),只不过我发现其中有这么一段代码(混淆后):
var c = new XMLHttpRequest;
c.open("GET", r, !0),
c.responseType = "blob",
c.onload = function() {
if (200 === this.status && "video/mp4" === this.response.type) {
var i = this.response
, a = (window.URL || window.webkitURL || window || {}).createObjectURL(i);
n(s),
e(l),
o.src = a
} else
t()
}
,
c.onerror = function(e) {
console.log(e),
t()
}
,
c.send()
发现用到了一个blob对象
,虽然不明觉厉,自己也手把手的用上了:
// 视频初始化
var _initVideo = function (cb) {
var xhr = new XMLHttpRequest();
xhr.open('GET', videoPath, true);
xhr.responseType = 'blob';
xhr.onload = function() {
if (200 === this.status && "video/mp4" === this.response.type){
var res = this.response;
var url = (window.URL || window.webkitURL || window || {}).createObjectURL(res);
video.src = url;
}else{
video.src = videoPath;
videoLoaded = true;
cb && cb();
}
}
xhr.onerror = function(e) {
console.log(e);
video.src = videoPath;
videoLoaded = true;
cb && cb();
}
xhr.send();
video.addEventListener('loadstart', function () {
videoLoaded = true;
handlePageSwitch(1);
cb && cb();
console.log('video start load.');
});
video.addEventListener('ended', function () {
// 视频播放结束
console.log('video is over!');
handlePageSwitch(1);
});
}
本来视频播放也没啥问题,但老是提心吊胆,感觉早晚出bug。
方案二:借助video.js库
后来就换了种实现方式——借助video.js,重新写了下视频的逻辑:
var _initVideo = function () {
video.ready(function () {
videojs.log('Your player is ready!');
this.on('loadedmetadata', function () { // 因为iOS下video无法自动加载,因此没办法触发canplaythrough-当浏览器预计能够在不停下来进行缓冲的情况下持续播放指定的音频/视频时,会发生 canplaythrough 事件。
if(isVideoLoaded) return false;
setTimeout(function () {
console.log('loading@ '+100+'%');
$loadingProgress.text(100); // 进度置为100
isVideoLoaded = true;
setTimeout(function () {
_pageChange(1);
}, 1000); // 加载完成1s后进入下一页
}, 2000);
});
this.on('ended', function() {
videojs.log('Awww...over so soon?!');
setTimeout(function () {
_pageChange(1);
}, 2000); // 视频播放完成2s后进入下一页
});
});
}
发现两个问题:
video在iOS下无法自动加载(暂未解决,将触发条件移至loadedmetadata)
理论上,当音频/视频处于加载过程中时,会依次发生以下事件:loadstart、durationchange、loadedmetadata、loadeddata、progress、canplay、canplaythrough
部分机型(如红米、部分iPhone6)播放黑屏
发现是分辨率过高的问题,调整后可以播放。
还有一个棘手的问题是,我们的视频是需要在一个容器内播放的,如何将video定位到容器内需要考虑。
首先和设计师沟通,我们制作的视频尺寸是1:2(mix2录制),那给我的容器框也应该是1:2的。
其次因为要满足各手机尺寸自适应的问题,我需要将容器作为背景贴底居中显示,那此时我只需要将video外包裹一个div相对屏幕绝对居中。
如何保证div的比例,借助padding
相对于父容器width实现。
.video{
width: 67%; // 父容器width的67%
height: 0;
padding-bottom: 134%; // 父容器width的134%
}
总体下来,本以为2个小时就能搞定的,最终因为各方面因素做了持续10天左右(当然主要原因还是需求不断变更的问题,哈哈),但是这个“小”的H5,最终还是没能做到尽善尽美,优化之路还很长。
从3.11到4.1这21天里,我参加“在行一点(原分答)”里的一个“21天减肥实战营”课程,成功减重4kg,很多朋友咨询课程内容,就想着总结下来分享给大家。
其实之前我是认为自己和减肥根本挨不着边的,但是17年底公司员工体检查出自己居然已经是“三高”(高血压、高血脂、高尿酸)人群!去医院复诊时,医生明确说我这种属于由于快速增重引起的“代谢综合征”,再不控制这些指标就需要用药治疗。但是一旦用药以后就很难离开了,毕竟我还这么年轻啊。所以痛定思痛,毅然决定减肥。
课程内容分为两块:
今日食谱就是你每天需要吃什么、吃多少。
首先,给了食谱,意思就是说,大家在课程这段时间,吃什么、吃多少,就必须按照食谱来了。**食谱以内的东西,才可以吃**,但也必须按照食谱要求的**量**来吃。**食谱以外的东西什么都不能吃**。这样才能达到最好的效果。
食谱分成两套(一套自己做饭的食谱、一套无法自己做饭的食谱),每一套又按照性别、体重来区分出很多种,你是什么性别,什么体重区间,就选择相应的食谱。
最后,不管是用哪一套食谱,都要给食物称重,按照要求的量吃东西,这样才能获得最好的效果。我们提前要求大家买食品秤就是这个目的。
我的食谱里,只给出食材和每一种食材的分量,不会过多限制加工方法,给你充分的灵活性。但是减肥饮食,肯定不可能大油炒、或者油炸。建议清炒、水煮、蒸、烤、微波炉加工。
为了方便大家两套食谱以**天**为单位轮换使用,我尽可能的把每天的食谱都设计的差不多,不会有太多变化,这样你可以今天用这套,明天用另一套。但是在一天之内,不能混用两套食谱。
1、每口食物缓慢咀嚼39次再咽下,完全咽下上一口食物之后,再吃下一口;
2、咀嚼食物的时候放下所有餐具和手里的食物,只要嘴里有食物了,就清空两只手;
3、吃东西的时候集中注意力在咀嚼上,不要看电视、看视频、看书或者听音频节目;
4、小口吃饭,原来的一口分成2-3口;
5、每顿饭如果感觉吃饱了食物还没吃完,也不要再吃了。
布置的运动任务不需要去健身房,大概分为户外运动和家用运动,其中家用运动方式很多,我们还专门制作了GIF动图。而且各种运动方式之间很多都可以互换,也就是说,你完全可以足不出户完成运动任务。
每天运动的时间不长,但如果实在没有整块的时间,运动任务**也可以分开完成**。比如要求每天原地踏步30分钟,没有整块时间,也可以分成2个15分钟来完成。
NEAT就是平时日常活动的热量消耗。
小贴士:在开始前记录下你的身高、体重等信息,拍摄一张减肥前的全身照。
1、饮食中严格遵守食谱最底端的饮食技巧;
2、每天的食谱尽量设计的非常类似,所以两套食谱可以以“天”为单位替换使用;
3、食谱中所有食物,除了“糙米饭”之外都是生的重量;
4、所有食物的重量都是指可食用部分的重量;
5、不可以使用沙拉酱、肉酱以或任何有油的酱料;
6、每天总共吃的东西不超过食谱要求就可以,每天食谱当中各餐之间可以替换;
7、植物油的使用量必须符合食谱的要求,一小勺是指一小咖啡勺(约5g);
8、在油的使用上,如果有橄榄油和亚麻籽油更好,没有的话也可以换成其它植物油;
9、进餐时间没有严格要求;
10、食材加工方式不做具体要求(植物油有限制,炸是不可能的),建议水煮、蒸、无油烤、清炒、煲汤等;
11、盐、酱油等调味料没有严格限制,但不建议使用太多;
12、上午加餐时间为上午两餐之间居中的时间点,下午加餐时间则是下午两餐之间的居中时间点;
13、任何蜂蜜、白糖、红糖、黑糖等都不可以吃;
14、注意区别“鸡蛋”和“蛋清”。鸡蛋指全蛋,蛋清只是鸡蛋清,不包括蛋黄;
15、肉类的选择本身是低脂肪,但买的时候也要二次把关,保证所有肉类都是瘦肉,有肥肉的不可以吃;
16、喝水不限制,各种不添加奶、糖的自己泡的茶都可以喝,咖啡只可以和黑咖啡。但因为减肥时代谢产水会减少,所以适当多喝点水也没坏处;
17、“血制品”所有动物血都可以,如鸭血、猪血等。当然,加工方式要自己加工或者符合我们对植物油的要求;
18、血制品、纯瘦牛肉建议大家要每周吃1-2次,对补铁很有好处;
19、胡椒粉、辣椒粉可以少量用,不能太多;
20、酸奶要无其它食物添加的原味酸奶,酸奶一般都有一点糖,不是很多就可以;
21、牛奶是纯牛奶,脱脂全脂都可以。牛奶和酸奶可以等量替换。
1、经期不可以做跑、跳动作的运动,用原地踏步代替即可,其它运动、NEAT可以酌情完成;
2、运动和NEAT都可以分开完成,一天加在一起总时间达到要求即可。但一次性完成更好,因为分开完成更容易给自己“通融”;
3、NEAT可以充分利用自己的生活、工作中的碎片时间。比如步行上下班、提前几站下车、站着看电视、站着看书、站着完成某些工作等等;
4、每日NEAT任务不可替换;
5、自己平时已有的运动可以用来代替作业里的运动,多运动没关系,不要少于作业的强度和时间要求即可;
6、瑜伽的种类很多,热量消耗不好衡量,所以不能用瑜伽替换任何运动;
7、游泳要看游动的时间,如果多数时间是浮水,那么不算是有效的运动。
随着 H5 的普及,各种 CSS 3 动画及 JS 特效在网页中层出不穷,PC端载入速度还可以,移动端则相对要慢很多,如果图片或脚本没有完全载入,用户在操作中可能会发生各种问题。因此我们需要对数据载入进行侦测,以更加人性化的方式给用户展现。
以下总结实现的一些现成好看的资源和涉及的相关逻辑:
有趣的是,遇到的 Bug 并不是代码层面的,仔细想想还蛮有意思的。
TL;DR
我发现最近我在使用 VSCode 写样式的过程中,每次从小程序 IDE 里复制 {类名} 到编辑器时,总是会弹出终端,如下图所示:
第一反应是我不小心设置了快捷键,当按下 Ctrl+V
时会弹出终端,于是便打开 VSCode 快捷键的设置项,“不出所料”,果然被我找到了:
果断,“右键——删除键绑定”,重启,应该没问题了吧。
但是,还是出现上面的情况,这可急死我了,为啥改了快捷键还不生效呢???
后来,同事一看:
你这哪是 {快捷键} 的问题,明显是报错啦,你仔细看终端里提示的,有个 Error ... 你是不是装了啥插件?
我一想,好像真的安装了格式化代码的插件,但是怎么会这样呢?
而且找了安装的插件,也没有相关的设置呀,没办法,想想最近 VSCode 有次更新,之前的 IDE 配置都是通过 setting.json 文件来配置的。这次更新提供了 {可视化配置} 的方法。
当时我好想改了好多配置,没办法一个个找吧,果然找到了“罪魁祸首”:
原因就是,当我粘贴内容时,就会触发“格式化程序”自动格式化代码,但是这时候的语法肯定是不对的,所以就会报错。
为什么要记录下来呢?因为我发现自己好像不怎么会看报错信息,很多时候会忽略掉报错信息,记录下来就是强化自己这方面的意识。
--EOF--
前几天通过手机把电脑端的有道云笔记下线了,发现在电脑上没办法登录了,折腾了好几天,最终咨询了人工客服解决了(赞网易的用诉)。记录如下:
1.更换默认浏览器后,去登录页面点击微信按钮授权登录看看;
2.清除当前默认浏览器的缓存后,再去授权试试;
3.您确认网页版的笔记都正常后,完全云笔记退出软件(在系统托盘点击笔记图标右键选择退出),在>计算机本地搜“ynote”文件夹,待其搜索完毕,将“ynote”里的文件清空,再重启软件登录账户试试
后来问为啥不支持微信账号绑定邮箱登录,给出解释及解决方法:
我能不能微信登录后绑定个邮箱啥——这个问题 目前没有绑定功能呢 各个账户都是独立的 如果您怕授权登录账号有问题 可以用网页版的导入笔记功能 把微信里的数据迁移到新的账号里
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.