wiki
来源:互联网 发布:lua调用linux c库 编辑:程序博客网 时间:2024/06/11 21:03
gulp(注:mac系统中用到了管理员权限,故命令行开头要加上sudo)
ps:以下属于伪代码
1.常用步骤:
(1)因为gulp是基于node开发的,所以要先全局安装node,若已安装,则下一步:
全局安装gulpnpm install -g gulpgulp -h查看帮助
(2)在桌面新建文件夹gulp-jinge,并在该文件里打开终端,
gulp init生成package.json文件
接着按要求填写项目信息(version:0.0.1),再确认
npm insatll gulp --save-dev生成node_modules(含义:将gulp添加到pakage.json中,并作为项目的依赖)rm -rf node——modules删除node_moduls(因为以后可以通过命令行来直接安装插件)npm install下载项目所需的所有模块(因为package.json有gulp依赖,所以安装node_modules时,自动将gulp安装在node_modules下面)
(3)在项目根目录下创建gulpfile.js文件,在gulpfile.js中写入:
var gulp=require("gulp");gulp.task("hello",function () { console.log("hello");});
再在终端敲入gulp hello(格式:gulp+任务名),即在终端可看到打印了hello
(4)添加默认任务
若接着在gulpfile.js敲入 gulp.task(“default”,[‘hello’]);
则在终端敲gulp,即可执行默认任务hello
(5)gulp.src找出想要处理的文件,.pipe通过管道来找到对应功能,来处理文件,gulp.dest()将处理好的文件放到指定的地方
(6)将项目中的文件(index.html)复制到dest中
在gulpfile.js中敲入:
var gulp=require("gulp");gulp.task("copy-index",function () { return gulp.src("index.html") .pipe(gulp.dest("dist"))});
则在终端敲gulp copy-index,即可执行copy-index任务
(7)依赖任务(格式:gulp.task(任务名,[所依赖的任务列表],执行的任务))
gulp.task("build",["copy-index","images","data"],function () { console.log("编译成功!");});
则在终端敲gulp build,即先执行”copy-index”,”images”,”data”任务,在输出“编译成功”。
(8)监视
//监视gulp.task("watch",function () { gulp.watch("index.html",["copy-index"]);//index.html变化时,执行(实现了index.html和dest中的index.html保持同步) gulp.watch("images/**/*.{jpg,png}",["images"]); gulp.watch(["xml/*.xml","json/*.json","!json/secret-*.json"],["data"]);});
2.sass
(1)
npm install gulp-sass --save-dev安装gulp-sass文件(安装好后,在node_modules中可以看到)
在guipfile.js中敲入:
var sass=require("gulp-sass");gulp.task("sass",function () { return gulp.src("stylesheets/**/*.sass") .pipe(sass()) .pipe(minifyCSS()) .pipe(gulp.dest("dist/css"));});
则在终端敲gulp sass,即可将sass自动转化为css,并输出到dist/style.css中。
3.将项目放到本地服务器运行
(1)
npm install gulp-connect --save-dev将connect放到项目开发的依赖中
(2)在gulpfile.js中
var connect=require("gulp-connect");gulp.task("server",function(){ //服务器配置 connect.server({ //服务器根目录 root:"dist", })});
再在终端敲gulp server,即可将dist中文件在浏览器打开(在浏览器搜localhosst:8080)。
(3)实时刷新
gulpfile.js中,在server任务中添加
//启动实时刷新livereload:true
在copy-index任务中,添加
.pipe(connect.reload());
在尾部添加默认任务
gulp.task("default",["server","watch"]);
此时在终端先ctrl+c结束监视功能,再敲入gulp,便可实现开启服务器、监视、实时刷新功能。
4.文件合并
(1)
npm install gulp-concat --save dev\安装gulp-concat,并保存到依赖中
(2)在gulpfile.js中,敲入
var concat=require("gulp-concat");gulp.task("scripts",function () { return gulp.src(["javascripts/jquery.min.js","javascripts/modernizr.js"]) //合并 .pipe(concat("vendor.js")) .pipe(gulp.dest("dist/js"))});
终端输入gulp scripts
5。文件压缩
uglify:压缩js
(1)
npm install gulp-uglify --save-dev安装gulp-uglify,并保存到依赖中
(2)在gulpfile.js中,在scripts任务里添加
.pipe(uglify())
在头部添加
var uglify=require("gulp-uglify");
(3)终端输入gulp scripts
minify:压缩css
(1)
npm install gulp-minify-css --save-dev安装gulp-minifyCSS,并保存到依赖中
(2)在gulpfile.js中,在less任务里添加
.pipe(minifyCSS())
在头部添加
var minifyCSS=require("gulp-minify-css");
(3)终端输入gulp less,即可在dist得到编译后且最小化(如:去注释)的css.
imagemin:图片压缩
(1)
npm install gulp-imagemin --save-dev安装gulp-minifyCSS,并保存到依赖中
(2)在gulpfile.js中,在images任务里添加
.pipe(imagemin())
在头部添加
var imagemin=require("gulp-imagemin");
(3)终端输入gulp images,即可实现图片压缩.
所遇到的问题:
(1)安装pakage.json,却出现package-lock.json
解决:package-lock.json是保证你的package.json文件中所有的安装包版本和安装包版本所依赖的二级安装包的版本不会发生变化.我暂时删除了它
(2)ls无法查看文件列表
解决:可能原因权限有问题、windows自带防火墙拦截。暂时手动查看
(3)cnpm install gulp-sass时出现build error
解决:删除node_modules文件,删除package.json中gulp-sass和node-sass的信息,再重新执行npm install gulp-sass –save-dev
BFC+两栏布局(左固定,右自适应)
什么是BFC
BFC(Block formatting context)直译为”BFC”。它是一个独立的渲染区域,只有Block-level box参与, 它规定了内部的Block-level Box如何布局,并且与这个区域外部毫不相干。
通俗一点的理解就是——不论内部元素怎么闹腾,都不会影响到外部元素。
触发BFC的条件:
标题
- float的值不为none。
- overflow的值为auto,scroll或hidden。
- display的值为table-cell,table-caption,inline-block中任意一个。
- position的值不为relative和static。
BFC的规则:
- 内部的Box会在垂直方向,一个接一个地放置。
- Box垂直方向的距离由margin决定。属于同一个BFC的两个相邻Box的margin会发生重叠。
- 每个元素的margin box的左边, 与包含块border box的左边相接触(对于从左往右的格式化,否则相反)。即使存在浮动也是如此。
- BFC的区域不会与float box重叠。
- BFC就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。反之也如此。
- 计算BFC的高度时,浮动元素也参与计算。
两栏布局—左边固定宽度,右边自适应
方法一:
假如左边部分宽度为x,设置样式时给左边部分float:left,给右边部分设置一个值为x的margin-left。
html处的代码为:
<div id="left"></div><div id="right"></div>
css样式为:
html,body{ height: 100%; padding: 0; margin: 0; } #left{ width: 300px; height: 100%; background-color: red; float: left; } #right{ height: 100%; background-color: blue; margin-left:300px; }
注意清除浏览器默认样式。
方法二:
利用浮动,给左边float:left,右边不设置宽度,只设置overflow:auto。
html处的代码不变,css样式为:
html,body{ height: 100%; padding: 0; margin: 0; } #left{ width: 300px; height: 100%; background-color: red; float: left; } #right{ height: 100%; background-color: blue; overflow: hidden; }
我发现此处给right的overflow设置auto也可以实现两栏布局的效果。两者同样都是利用了BFC特性,给右边部分设置一个触发BFC的条件,使右边部分不会与float的左边部分发生重叠,从而达到我们想要的效果。
方法三
设左边部分的宽度为x,利用绝对定位。将左边部分使用绝对定位固定在左边,然后给右边部分设置一个值为x的margin-left。
需要注意的是这种方法当左边部分的高度大于右边部分的高度时,无法将父元素撑开,如果有底部元素,就会出现底部元素与左边元素重叠的现象。
html部分的代码为:
<div id="container"> <div id="left"> 左栏 </div> <div id="right"> 右栏 </div></div>
css部分的代码为:
body{ margin: 0;}#left{ top: 0; left: 0; width: 230px; height: 600px; background: #ccc; position: absolute;}#right{ height: 600px; margin-left: 230px; background: #0099ff;}
方法四
我打算把右边部分的宽度设为100%,然后设置float:right,最后把他向右移动310,以便于左边部分能挤上来。
但这么一来右边部分里面的内容也会跟着左移310,导致被遮住了,所以我们要把他重新挤出来。为了好挤,我用了一个额外的div包裹住内容,通过margin-right将内容挤到屏幕相应位置。
html部分的代码为:
<div id="wrap"> <div id="content">主体内容(左)</div> <div id="sidebar_wrap"> <div id="sidebar">边栏(右)</div> </div></div><div id="footer">底部</div>
css部分的代码为:
#content { width: 310px; float: left; background-color: red; } #sidebar_wrap{ margin-right: -310px; float:right; width: 100%; } #sidebar{ margin-right: 310px; background-color: blue; } #footer{ background-color: indianred; }
方法五
通过table和table-cell实现。
html部分的代码为:
<div id="wrap"> <div id="content" style="height:40px;">左固定区con</div> <div id="sidebar" style="height:100px;">右自适应区bar</div></div><div id="footer">后面的一个DIV,以确保前面的定位不会导致后面的变形</div>
css部分的代码为:
#wrap{ display: table; width: 100%; } #content,#sidebar{ display:table-cell; } #content{ width: 500px; background-color: red; } #sidebar{ background-color: blue; } #footer{ background-color: blueviolet; }
注意:1.ie7-不适应 2.不论如何设置左右部分各自高度,最终效果:左边高度一定等于右边高度
圣杯+双飞翼(三栏布局)
1.margin设为负值的作用
首先把文档流想象成水流可能更方便我们理解一些。
负边距对文档流控制的元素的作用是,会使它们在文档流中的位置发生偏移。这种偏移和因为相对定位而导致的偏移是不一样的,相对定位后,元素依旧占据它原来的空间,文档流后面的内容不会流上来,但因为负边距而导致的偏移会放弃之前所占据的空间,文档流后面的元素就会像水流一样随之流上来填充。
在文档流中,元素的最终边界是由margin决定的。
需要注意的是,文档流只能向左或向上流,即后面流向前面,不能逆向流。
个别情况下margin为负值可以增加元素宽度
这里说的个别情况是指该元素没有设定固定宽度的前提。我们清楚,当一个元素没有设定宽度的时候,父元素设定了宽度,它就会默认占满父元素,这个时候如果给该元素设定一个负的左右margin,设了多少,就会增加多少。
2.共同目的
三列布局,要求中间宽度自适应,左右宽度为定值的布局,且都是让浏览器将中间区块优先渲染。(定-自-定)
3.圣杯布局
实例
#contain{ padding-right: 120px; padding-left: 120px;}.main,.left,.right{ float: left;}.left,.right{ width: 120px;}.main{ width:100%;}.left{ position: relative; left: -120px; margin-left: -100%;}.right{ position: relative; left: 120px; margin-left: -120px;}
<div id="contain"> <div class="main">我是中间</div> <div class="left">我是左边</div> <div class="right">我是右边</div></div>
4.双飞翼布局
实例:
.box{ float: left;}.main-wrap{ width: 100%;}.main{ margin:0 300px 0 200px; height:400px; background: blue;}.left{ width: 200px; height: 400px; background: pink; margin-left: -100%;}.right{ width: 300px; height: 400px; background: grey; margin-left: -300px;}.box{ text-align: center; line-height: 400px; color: #fff;}
<div class="container"> <div class="main-wrap box"> <div class="main">我是中间</div> </div> <div class="left box">左边</div> <div class="right box">右边</div></div>
5.比较
同:两者解决问题的方案在前一半是相同的,也就是三栏全部float浮动,但是左右两栏加上负margin让它跟中间div并排,以形成三栏布局。
异:
圣杯布局: 将左右两个div采用相对定位配合left、right属性,以便左右两栏div移出后不遮挡中间div。
再将父元素设置了padding-left和padding-right,使左右两列分别流入到窗口中。
(注意:左列宽度<中间列宽度,如不满足,则在父元素padding-left时,三列无法处于同一行。)
双飞翼布局: 为了中间div内容不被遮挡,直接在中间列div内部创建一个子div,用于放置内容,在子div里用margin-left,margin-right为左右两栏div留出位置。
三栏布局@定-自-定
方法一:绝对定位法
原理:将左右两边使用absolute定位,因为绝对定位使其脱离文档流,后面的center会自然流动到他们下面,然后使用margin属性,留出左右元素的宽度,就可以使中间元素自适应屏幕宽度。
点评:该法布局的好处,三个div顺序可以任意改变。不足是因为绝对定位,所以如果页面上还有其他内容,top的值需要小心处理
css:
#left{ position: absolute; left: 0; top: 0; width: 200px; } #right{ position: absolute; right: 0; top: 0; width: 200px; } #center{ padding-left: 200px; padding-right: 200px; }
html:
<div id="left">左边内容</div><div id="right">右边内容</div><div id="center">中间内容</div>
方法二:自身浮动法
原理:对左右使用分别使用float:left和float:right,float使左右两个元素脱离文档流,中间元素正常在正常文档流中,使用margin指定左右外边距对其进行一个定位。
点评:好处是受外界影响小,但是不足是 三个元素的顺序,center一定要放在最后(原因:center占据文档流位置,所以一定要放在最后,左右两个元素位置没有关系。否则左右元素将会被挤到下一行)。当浏览器窗口很小的时候,右边元素会被挤到下一行。
css:
#left,#right{ width: 200px;height: 200px; background-color: #ccc; }#left{float: left;}#right{float: right;}#center{margin: 0 200px; height: 200px; background-color:pink;}
html:
<div id="left">左边内容</div><div id="right">右边内容</div><div id="center">中间内容</div>
方法三:flex
原理:在外围包裹一层div,设置为display:flex;中间设置flex:1;但是盒模型默认紧紧挨着,可以使用margin控制外边距。
#wrap{ width: 100%; display: flex; } #left,#right{ width: 200px; } #center{ flex:1; }
负margin的详细整理
给没有设置float的元素使用负margin时
- 给元素设置margin-left和margin-top上的负值x时,设置的哪个方向,就会使元素向那个方向偏移x的距离。
- 给元素设置margin-right/bottom上的负值x时,会将后续元素向自己这边拉动x的距离,即让文档流后面的内容向自己流x距离。并且在没有设定元素的宽度时,会增加元素宽度。利用负margin增加宽度就是这个原理。
负margin的作用
- 实现圣杯双飞翼布局
- 增加未设置宽度的元素的自身宽度
- 去掉浮动列表的右边框
- 和绝对定位一起实现水平垂直居中
- 去除列表最后一个li元素的border-bottom
因为圣杯双飞翼布局和增加元素自身宽度昨天已经整理过了。所以今天整理剩余的部分。
去掉浮动列表的右边框
这里有时候会采用给最右边的元素添加class,然后margin-right设置为0的方法,但是缺点是需要动态判断哪些元素是最右边的元素,列表里的内容是采用动态循环的方式显示,利用class使用margin-right:0的方式就不行了。这个时候如果给父元素添加一个负的margin-right就会好办很多啦,不需要动态判断到底哪些元素是最右边的。
代码如下:
html部分的代码为:
<div id="wrap"> <ul> <li>1</li> <li>2</li> <li>3</li> <li>4</li> <li>5</li> <li>6</li> </ul></div>
css样式为:
*{ margin: 0; padding: 0;}#wrap{ background-color: black; width: 320px; height: 210px; overflow: hidden;}ul{ zoom:1; margin-right: -10px; }li{ float: left; width: 100px; height: 100px; margin-right: 10px; margin-bottom: 10px; list-style: none; background-color: red;}
和定位一起实现水平垂直居中
这个比较简单好理解了。id为box的div样式如下:
#box{ width: 100px; height: 100px; background-color: brown; position: absolute; top: 50%; left: 50%; margin-top: -50px; margin-left: -50px;}
去除列表最后一个li元素的border-bottom
适用情况:当每个li都有boder-bottom,ul也有border的时候,最后一个li的border-bottom就会和ul的底边边框发生重叠,导致看上去不美观,这个时候给li设置一个等于border宽度的margin-bottom,就可以消除这种不美观的影响了。
当然这里也可以利用css3的选择器选择最后一个li,将它的border-bottom设置为0px来解决这种问题。两种方法都很简便。
html代码如下:
<ul id="box"> <li>1</li> <li>2</li> <li>3</li> <li>4</li> <li>5</li> <li>6</li></ul>
css 部分代码如下:
#box{ padding: 0; border: 5px solid grey; background-color: black; overflow:hidden;} #box li{ color: white; border-bottom: 5px solid grey; list-style: none; margin-bottom: -5px;}
由于第一个li的margin-bottom导致第二个li被上拉5px,第二个li最上面的5px高度覆盖在第一个li之上,但由于li的background-color:transparent,第一个元素的boder-bottom能够正常显示出来
由于最后一个li -5px的margin-bottom,所以它将覆盖在ul的border-bottom之上
适当调整border,便可以看出来,最后一个li的灰色border覆盖在ul的灰色border之上,为达到去除最后一个li的边框效果,必须保证二者颜色相近,或者给ul写个overflow:hidden
清除浮动
1. 清除浮动与闭合浮动
清除浮动:清除对应的单词是 clear,对应CSS中的属性是 clear:left | right | both | none;
例子:
.main{ float: left; width: 200px; height: 100px;}.side{ float: right; width: 300px; height: 120px;}.footer{ clear: both; height: 30px;}
<div class="main"></div><div class="side"></div><div class="footer"></div>
闭合浮动:更确切的含义是使浮动元素闭合,从而减少浮动带来的影响。
点评:其实我们想要达到的效果更确切地说是闭合浮动,而不是单纯的清除浮动,在footer上设置clear:both清除浮动并不能解决wrap高度塌陷的问题。
结论:用闭合浮动比清除浮动更加严谨,所以后文中统一称之为:闭合浮动。
2. 闭合浮动的原因
由于浮动元素脱离文档流的特性,导致本属于普通流中的元素浮动之后,包含框内部由于不存在其他普通流元素了,也就表现出高度为0(高度塌陷)。在实际布局中,往往这并不是我们所希望的,所以需要闭合浮动元素,使其包含框表现出正常的高度。
普通流:
文档流(document flow),普通文档流,标准中叫做普通流或者常规流(normal flow)。
浮动:
浮动的框可以左右移动,直至它的外边缘遇到包含框或者另一个浮动框的边缘。
浮动框不属于文档中的普通流。
当一个元素浮动之后,不会影响到块级框的布局而只会影响内联框(通常是文本)的排列,文档中的普通流就会表现得和浮动框不存在一样,当浮动框高度超出包含框的时候,也就会出现包含框不会自动伸高来闭合浮动元素(“高度塌陷”现象)。
顾名思义,就是漂浮于普通流之上,像浮云一样,但是只能左右浮动
3.闭合浮动的原理——了解 hasLayout 和 Block formatting contexts
1)添加额外标签
–通过在浮动元素末尾添加一个空的标签,例如
实例:
<div class="wrap"> <div class="main"></div> <div class="side"></div> <div style="clear:both;"></div></div>
.main{ float:left;}.side{float:right;}
点评:
优点:通俗易懂,容易掌握
缺点:可以想象通过此方法,会添加多少无意义的空标签,有违结构与表现的分离,在后期维护中将是噩梦,这是坚决不能忍受的,所以你看了这篇文章之后还是建议不要用了吧。
2)使用 br标签和其自身的 html属性
–这个方法有些小众,br 有 clear=“all | left | right | none” 属性
实例:
<div class="wrap" id="float2"> <div class="main left">.main{float:left;}</div> <div class="side left">.side{float:right;}</div> <br clear="all" /></div><div class="footer">.footer</div>
点评:
优点:比空标签方式语义稍强,代码量较少
缺点:同样有违结构与表现的分离,不推荐使用
3)父元素设置 overflow:hidden
通过设置父元素overflow值设置为hidden;在IE6中还需要触发 hasLayout ,例如 zoom:1;
<div class="wrap" id="float3" style="overflow:hidden; *zoom:1;"> <div class="main left">.main{float:left;}</div> <div class="side left">.side{float:right;}</div></div><div class="footer">.footer</div>
点评:
优点:不存在结构和语义化问题,代码量极少
缺点:
- 内容增多时候容易造成不会自动换行导致内容被隐藏掉,无法显示需要溢出的元素;
- 04年POPO就发现overflow:hidden会导致中键失效,这是我作为一个多标签浏览控所不能接受的。所以还是不要使用了
4)父元素设置 overflow:auto 属性;
同样IE6需要触发hasLayout,演示和3差不多
优点:
不存在结构和语义化问题,代码量极少
缺点:
多个嵌套后,firefox某些情况会造成内容全选;
IE中 mouseover造成宽度改变时会出现最外层模块有滚动条等,firefox早期版本会无故产生focus等,不要使用
5)父元素也设置浮动
优点:
不存在结构和语义化问题,代码量极少
缺点:
使得与父元素相邻的元素的布局会受到影响,不可能一直浮动到body,不推荐使用
6)父元素设置display:table
优点:结构语义化完全正确,代码量极少
缺点:盒模型属性已经改变,由此造成的一系列问题,得不偿失,不推荐使用
7)使用:after 伪元素
需要注意的是 :after是伪元素(Pseudo-Element),不是伪类(某些CSS手册里面称之为“伪对象”),很多闭合浮动大全之类的文章都称之为伪类,不过csser要严谨一点,这是一种态度。
由于IE6-7不支持:after,所以使用 zoom:1触发 hasLayout来兼容。
实例:
<div class="wrap"> <div class="left">我在左浮动</div> <div class="right">我在右浮动</div> </div>
.wrap{ border: 1px solid #000;}.left{ float: left; margin-left: -20px; background: red;}.right{ float: right;; background: blue;}.wrap:after{ content:".";//生成内容作为最后一个元素 /*至于content里面是点还是其他都是可以的,例如oocss里面就有经典的 content:"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",有些版本可能content 里面内容为空,并不推荐这样做,firefox直到7.0 content:”"时,仍然会产生额外的空隙;*/ display: block;//使生成的元素以块级元素显示,占满剩余空间 height: 0;//避免生成内容破坏原有布局的高度 visibility: hidden;//使生成的内容不可见,目的是:让可能被生成内容盖住的部分,能够进行点击和交互。 clear: both;}.clearfix{ *zoom: 1;//触发IE hasLayout。}
优点:结构和语义化完全正确,代码量居中
缺点:复用方式不当会造成代码量增加
小结
通过对比,我们不难发现,其实以上列举的方法,无非有两类:
其一,通过在浮动元素的末尾添加一个空元素,设置 clear:both属性,after伪元素其实也是通过 content 在元素的后面生成了内容为一个点的块级元素;
其二,通过设置父元素 overflow 或者display:table 属性来闭合浮动
原理:
触发BFC的条件
float 除了none以外的值
overflow 除了visible 以外的值(hidden,auto,scroll )
display (table-cell,table-caption,inline-block)
position(absolute,fixed)
fieldset元素
注意
display:table 本身并不会创建BFC,但是它会产生匿名框(anonymous boxes),而匿名框中的display:table-cell可以创建新的BFC。
触发块级格式化上下文的是匿名框,而不是display:table。
通过display:table和display:table-cell创建的BFC效果是不一样的。
回顾:
1.overflow:hidden/auto
清浮动原理:因为父元素创建了新的BFC.
2.关于ie
IE6-7的显示引擎使用的是一个称为布局(layout)的内部概念,由于这个显示引擎自身存在很多的缺陷,直接导致了IE6-7的很多显示bug
当我们说一个元素“得到 layout”,或者说一个元素“拥有 layout” 的时候,我们的意思是指它的微软专有属性 hasLayout.
为此被设为了 true 。IE6-7使用布局的概念来控制元素的尺寸和定位,那些拥有布局(haslayout)的元素负责本身及其子元素的尺寸设置和定位。
如果一个元素的 hasLayout为false,那么它的尺寸和位置由最近拥有布局的祖先元素控制。
3.触发hasLayout的条件:
position: absolute float: left|right display: inline-block width: 除 “auto” 外的任意值 height: 除 “auto” 外的任意值 (例如很多人闭合浮动会用到 height: 1% )zoom: 除 “normal” 外的任意值 (MSDN)
在 IE7 中,overflow 也变成了一个 layout 触发器:
overflow: hidden|scroll|auto ( 这个属性在ie7-没有触发 haslayout 的功能。 )
4.overflow-x|-y: hidden|scroll|auto 在ie7-中同样没有触发 haslayout 的功能)
综上:
在支持BFC的浏览器(IE8+,firefox,chrome,safari)通过创建新的BFC闭合浮动;
在不支持 BFC的浏览器 (IE6-7),通过触发 hasLayout 闭合浮动。
推荐闭合浮动方法
方法一
Unicode字符里有一个“零宽度空格”,也就是U+200B,这个字符本身是不可见的,所以我们完全可以省略掉
visibility:hidden了.clearfix:after {content:"200B"; display:block; height:0; clear:both; }.clearfix { *zoom:1; }.
方法二
该方法也不存在firefox中空隙的问题。
/* For modern browsers */.cf:before,.cf:after {content:"";display:table;}.cf:after { clear:both; }/* For IE 6/7 (trigger hasLayout) */.cf { zoom:1; }
块元素居中总结:
水平:
1.相对居中
2.绝对
3.给需要居中的元素添加:
display: inline-block;vertical-align: middle;
垂直:
1.绝对
水平+垂直:
1.绝对+margin负值
2.绝对+auto
3.绝对+transform反向偏移
#box{ width: 100px; height: 100px; background-color: red; position: absolute; top: 50%; left: 50%; transform:translate(-50%,-50%); -webkit-transform: translate(-50%, -50%); -ms-transform: translate(-50%, -50%);}
4.flex
justify-content: center;align-items: center
css3特性
1. 实现圆角border-radius
border-radius: 1-4个数字
参数
各种长度单位都可以:px,%,…%有时很方便但宽高不一致时不太好
1个:都一样
border-radius: 一样
2个:对角
border-radius: 左上&右下 右上&左下
3个:斜对角
border-radius: 左上 右上&左下 右下
4个:全部,顺时针
border-radius: 左上 右上 右下 左
border-radius: 1-4个数字 / 1-4个数字
说明:
前面代表水平,后面代表垂直。
不给“/”则水平和垂直一样,例如:border-radius: 10px/5px;
“/”前面以及后面的四个数字,分别按照TRBL排布,来对矩形的四个角单独设置其水平、垂直圆角半径。
兼容性
border-radius只有在以下版本的浏览器:Firefox4.0+、Safari5.0+、Google Chrome10.0+、Opera10.5+、IE9+支持border-radius标准语法格式,对于老版的浏览器,border-radius需要根据不同的浏览器内核添加不同的前缀,比说Mozilla内核需要加上“-moz”,而Webkit内核需要加上“-webkit”等。
例如:
moz-border-radius:Mozilla //(Firefox, Flock等浏览器)-webkit-border-radius://WebKit (Safari, Chrome等浏览器)border-radius://Opera浏览器,不支持ie<9
冷门使用
1.对于border-radius还有一个内半径和外半径的区别,它主要是元素 边框值较大时,效果就很明显,当我们border-radius半径值小于或等于border的厚度时,我们边框内部就不具有圆角效果。
.border-big { border: 15px solid green; border-radius: 15px;}
原因:border-radius的内径值是等于外径值减去边框厚度值,当他们的值为负时,内径默认为0,最前面讲border-radius取值时就说过其值不能为负值。同时也说明border-radius的内外曲线的圆心并不一定是一致的。只有当边框厚度为0时,我们内外曲线的圆心才会在同一位置。
2.如果角的两个相邻边有不同的宽度,那么这个角将会从宽的边平滑过度到窄的边。其中一条边甚至可以是0。相邻转角是由大向小转。
.demo { border-width: 10px 5px 20px 3px; border-radius: 30px;}
3.相邻两条边颜色和线条样式不同时,那么两条相邻边颜色和样式转变的中心点是在一个和两边宽度成正比的角上。比如,两条边宽度相同,这个点就是一个45°的角上,如果一条边是另外一条边的两倍,那么这个点就在一个30°的角上。界定这个转变的线就是连接在内外曲线上的两个点的直线。
下面是一个四边颜色不一样,宽度不一样的实例:
.demo { border-color: red green blue orange; border-width: 15px 30px 30px 80px; border-radius: 50px;}
4.table的样式属性border-collapse是collapse时,border-radius不能正常显示,只有border-collapse: separate;时才能正常显示。
table { border-collapse: collapse; border: 2px solid red; background: green; border-radius: 15px;}
盒阴影(box-shadow)
box-shadow:[inset] x y blur [spread] color
参数
[inset]:投影方式
inset:内投影
不写:外投影
x、y:阴影偏移(右下正)
blur:模糊半径
[spread]:扩展阴影半径
先扩展原有形状,再开始画阴影
Color:颜色
文字阴影(text-shadow)
text-shadow:x y blur color, …
参数
x 横向偏移(右下正,左上负)
y 纵向偏移
blur 模糊距离(颜色渐变边界线模糊)
color 阴影颜色
文本阴影如果加很多层,会很卡很卡很卡
文字阴影应用(1)
- 最简单用法
text-shadow:2px 2px 4px black
- 阴影叠加
text-shadow:2px 2px 0px red, 2px 2px 4px green;
先渲染后面的,再渲染前面的(前面覆盖后面)
- 几个好玩的例子
层叠:color:red; font-size:100px; font-weight:bold; text-shadow:2px 2px 0px white, 4px 4px 0px red;
光晕:color:white; font-size:100px; text-shadow:0 0 10px #fff, 0 0 20px #fff, 0 0 30px #fff, 0 0 40px #ff00de, 0 0 70px #ff00de, 0 0 80px #ff00de, 0 0 100px #ff00de, 0 0 150px #ff00de;
- 火焰文字:
text-shadow: 0 0 20px #fefcc9, 10px -10px 30px #feec85, -20px -20px 40px #ffae34, 20px -40px 50px #ec760c, -20px -60px 60px #cd4606, 0 -80px 70px #973716, 10px -90px 80px #451b0e; font-family:Verdana, Geneva, sans-serif; font-size:100px; font-weight:bold; color:white;
线性渐变(gradient)
兼容性
IE10+
chrome 26+,10.0 -webkit-
firefox 16+,3.6 -moz
safari 6.1+,5.1 -webkit
opera 12.1+,11.6 -o-
常见写法
- 写法1
background: linear-gradient(color-stop1, color-stop2, …);
默认to bottom
示例代码:
div { width: 800px; height: 500px; background: -webkit-linear-gradient(red, blue); background: -moz-linear-gradient(red, blue); background: -o-linear-gradient(red, blue); background: linear-gradient(red, blue);}
- 写法2
background: -webkit-linear-gradient( begin-direction, color-stop1,color-stop2, ...);background: -moz-linear-gradient(end-direction, color-stop1, color-stop2, ...);background: -o-linear-gradient(end-direction, color-stop1, color-stop2, ...);background: linear-gradient(to end-direction, color-stop1, color-stop2,...);
★【备注】:
浏览器为webkit内核,写开始点方向浏览器为moz和o内核,写结束点方向标准浏览器是to结束位置//标准浏览器:尊重w3c的web浏览器
示例代码:
div { width: 800px; height: 500px; background: -webkit-linear-gradient(left, red , blue); background: -moz-linear-gradient(right, red, blue); background: -o-linear-gradient(right, red, blue); background: linear-gradient(to right, red , blue);}
- 写法3
background:-webkit-linear-gradient(begin-level begin-vertical,color-stop1,color-stop2,...);background:-moz-linear-gradient(end-level end-vertical,color-stop1,color-stop2,...);background:-o-linear-gradient(end-level end-vertical,color-stop1,color-stop2,...);background:linear-gradient(to end-level end-vertical,color-stop1,color-stop2,...);
示例代码
div { width: 800px; height: 500px; background: -webkit-linear-gradient(left top, red, yellow, blue); background: -moz-linear-gradient(right bottom, red, yellow, blue); background: -o-linear-gradient(right bottom, red, yellow, blue); background: linear-gradient(to right bottom, red, yellow, blue);}
- 写法4(线性渐变,使用角度)
渐变方向与角度关系见上图(注意区分渐变方向、渐变界线)–渐变界限指产生渐变效果的某两种颜色的交线
示例代码:
div { width: 800px; height: 500px; background: -webkit-linear-gradient(135deg, red, yellow, blue); background: -moz-linear-gradient(135deg, red, yellow, blue); background: -o-linear-gradient(135deg, red, yellow, blue); background: linear-gradient(135deg, red, yellow, blue);}
- 写法5
background:linear-gradient(角度? color1 length|percentage,color2 length|percentage,…);
【备注】:颜色结点中
如果最后一个不写值,默认100%,
如果第一个不写值,默认0%
- 写法6(透明渐变)
div { width: 800px; height: 500px; background: -webkit-linear-gradient(90deg, rgba(255, 0, 0, 0), rgba(255, 0, 0, 1)); background: -moz-linear-gradient(90deg, rgba(255, 0, 0, 0), rgba(255, 0, 0, 1)); background: -o-linear-gradient(90deg, rgba(255, 0, 0, 0), rgba(255, 0, 0, 1)); background: linear-gradient(90deg, rgba(255, 0, 0, 0), rgba(255, 0, 0, 1));}
- 重复渐变
background:repeating-linear-gradient(角度?,color1 length|percentage,color2 length|percentage,...);
示例代码:
background: -webkit-repeating-linear-gradient(90deg, red 0%, blue 10%, red 20%);background: -moz-repeating-linear-gradient(90deg, red 0%, blue 10%, red 20%);background: -o-repeating-linear-gradient(90deg, red 0%, blue 10%, red 20%);background: repeating-linear-gradient(90deg, red 0%, blue 10%, red 20%);
径向渐变
从起点到终点,颜色从内向外进行圆形渐变(从中间向外拉)
语法
background:radial-gradient(center,shape size,start-color,...,last-color);
- 写法1
径向渐变-颜色结点均匀分布(默认)
background:radial-gradient(color-stop1,color-stop2,...);实例:
background: -webkit-radial-gradient(red, blue);background: -moz-radial-gradient(red, blue);background: -o-radial-gradient(red, blue);background: radial-gradient(red, blue);
写法2
颜色结点不均匀分布
background:radial-gradient(color1 length|percentage,color2 length|percentage,...);background: -webkit-radial-gradient(red 50%, blue 70%);background: -moz-radial-gradient(red 50%, blue 70%);background: -o-radial-gradient(red 50%, blue 70%);background: radial-gradient(red 50%, blue 70%);
- 写法3
径向渐变——设置形状
【语法】
background:radial-gradient(shape,color-stop1,color-stop2,...);
形状说明
circle——圆形ellipse——椭圆(默认)background: -webkit-radial-gradient(circle, red, blue);background: -moz-radial-gradient(circle, red, blue);background: -o-radial-gradient(circle, red, blue);background: radial-gradient(circle, red, blue);
写法4
径向渐变——尺寸大小关键字
【语法】
background:radial-gradient(size,color-stop,color-stop2,…);
【关键字说明】
closest-side:最近边//指定:径向渐变的半径长度为从圆心到离圆心最近的边farthest-side:最远边//指定:径向渐变的半径长度为从圆心到离圆心最远的边。closest-corner:最近角farthest-corner:最远角
transform
1.Transform:(css3 2D 转换)
注意:这些效果叠加时,中间用空格隔开
作用:能够对元素进行移动、缩放、转动、拉长、拉伸
转换:使元素改变形状、尺寸、位置的一种效果
2.Transform:
2D的转换方法:
- rotate
设置元素顺时针旋转的角度,用法是:transform: rotate(x);
参数x必须是以deg结尾的角度数或0,可为负数表示反向。
说明:
围绕x或y轴旋转,属于3d变换;围绕z轴,属于2d变换.
围绕x、y轴,正值:向屏幕里
围绕z轴,正顺负逆。
- scale
设置元素放大或缩小的倍数,用法包括:
transform: scale(a); 元素x和y方向均缩放a倍transform: scale(a, b); 元素x方向缩放a倍,y方向缩放b倍transform: scaleX(a); 元素x方向缩放a倍,y方向不变transform: scaleY(b); 元素y方向缩放b倍,x方向不变
- translate()
通过translate()方法,元素从其当前位置移动,根据给定的left(x坐标)和top(y坐标)位置.(参数为负数时,反方向移动物体,其基点默认为元素中心点,也可以根据transform-origin进行改变基点)
translate 设置元素的位移,用法为:transform: translate(a, b); 元素x方向位移a,y方向位移btransform: translateX(a); 元素x方向位移a,y方向不变transform: translateY(b); 元素y方向位移b,x方向不变
skew()
设置元素倾斜的角度,用法包括:
transform: skew(a, b);元素x方向逆时针倾斜角度a,y方向顺时针倾斜角度btransform: skewX(a); 元素x方向逆时针倾斜角度a,y方向不变transform: skewY(b); 元素y方向顺时针倾斜角度b,x方向不变以上的参数均必须是以deg结尾的角度数或0,可为负数表示反向。
- matrix
设置元素的变形矩阵,因为矩阵变形过于复杂,暂略。
HTML5新特性
- Javacript 客户端保存数据
WebStorage
1.Cookie
cookie 是存储于访问者的计算机中的变量。每当同一台计算机通过浏览器请求某个页面时,就会发送这个 cookie。你可以使用 JavaScript 来创建和取回 cookie 的值。当浏览器从web的回应到页面请求中接收到一个 Set-Cookie 头部时Cookies便创建了:浏览器接收到表明回应成功的 HTTP 200 代码,以及回应的内容类型。同时也接收到了Set-Cookie头部,并创建了一个的cookie。
cookie在浏览器和服务器间来回传递,主要应用场景:
保持登录保持上次查看的页面浏览计数广告追踪购物车的状态保持
2.Cookie的缺陷
数据大小
:作为存储容器,cookie的大小限制在4KB左右这是非常糟糕的,尤其对于现在复杂的业务逻辑需求,4KB的容量除了存储一些配置字段和简单单值信息,对于绝大部分开发者来说真的不知指望什么了。
安全性问题
:由于在HTTP请求中的cookie是明文
传递的(HTTPS不是),带来的安全性问题还是很大的。除非在你给定的时间前刷新,否则cookie将在这以后无效并被浏览器移除。如果它没有被终止,在将来所有的该网站的请求中都将携带类似的信息头部,所以他们很容易受到安全问题攻击影响,例如关键攻击载体的CSRF(Cross Site Request Forgery),XSS(Cross Site Scripting Attacks) 以及 Session Hijacking 。一个用功且专业的开发者也许不会把很多安全细节信息放在cookie中,或者实现一系列的方法来减轻可能的这些形式的攻击。
网络负担
:我们知道cookie会被附加在每个HTTP请求中,在HttpRequest 和HttpResponse的header中都是要被传输的,所以无形中增加了一些不必要的流量损失。
3.webstorage是什么?
WebStorage是HTML5中本地存储的解决方案之一,在HTML5的WebStorage概念引入之前除去IE User Data、Flash Cookie、Google Gears等看名字就不懂的解决方案,浏览器兼容的本地存储方案只有使用cookie。
但WebStorage并不是为了取代cookie而制定的标准,cookie作为HTTP协议的一部分用来处理客户端和服务器之间的通信是不可或缺的。WebStorage的意图在于解决本来不应该cookie做,却不得不用cookie的本地存储。
早些时候,本地存储使用的是cookies。
但是Web 存储需要更加的安全与快速.这些数据不会被保存在服务器上,但是这些数据只用于用户请求网站数据上.它也可以存储大量的数据,而不影响网站的性能。数据以 键/值
对存在, web网页的数据只允许该网页访问使用.
4.分类
WebStorage提供两种类型的API:localStorage和sessionStorage,两者的区别看名字就有大概了解,localStorage在本地永久性存储数据,除非显式将其删除或清空,sessionStorage存储的数据只在会话期间有效,关闭浏览器则自动删除。两个对象都有共同的API。
5.方法
interface Storage { readonly attribute unsigned long length; DOMString? key(unsigned long index); getter DOMString getItem(DOMString key); setter creator void setItem(DOMString key, DOMString value); deleter void removeItem(DOMString key); void clear();};ls.length:唯一的属性,只读,用来获取storage内的键值对数量。ls.key(i):根据index获取storage的键名。ls.getItem(key):根据key获取storage内的对应value。ls.setItem(‘name’,’bryon’):为storage内添加键值对。ls.removeItem(‘name’):根据键名,删除键值对。ls.clear():清空storage对象PS:ls是localstorage对象名。
键值对通常以字符串存储,你可以按自己的需要转换该格式。
以localStorage为例,
<script type="text/javascript">var ls=localStorage;console.log(ls.length);//0ls.setItem('name','Byron');ls.setItem('age','24');console.log(ls.length);//2for(var i=0;i<ls.length;i++){ //遍历localStorage var key=ls.key(i); //获取键 console.log(key+' : '+ls.getItem(key));} /* name : Byron age : 24 */ls.removeItem('age'); //删除age键值对for(var i=0;i<ls.length;i++){var key=ls.key(i);console.log(key+' : '+ls.getItem(key));}/* name : Byron */ls.clear(); //清空storage对象console.log(ls.length); //0</script> var person = {'name': 'rainman', 'age': 24};localStorage.setItem("me", JSON.stringify(person));console.log(JSON.parse(localStorage.getItem('me')).name); // 'rainman'/** * JSON.stringify,将JSON数据转化为字符串 * JSON.stringify({'name': 'fred', 'age': 24}); // '{"name":"fred","age":24}' * JSON.stringify(['a', 'b', 'c']); // '["a","b","c"]' * JSON.parse,反解JSON.stringify * JSON.parse('["a","b","c"]') // ["a","b","c"] */
事件:
同时HTML5规定了一个storage事件,在WebStorage发生变化的时候触发,可以用此监视不同页面对storage的修改
interface StorageEvent : Event { readonly attribute DOMString key; readonly attribute DOMString? oldValue; readonly attribute DOMString? newValue; readonly attribute DOMString url; readonly attribute Storage? storageArea;};
以下均为属性:
key:键值对的键oldValue:修改之前的valuenewValue:修改之后的valueurl:触发改动的页面urlStorageArea:发生改变的Storage在使用 web 存储前,应检查浏览器是否支持 localStorage 和sessionStorageif(typeof(Storage)!=="undefined") { // 是的! 支持 localStorage sessionStorage 对象! // 一些代码..... }else { // 抱歉! 不支持 web 存储。 }
localStorage实例
<script> function clickCounter() { if(typeof(Storage)!=="undefined") { if (localStorage.clickcount) { localStorage.clickcount=Number(localStorage.clickcount)+1; } else { localStorage.clickcount=1; } document.getElementById("result").innerHTML=" 你已经点击了按钮 " + localStorage.clickcount + " 次 "; } else { document.getElementById("result").innerHTML="对不起,您的浏览器不支持 web 存储。"; } } </script> </head> <body> <p><button onclick="clickCounter()" type="button">点我!</button></p> <div id="result"></div> <p>点击该按钮查看计数器的增加。</p> <p>关闭浏览器选项卡(或窗口),重新打开此页面,计数器将继续计数(不是重置)。</p> </body> </html>
sessionStorage实例
<script> function clickCounter() { if(typeof(Storage)!=="undefined") { if (sessionStorage.clickcount) { sessionStorage.clickcount=Number(sessionStorage.clickcount)+1; } else { sessionStorage.clickcount=1; } document.getElementById("result").innerHTML="在这个会话中你已经点击了该按钮 " + sessionStorage.clickcount + " 次 "; } else { document.getElementById("result").innerHTML="抱歉,您的浏览器不支持 web 存储"; } } </script> </head> <body> <p><button onclick="clickCounter()" type="button">点我!</button></p> <div id="result"></div> <p>点击该按钮查看计数器的增加。</p> <p>关闭浏览器选项卡(或窗口),重新打开此页面,计数器将继续计数(不是重置)。</p> </body> </html>
sessionstorage生命周期以页面session为界(不能自己设置过期时间)。只要浏览器保持打开,页面刷新和重载都会保持 sessionStorage 内容,关闭的时候清除。新建标签页和新建窗口则属于新的 session。
与cookie的异同
cookie数据始终在同源的http请求中携带(即使不需要),即cookie在浏览器和服务器间
来回传递。而sessionStorage和localStorage不会自动把数据发给服务器,仅在本地保存
。cookie数据还有路径
(path)的概念,可以限制cookie只属于某个路径下;
存储大小限制也不同,cookie数据不能超过4k,同时因为每次http请求都会携带cookie,所以cookie只适合保存很小的数据,如会话标识。sessionStorage和localStorage虽然也有存储大小的限制,但比cookie大得多,可以达到5M;
数据有效期不同,sessionStorage:仅在当前浏览器窗口关闭前有效
,自然也就不可能持久保持;localStorage:始终有效
,窗口或浏览器关闭也一直保存,因此用作持久数据永久有效;cookie只在设置的cookie过期时间之前一直有效,在这以后无效并被浏览器移除。
作用域不同,sessionStorage不在不同的浏览器窗口中共享,即使是同一个页面;localStorage在所有同源窗口中都是共享的;cookie也是在所有同源窗口中都是共享的;
WebStorage支持事件通知机制,可以将数据更新的通知发送给监听者,即storage事件。WebStorage 的 api 接口使用更方便。
需要注意的地方
1.浏览器兼容性,这个几乎是所有HTML5新特性中最容易实施的了,因为IE8+的浏览器都支持,在IE7、IE6中可以使用IE User Data实现。
2.由于localStorage和sessionStorage都是对象,所以我们也可以通过”.key”或”[key]”的方式获取、修改键值对,但不推荐这么做 。
localStorage.userName='Frank';console.log(localStorage['userName']);
3.虽然localStorage存储在本地,但不同的浏览器存储存储数据是独立的,所以在Chrome上存储的localStorage在FireFox上是获取不到的。
4.localStorage和sessionStorage只能存储字符串类型,对于复杂的对象可以使用ECMAScript提供的JSON对象的stringify和parse来处理,低版本IE可以使用json2.js
5.除了控制台,Chrome还为本地存储提供了非常直观的显示方式,调试的时候很方便
localstorage过期设置
我们都知道localStorage不主动删除,永远不会销毁,那么如何设置localStorage的过期时间呢,今天我们来一起尝试一下!
<script type="text/javascript"> //封装过期控制代码 function set(key,value){ var curTime = new Date().getTime(); localStorage.setItem(key,JSON.stringify({data:value,time:curTime})); } function get(key,exp){ var data = localStorage.getItem(key); var dataObj = JSON.parse(data); if (new Date().getTime() - dataObj.time>exp) { console.log('信息已过期'); //alert("信息已过期") }else{ //console.log("data="+dataObj.data); //console.log(JSON.parse(dataObj.data)); var dataObjDatatoJson = JSON.parse(dataObj.data) return dataObjDatatoJson; } } </script>
使用场景:
1.利用本地数据,减少网络传输
2.弱网络环境下,高延迟,低带宽,尽量把数据本地化
使用方法:
<script>window.onload = function(){ var Ipt = document.getElementById('input1'); var value = '{"name":"和派孔明","Age":"18","address":"陆家嘴金融城"}'; set('information',value); Ipt.onclick = function(){ //var dataObjData=get('information',1000);//过期时间为1秒,正常情况下,你点击的时候已经过期 //var dataObjData=get('information',1000*60);//过期时间为1分钟 //var dataObjData=get('information',1000*60*60);//过期时间为1小时 //var Obj=get('information',1000*60*60*24);//过期时间为24小时 var dataObjData=get('information',1000*60*60*24*7);//过期时间为1周 console.log(dataObjData || null); if (dataObjData!="" && dataObjData!=null) { console.log("姓名:"+dataObjData.name); console.log("年龄:"+dataObjData.Age ); console.log("地址:"+dataObjData.Age ); }else{ alert("获取的信息已经过期"); } }}</script>
常见效果
1.自制单选框:label标签是和input配合使用的,运行代码,点击label中内容,使得input获得焦点
2.小球惯性运动
利用拖拽原理
当鼠标在小球上落下时,记录旧的事件位置old
当鼠标在窗口不断被拖动时,不断记录新的事件位置new
当鼠标抬起时,计算old、new差值(即为小球运动方向),然后不断利用差值给小球赋值
3.图片拉近效果
鼠标移入transform:scale(1.2);
4.立体盒子
7.11记
7.13记 瀑布流(图片等宽不等高)
JS实现
单行排列不下时,接着最矮的那一列向下排列检测是否具有滚动条加载事件,
最后元素.offsetTop+最后元素.offsetHeight/2<页面滚走距离+窗口高度注意:开始时,页面的图片必须足够,才能触发onscroll加载
JQuery
注意:
jQuery方法,只给jQuery对象用.get(i)/[i] //把jq对象转化为原生width//仅仅获取所定义的宽度outerwidth//获取width+padding+borderjQuery获取宽度,对象.width(无单位的数值)**jQuery支持连缀,隐式迭代**
理解:对象后可跟多个方法 query选择器,得到的是这个对象的集合,所以不用再循环去找了
jQuqry遍历:
类数组.each(function(index,value){})注:value指每一个参与遍历的dom对象
jQuery.inArray( value, array [, fromIndex ] )
描述: 在数组中查找指定值并返回它的索引(如果没有找到,则返回-1)。
JS入门
标签(空格分隔): 自定义属性+数组,字符串方法+ajax
入门须知:
js概念:JavaScript原名叫LiveScript,是一种基于对象和事件驱动的客户端脚本语言,最初的设计是为了检验HTML表单输入的正确性。
完整的JavaScript是由3部分组成的:
- ECMAScript (当前学习版本5.1) 规定了JS的基本语法
- BOM 浏览器对象模型
- DOM 文档对象模型元素对象的属性操作是指操作元素的行间属性
操作元素的class不能用class,需要用className
使用cssText会先清空之前的所有样式,然后添加它的值
获取元素的颜色的属性值是不准确的
console.log(box.style.background);
获取图片的src属性的时候获取到的是绝对路径
console.log(img.src);
直接修改是可以写相对路径的
img.src = './img/2.jpg';
数组里面可以储存任意类型的数据
length 属性 代表数组的长度, 是可读可写的
模拟单选的思路:
(1) 清除所有,给当前添加
(2) 清除上一个,给当前添加(见.选项卡的应用)
(3) 使用开关效果
如果多个元素同时需要切换某种状态,需要为每个元素添加自己的用来控制状态的属性,不能共用一个。
2.字符串方法
str.length //获取它的长度str.charAt(2) //获取传入下标处的字符,如果不传默认为”0“,超出时返回空str.charCodeAt(2) //获取传入下标处的Unicode 编码,不写默认为'0'String.fromCharCode()//传入的编码值返回相应的字符,可以传多个编码,用','分割(静态方法,String不能少)str.indexOf() //获取()中在字符串中第一次出现的位置(可以一次找多个字符),未找到返回-1,第二个参数为开始位置(负数和不写默认是从0开始)lastIndexOf() //获取()中在字符串中最后出现的位置(可以一次找多个字符),未找到返回-1,第二个参数为开始位置(负数是从0开始,不写默认从最后开始找)str.substring()//截取从第一个参数到第二个参数(不包括第二个参数)的字符串,一个参数时截取到最后,会自动检测两个参数大小,按照从小到大的顺序截取,不写和负数默认从0开始 左闭右开区间str.slice() //与上面方法类似,但是不会自动检测参数大小,负数从后向前找(最后一个为-1) 必须从左到右的截取str.split('.') //以.分割整个字符串并输出一个数组,即使什么参数都不传也会变成一个数据的数组,因此如果想把一个字符串每个字符分割,就传一个空字符串'',(注意:如果分隔符在左右,也会生成个空在数组里),第二个可选参数限制生成数组的长度(剩下的会被舍去) 替换或者删除子串str.toUpperCase() //把内容转换成大写str.toLowerCase() //把内容转换成小写
数组方法
arr.join(‘’)
//split的反义词,把arr数组里的内容用‘’连接成字符串,生成一个所有数组组成的字符串,‘’里写什么每个数据中间就会用什么连接arr.push()/arr.unshift()//给数组向后/前添加数据,这两个方法的返回值是新数组的长度,ie8以下不支持unshift的返回值
arr.pop()/arr.shift()//把数组最后/最前的数据删除,这两个方法的返回值是被删除的数据
splice(a,b,c)//作用:删除、替换、添加
删除:splice(a,b)a是删除第一项序号,b表示删除的数量
替换:splice(a,b,c)a是替换的起点项,b表示删除的数量,c及c后面表示要加什么数据或多个数据
添加:splice(a,0,c)a是添加的起点项,0表示不删除,c及c后面表示要加什么数据或多个数据 加在下标a之前
只有删除的时候会有返回值(是被删除的数据)数组sort
作用:排序
默认机制按照字符串把内容按照顺序排列(Unicode 编码),因此排序数字时会出错
sort()的比较函数,就是把一个写好的函数作为一个参数数组concat
作用:连接数组
arr1.concat(arr2);和之前的两个数组没有关系,可以传多个参数,把多个数组连接一起- 数组reverse
arr1.reverse();把数组变成倒序
AJAX(注意简写)
例子:见分页效果
1.异步请求的核心:XHR(进行数据的异步交换)
2.HTTP是无状态协议,即不建立持久的连接。–无记忆
3.一个完整的HTTP请求过程的七大步骤:
- 建立TCP连接
- 浏览器向服务器发送请求命令
- 浏览器发送请求头信息
- 服务器应答
- 服务器发送应答头信息
- 服务器给浏览器发送数据
- 服务器关闭TCP连接
4.HTTP请求有以下组成:
- 方法、动作。例如:get/post
- url:请求地址
- 请求头(包含客户端环境信息,身份验证信息等)
- 请求体(请求正文,包含客户提交的查询字符串、表单信息等)
5.概念
1. get:获取或查询信息;通过url传递参数;限制发送的信息数量在2000个左右
–安全(发送的信息对任何人可见,通过键值对显示在url中 优点:通过书签url保存页面)
幂等(查询内容,不会因为查询次数被改变)
2. post:给服务器发送信息;修改服务器上的资源;一般用于提交表单或修改删除;对发送信息数量无限制
–安全(发送的内容键值对,通常被嵌于HTTP请求体中)
6.HTTP响应由以下组成
- 状态码(文字和数字组成),显示请求成功或失败
- 响应头(例如:服务器类型,日期时间,内容类型,长度等)
- 响应体(响应正文)//字符串或html代码等
7.状态码(首位数字,定义了状态码类型) —方便调试
- 1xx:信息类,表示接受到浏览器请求,正在进一步的处理中
- 2xx:成功。表示请求被成功接收、理解和处理 例如:200(ok)
- 3xx:重定向。表示请求没有成功,客户必须采取进一步的动作
- 4xx:客户端(提交的请求)错误。 例如:404 not found(请求所引用的文档不存在)
- 5xx:服务器错误。表示服务器不能完成对请求的处理。 如:500
8.XHR发送请求
- open(method,url,async)
method:一般大写
url:相对文档的地址,或绝对地址
默认true(异步请求)
若将async设置为false,则相当于return false.(注:return false作用: 一.阻止浏览器默认行为 二.阻止冒泡)
- send(string)
若采用get,则string可以不写或null,因为它将拼在url中
若post,则一定写str(否则无意义)
- 思路
request.open("POST","create.php",true);request.setRequsetHeader("content-type","application/x-www-form-urlencoded");//声明文档类型request.send("name=王达&sex=男")
注意setHR位置,否则引起异常
resonseText获得字符串形式的响应数据
statue和statusText:以数字和文本形式返回HTTP状态码
getALLRH():获取所有的响应报头
getRH():查询相应中某个字段的值readyState
0:请求未初始化,open还未调用
1:服务器连接已建立,open已经调用了
2:请求已接收,即接收到头信息
3:请求处理中,即接收到响应主体
4:请求已完成,且响应已就绪,即响应完成
// JavaScript Documentfunction ajax(url,fnSucc,fnFaild){ //参数:1、连接服务器的地址;2、成功时函数;3、失败时函数 //1、创建Ajax对象(创建XHR对象) var oAjax = null; if(window.XMLHttpRequest){ oAjax = new XMLHttpRequest(); //ie6及以下不兼容 }else{ oAjax = new ActiveXObjext("Microsoft.XMLHTTP"); } //2、连接服务器 oAjax.open('GET',url,true); //3、发送请求 oAjax.send(); //4、接收服务器的返回 oAjax.onreadystatechange = function(){ if(oAjax.readyState ==4){ //完成 if(oAjax.status ==200){ //alert('成功: '+oAjax.responseText); fnSucc(oAjax.responseText); }else{ //alert('失败'); if(fnFaild){ fnFaild(); } } } }}
9.PHP测试页面
- PHP脚本以
跨域(扩展知识)
一个域名的组成:
协议、域名、端口、虚拟目录、文件目录
当协议、子域名、主域名、端口号中任意一个不同时,都算作不同域。
跨域:不同域之间互相请求资源
js出于安全考虑,不允许跨域调用其他页面的对象。
端口号默认80
注意区分HTTP和HTTPS
主域名确定,则它下面的子域名可以有多个,也可以多级。
例如:
主域名:abc.com
一级子域名:www.abc.com
二级子域名:xiantai.bbs.abc.com
三级子域名:haidian.beijing.bbs.abc.com
处理跨域方式(一):代理
所谓反向代理服务器,它是代理服务器中的一种。客户端直接发送请求给代理服务器,然后代理服务器会根据客户端的请求,从真实的资源服务器中获取资源返回给客户端。所以反向代理就隐藏了真实的服务器。利用这种特性,我们可以通过将其他域名的资源映射成自己的域名来规避开跨域问题。
处理跨域方式(二):JSONP
1.原理:ajax无法跨域是因为同源政策,但是带有src的标签(例如img、iframe、script)是不受该政策限制,故我们可以利用向页面动态添加script标签,来完成对跨域的访问
2.假设,我们源页面是在a.com,想要获取b.com的数据,我们可以动态插入
script = document.createElement('script');script.type = 'text/javascript';script.src = 'http://www.b.com/getdata?callback=demo';
这里,我们利用动态脚本的src属性,变相地发送了一个http://www.b.com/getdata?callback=demo的GET请求。这时候,b.com页面接受到这个请求时,如果没有JSONP,会正常返回json的数据结果,像这样:
{ msg: 'helloworld' }
而利用JSONP,服务端会接受这个callback参数,然后用这个参数值包装要返回的数据:
demo({msg: 'helloworld'});
这时候,如果a.com的页面上正好有一个demo的函数:
function demo(data) { console.log(data.msg);}
当远程数据一返回的时候,随着动态脚本的执行,这个demo函数就会被执行。
到这里,你应该能明白这个技术为什么叫JSONP了吧?就是因为使用这种技术服务器会接受回调函数名作为请求参数,并将JSON数据填充进回调函数中去。
3.特点:
优点:兼容性好
缺点:(1)只能get不能post,因为script引入的资源,参数全都显式地url里 (2)安全隐患,因为动态插入<sciprt>
本质是脚本注入
4.封装
jQuery
$.ajax({ url: "http://tonghuashuo.github.io/test/jsonp.txt", dataType: 'jsonp', jsonp: "callback", jsonpCallback: "dosomething"}).done(function(res) { console.log("success"); console.log(res);}).fail(function(res) { console.log("error"); console.log(res);});
实际发送出来的完整请求长这样: http://tonghuashuo.github.io/test/jsonp.txt?callback=dosomething&_=1471419449018。
,后面的随机字符串是jQuery加上的。
这里真正需要关心的参数有以下 3 个:
dataType: 'jsonp',用于表示这是一个 JSONP 请求。jsonp: 'callback',用于告知服务器根据这个参数获取回调函数的名称,通常约定就叫 callback。jsonpCallback: 'dosomething',回调函数的名称,也是前面callback参数的值。
其中jsonpCallback参数是可以省略的,jQuery 会自动生成一个随机字符串作为函数名,推荐这么做,以减少不必要的命名工作,同时排除潜在的安全隐患。
注意:省略jsonpCallback的同时,jsonp参数必须指明,不能为false。
js
<script> // 实现回调函数,这里没有了 jQuery 的封装,必须手动指定并实现 var dosomething = function(data){ console.log(data); }; // 提供 JSONP 服务的 URL 地址,查询字符串中加入 callback 指定回调函数 var url = "tonghuashuo.github.io/test/jsonp.txt?callback=docomething"; // 创建 <script> 标签,设置其 src 属性 var script = document.createElement('script'); script.setAttribute('src', url); // 把 <script> 标签加入 <body> 尾部,此时调用开始。 document.getElementsByTagName('body')[0].appendChild(script); // 因为目标 URL 是一个后台脚本,访问后会被执行,返回的 JSON 被包裹在回调函数中以字符串的形式被返回。 // 返回的字符串放入 <script> 中就成为了一个普通的函数调用,执行回调函数,返回的 JSON 数据作为实参被传给了回调函数。</script>
是不是觉得没看够,来点干货,2333…
(1)资源类型文件可以实现跨域:当前网页可以查看其他域的image标签、script脚本文件、css等,其中原理是利用src属性或link的href属性。(image标签、script脚本文件、css属于资源类型文件。)
举个例子: <script src="http://static.runoob.com/assets/vue/1.0.11/vue.min.js"></script>
常见的利用cdn引入库文件,便属于利用script标签实现跨域。
(2)利用高德地图举例:
(3)为何jsonp只能gt,不能post?
i>script跨域请求静态资源cdn,只能为get,不能为postii>当用户输入完url来访问网页时,便会对服务器发送请求,该请求属于通过get获取资源。若将资源替换成API(或说是回调),即可实现跨域。
处理跨域方式(三):XHR2
1.HTML5提供的XHR Level2已经实现了跨域访问,以及其他的新功能
2.IE10-独不支持XHR 2
3.在服务器端添加:
header('Access-Control-Origin:*');header('Access-Control-Allow-Methods:POST,GET');
4.并非所有浏览器都实现了XHR2规范,但所有浏览器都实现了它规定的部分内容。
处理跨域方式(四):web sockets
WebSocket protocol是HTML5一种新的协议。它实现了浏览器与服务器全双工通信,同时允许跨域通讯。
原生WebSocket API使用起来不太方便,我们使用Socket.io,它很好地封装了webSocket接口,提供了更简单、灵活的接口,也对不支持webSocket的浏览器提供了向下兼容。
1.)前端代码:
<div>user input:<input type="text"></div><script src="./socket.io.js"></script><script>var socket = io('http://www.domain2.com:8080');// 连接成功处理socket.on('connect', function() { // 监听服务端消息 socket.on('message', function(msg) { console.log('data from server: ---> ' + msg); }); // 监听服务端关闭 socket.on('disconnect', function() { console.log('Server socket has closed.'); });});document.getElementsByTagName('input')[0].onblur = function() { socket.send(this.value);};</script>
2.)Nodejs socket后台:
var http = require('http');var socket = require('socket.io');// 启http服务var server = http.createServer(function(req, res) { res.writeHead(200, { 'Content-type': 'text/html' }); res.end();});server.listen('8080');console.log('Server is running at port 8080...');// 监听socket连接socket.listen(server).on('connection', function(client) { // 接收信息 client.on('message', function(msg) { client.send('hello:' + msg); console.log('data from client: ---> ' + msg); }); // 断开处理 client.on('disconnect', function() { console.log('Client socket has closed.'); });});
处理跨域方式(五):CORS
阮一峰
1.在CORS中,浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。
2.浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。
必须同时满足以下两大条件,才属于简单请求。否则属于非简单请求。
(1) 请求方法是以下三种方法之一:
HEADGETPOST
(2)HTTP的头信息必须属于以下几种字段:
AcceptAccept-LanguageContent-LanguageLast-Event-IDContent-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
3.对于简单请求,浏览器直接发出CORS请求。具体来说,就是在头信息之中,增加一个Origin字段。下面是一个例子,浏览器发现这次跨源AJAX请求是简单请求,就自动在头信息之中,添加一个Origin字段。
GET /cors HTTP/1.1Origin: http://api.bob.comHost: api.alice.comAccept-Language: en-USConnection: keep-aliveUser-Agent: Mozilla/5.0...
4.非简单请求
(1)非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json。非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为”预检”请求(preflight)。
下面是这个”预检”请求的HTTP头信息。
OPTIONS /cors HTTP/1.1Origin: http://api.bob.comAccess-Control-Request-Method: PUTAccess-Control-Request-Headers: X-Custom-HeaderHost: api.alice.comAccept-Language: en-USConnection: keep-aliveUser-Agent: Mozilla/5.0...
“预检”请求用的请求方法是OPTIONS,表示这个请求是用来询问的。头信息里面,关键字段是Origin,表示请求来自哪个源。
(2)服务器收到”预检”请求以后,检查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段以后,确认允许跨源请求,就可以做出回应。
(3)一旦服务器通过了”预检”请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个Origin头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin头信息字段。
5.CORS比JSONP更强大JSONP只支持GET请求,CORS支持所有类型的HTTP请求;JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。
处理跨域方式(六):图像ping
图像ping是与服务器进行简单,单项的跨域通信的一种方式
var img=new Image();img.onload=img.onerror=function(){ alert("done")}img.src="http://www.example.com/test?name=sl";
处理跨域方式(七):jquery
只不过我们不需要手动的插入script标签以及定义回掉函数。jquery会自动生成一个全局函数来替换callback=?中的问号,之后获取到数据后又会自动销毁,实际上就是起一个临时代理函数的作用。
$.getJSON方法会自动判断是否跨域,不跨域的话,就调用普通的ajax方法;跨域的话,则会以异步加载js文件
的形式来调用jsonp的回调函数。
$.getJSON( "http://example.com/data.php?callback=?", function (jsondata){ // 回调事件});
处理跨域方式(八):iframe
只有在主域相同时,才能使用该方法
处理跨域方式(九):window.name+iframe
window对象有个name属性,该属性有个特征:即在一个窗口(window)的生命周期内,窗口载入的所有的页面都是共享一个window.name的,每个页面对window.name都有读写的权限,window.name是持久存在一个窗口载入过的所有页面中的,并不会因新页面的载入而进行重置。
处理跨域方式(十):window.postMesssage+iframe
postMessage是HTML5新增在window对象上的方法,目的是为了解决在父子页面上通信的问题。该技术有个专有名词:跨文档消息(cross-document messaging)。利用postMessage的特性可以实现较为安全可信的跨域通信。
postMessage方法接受两个参数:
message
: 要传递的对象,只支持字符串信息,因此如果需要发送对象,可以使用JSON.stringify和JSON.parse做处理 targetOrigin
: 目标域,需要注意的是协议,端口和主机名必须与要发送的消息的窗口一致。如果不想限定域,可以使用通配符“*”,但是从安全上考虑,不推荐这样做。
处理跨域方式(十一):Window.loacationhhash+iframe
location.href可以获取url中“#”后面的值,并且改变hash值不会导致页面刷新,所以可以利用hash值来进行数据的传递,当然数据量有限。
缺点:(1)数据直接暴漏在url中 (2)数据容量和类型都有限
处理跨域方式(十二):document.domain
如,来自www.a.com想要获取document.a.com中的数据。只要基础域名相同,便可以通过修改document.domain为基础域名的方式来进行通信,但是需要注意的是协议和端口也必须相同。
json和jsonp
1.AJAX以何种格式来交换数据?
解决方案:通过xml或自定义字符串来描述数据
跨域问题?
通过服务器端代理
2.用JSON来传数据,靠JSONP来跨域
json
1.一种数据描述格式,也是一种基于文本的数据交换方式
2.优点:
1、基于纯文本,跨平台传递极其简单; 2、Javascript原生支持,后台语言几乎全部支持; 3、轻量级数据格式,占用字符数量极少,特别适合互联网传递; 4、可读性较强,虽然比不上XML那么一目了然,但在合理的依次缩进之后还是很容易识别的; 5、容易编写和解析,当然前提是你要知道数据结构; JSON的缺点当然也有,但在作者看来实在是无关紧要的东西,所以不再单独说明。
3.规则:
JSON只有两种数据类型描述符,大括号{}和方括号[],其余英文冒号:是映射符,英文逗号,是分隔符,英文双引号”“是定义符。
大括号{}用来描述一组“不同类型的无序键值对集合”(每个键值对可以理解为OOP的属性描述),方括号[]用来描述一组“相同类型的有序数据集合”(可对应OOP的数组)。
- 上述两种集合中若有多个子项,则通过英文逗号,进行分隔。
- 键值对以英文冒号:进行分隔,并且建议键名都加上英文双引号””,以便于不同语言的解析。
JSON内部常用数据类型无非就是
字符串、数字、布尔、日期、null
这么几个,字符串必须用双引号引起来,其余的都不用建议:如果客户端没有按日期排序功能需求的话,那么把日期时间直接作为字符串传递就好,可以省去很多麻烦
4.jsonp的产生
(1)ajax直接请求普通文件存在“跨域无权限访问”的问题,即只要是跨域请求,一律不准,然而,Web页面上调用js文件时则不受是否跨域的影响(凡是拥有src属性的标签都拥有跨域能力,例如script、iframe);利用以上特点可判断:若想通过纯web端(除了ActiveX控件、服务端代理、属于HTML5之Websocket)实现跨域访问数据,可以在远程服务器上将数据装进js格式的文件里,供客户端调用和处理。
(2)解决方案:web客户端先调用服务器上动态生成的js文件(一般以json为后缀),当调用成功后,就可获得需要的数据,然后即可按照自己需求来处理数据。
注:服务器动态生成json文件,目地:将客户端需要的数据装进去
(3)为了便于客户端使用数据,逐渐形成了一种非正式传输协议,人们把它称作JSONP
特点:
允许用户先传递一个callback参数给服务端,然后服务端返回数据时会将这个callback参数作为函数名来包裹住JSON数据,这样客户端就可以随意定制自己的函数来自动处理返回数据了。
5.JSONP的客户端具体实现:
案例一:web页面无条件得执行js文件中的安全代码,即使跨域
远程
服务器remoteserver.com根目录下有个remote.js文件,代码如下:
alert('我是远程文件');
本地
服务器localserver.com下有个jsonp.html页面代码如下:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head><title></title><script type="text/javascript" src="http://remoteserver.com/remote.js"></script></head><body></body></html>
毫无疑问,页面将会弹出一个提示窗体,显示跨域调用成功。
案例二:在jsonp.html页面定义一个函数,然后在远程remote.js中传入数据进行调用
jsonp.html页面代码如下:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head> <title></title> <script type="text/javascript"> var localHandler = function(data){ alert('我是本地函数,可以被跨域的remote.js文件调用,远程js带来的数据是:' + data.result); }; </script> <script type="text/javascript" src="http://remoteserver.com/remote.js"></script></head><body></body></html>
remote.js文件代码如下:
localHandler({"result":"我是远程js带来的数据"});
结果:页面成功弹出提示窗口,显示本地函数被跨域的远程js调用成功,并且还接收到了远程js带来的数据。
方案三:调用者可以传一个参数过去告诉服务端“我想要一段调用XXX函数的js代码,请你返回给我”,于是服务器就可以按照客户端的需求来生成js脚本并响应了。
看jsonp.html页面的代码:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head> <title></title> <script type="text/javascript"> // 得到航班信息查询结果后的回调函数 //服务端返回数据时会将flightHandler参数作为函数名来包裹住JSON数据 var flightHandler = function(data){ alert('你查询的航班结果是:票价 ' + data.price + ' 元,' + '余票 ' + data.tickets + ' 张。'); }; // 提供jsonp服务的url地址(不管是什么类型的地址,最终生成的返回值都是一段javascript代码) var url = "http://flightQuery.com/jsonp/flightResult.aspx?code=CA1998&callback=flightHandler"; // 创建script标签,设置其属性 var script = document.createElement('script'); script.setAttribute('src', url); // 把script标签加入head,此时调用开始 document.getElementsByTagName('head')[0].appendChild(script); </script></head><body></body></html>
结果:不再直接把远程js文件写死,而是编码实现动态查询,而这也正是jsonp客户端实现的核心部分
分析:
调用的url中传递了一个code参数,告诉服务器我要查的是CA1998次航班的信息,而callback参数则告诉服务器,我的本地回调函数叫做flightHandler,所以请把查询结果传入这个函数中进行调用。 OK,服务器很聪明,这个叫做flightResult.aspx的页面生成了一段这样的代码提供给jsonp.html
方案四:jQuery如何实现jsonp调用
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head> <title>Untitled Page</title> <script type="text/javascript" src=jquery.min.js"></script> <script type="text/javascript"> jQuery(document).ready(function(){ $.ajax({ type: "get", async: false, url: "http://flightQuery.com/jsonp/flightResult.aspx?code=CA1998", dataType: "jsonp", jsonp: "callback",//传递给请求处理程序或页面的,用以获得jsonp回调函数名的参数名(一般默认为:callback) jsonpCallback:"flightHandler",//自定义的jsonp回调函数名称,默认为jQuery自动生成的随机函数名,也可以写"?",jQuery会自动为你处理数据 success: function(json){ alert('您查询到航班信息:票价: ' + json.price + ' 元,余票: ' + json.tickets + ' 张。'); }, error: function(){ alert('fail'); } }); }); </script> </head> <body> </body> </html>
分析:(jqury把jsonp归入了ajax,其实它们真的不是一回事儿),它自动帮你生成回调函数,并把数据取出来,供success属性方法来调用
服务器端实现原理:
类似于拼接字符串
flightHandler({ "code": "CA1998", "price": 1780, "tickets": 5});
我们看到,传递给flightHandler函数的是一个json,它描述了航班的基本信息。运行一下页面,成功弹出提示窗口,jsonp的执行全过程顺利完成!
6.补充:
ajax和jsonp其实本质上是不同的东西。ajax的核心是通过XmlHttpRequest获取非本页内容,而jsonp的核心则是动态添加
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Ajax</title></head><body> <script src="./js/$Ajax.js"></script> <script type="application/javascript"> window.onload = function () { // 该字段用于定义请求数据的条数 var data = { pageSize: 4 }; //初始化 $Ajax.init({ url: 'http://v2.sohu.com/public-api/headLines/home/hotAuthors', type: "GET", data: data, //用JSON来传数据,靠JSONP来跨域 dataType:"jsonp", success: function (res) { console.log(res); }, error: function () { console.log('请求数据失败!'); } }); }; </script></body></html>
$Ajax.js文件:
;(function () { var $Ajax = function (options) { this.config = { url: '', type: 'get', async: true, dataType: 'json', contentType: 'application/json', data: {} }; this.start(options); }; var xhr = null; $Ajax.init = function (options) { new $Ajax(options); }; $Ajax.prototype = { constructor: $Ajax, createXHR: function () { if(typeof XMLHttpRequest != 'undefined'){ return new XMLHttpRequest(); } else { throw new Error('No XHR object available!'); } }, start: function (options) { xhr = this.createXHR(); if(options.url){ this.config.url = options.url; } else { throw new Error("url cannot be null!"); } if(options.type){ this.config.type = options.type; } if(options.async){ this.config.async = options.async; } if(options.dataType){ this.config.dataType = options.dataType; } if(options.data){ this.config.data = options.data; } if(options.success){ this.config.success = options.success; } if(options.error){ this.config.error = options.error; } if(options.beforeSend){ options.beforeSend(); } var complete = function () { return new Promise(function (resolve, reject) { if(xhr.readyState == 4){ if((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){ resolve(options.success(xhr.responseText)); } } else { if(options.error){ resolve(options.error()); } else { throw new Error("Request was unsucessful:"+xhr.status); } } }); } if(this.config.dataType == 'json' || this.config.dataType == 'JSON'){ // 非跨域处理 if((this.config.type == 'get') || (this.config.type == 'GET')){ for(var item in this.config.data){ this.config.url = addURLParam(this.config.url, item, this.config.data[item]); } xhr.onreadystatechange = complete; xhr.open(this.config.type, this.config.url, this.config.async); xhr.send(null); } if((this.config.type == 'post') || (this.config.type == 'POST')){ xhr.addEventListener('readystatechange', complete, false); xhr.open(this.config.type, this.config.url, this.config.async); if(options.contentType){ this.config.contentType = options.contentType; } xhr.setRequestHeader('Content-Type', this.config.contentType); xhr.send(serialize(this.config.data)); } } else if (this.config.dataType == 'jsonp' || this.config.dataType == 'JSONP'){ // 跨域处理 if((this.config.type == 'get') || (this.config.type == 'GET')){ // jsonp只能进行get请求跨域 // 创建script标签 var cbName = 'callback'; var timer = null; var head = document.getElementsByTagName('head')[0]; var scriptTag = document.createElement('script'); this.config.callback = cbName; head.appendChild(scriptTag); // 创建jsonp的回调函数 window[cbName] = function (json) { head.removeChild(scriptTag); clearTimeout(timer); window[cbName] = null; options.success && options.success(json); }; // 超时处理 if(options.time){ timer = setTimeout(function () { head.removeChild(scriptTag); options.fail && options.fail({message: "Over time!"}); window[cbName] = null; }, options.time); } this.config.url = this.config.url + "?callback=" + cbName; for(var item in this.config.data){ this.config.url = addURLParam(this.config.url,item,this.config.data[item]); } scriptTag.src = this.config.url; } } else { throw new Error('dataType is error!'); } } }; function addURLParam(url, name, value) { url += (url.indexOf('?') == -1 ? '?' : '&'); url += encodeURIComponent(name) + '=' + encodeURIComponent(value); return url; } // 序列化函数 function serialize(data) { var str = ''; for(var item in data){ str += item + '=' + data[item] + '&'; } return str.slice(0, str.length - 1); } window.$Ajax = $Ajax;})();
jQuery选择器
1. 基础选择器
id选择器 –其返回值为Array。
$(“#div1”).css(“background”,”red”);
标签名选择器 –其返回值为Array。
$(“div”).css(“background”,”red”);
class选择器 –其返回值为Array。
(“.box”).css(“background”,”red”);
//不需遍历,取到所有.box元素,生成类数组
*选择器 –其返回值为Array。
匹配所有元素
selector1,selector2,selectorN选择器 –其返回值为Array。
将每一个选择器匹配到的元素合并后一起返回
2.层级选择器
- ancestor descendant选择器(其返回值为:Array
//第一个li内容jQuery(“li:first”).text();//最后一个li内容jQuery(“li:last”).text();//input未被选中的值 jQuery(“li input:not(:checked)”).val();//索引为偶数的lijQuery(“li:even”).text();//索引为奇数的li jQuery(“li:odd”).text();//索引大于2的li的内容 jQuery(“li:gt(2)”).text();//索引小于1的li的内容 jQuery(“li:lt(1)”).text();
4.内容过滤选择器
(1):contains选择器。
(2):empty选择器。
(3):has选择器。
(4):parent选择器。
//选中内容包含hyip的lijQuery(“li:contains(‘hyip’)”)//选中内容为空的li的后一个lijQuery(“li:empty+li”)//选中包含(嵌套)a标签的lijQuery(“li:has(a)”)
5.可见性过滤选择器
(1):hidden选择器。
(2):visible选择器。
//选中不可见的lijQuery(“li:hidden”).text();//选中可见的lijQuery(“li:visible”).text();
6.属性过滤选择器
(1) [attribute]选择器。
(2)[attribute=value]、[attribute!=value]选择器(此处包含两种)。
(3)[attribute^=value]、[attribute$=value]、[attribute*=value]选择器(此处包含三种)。
(4)[selector][selector2]选择器。
//name为hyipinvestjQuery(“input[name=’hyipinvest’]”)//name以hyip开始jQuery(“input[name^=’hyip’]”)//name以hyip结束jQuery(“input[name$=’hyip’]”)//name包含oojQuery(“input[name*=’oo’]”
7.子元素过滤选择器
(1):nth-child选择器。
(2):first-child、:last-child选择器(两种)。
(3):only-child选择器。
8.表单对象属性过滤选择器
表单属性过滤选择器(1):enabled、:disabled选择器。(2):checked选择器。(3):selected选择器。表单过滤选择器(1):input选择器。(2):text、:password选择器。(3):radio、:checkbox选择器。(4):submit、:image、:reset、:button、:file选择器。(5):hidden选择器。
———– 7.16
scss
CSS预处理
1.CSS 预处理器用一种专门的编程语言,进行 Web 页面样式设计,然后再编译成正常的 CSS 文件,以供项目使用。CSS 预处理器为 CSS 增加一些编程的特性,无需考虑浏览器的兼容性问题”
2.可以在 CSS 中使用变量、简单的逻辑程序、函数(如变量$color)等等在编程语言中的一些基本特性,可以让你的 CSS 更加简洁、适应性更强、可读性更佳,更易于代码的维护等
3.CSS 预处理器语言,比如说:
Sass(SCSS)
LESS
Stylus
Turbine
Swithch CSS
CSS Cacheer
DT CSS
什么是 Sass?
1.官方定义:Sass 是一门高于 CSS 的元语言,它能用来清晰地、结构化地描述文件样式,有着比普通 CSS 更加强大的功能。
Sass 能够提供更简洁、更优雅的语法,同时提供多种功能来创建可维护和管理的样式表。
2.Sass 是采用 Ruby 语言编写的一款 CSS 预处理语言,它诞生于2007年,是最大的成熟的 CSS 预处理语言。
3.为什么早期不如 LESS 普及?
scss的缩进式风格可以有效缩减代码量,强制规范编码风格;但它一方面并不为大多数程序接受,另一方面无法兼容已有的 CSS 代码。这也是 Sass 虽然出现得最早,但远不如 LESS 普及的原因
Sass 和 SCSS 有什么区别?
Sass 和 SCSS 其实是同一种东西,我们平时都称之为Sass,两者之间不同之处有以下两点:
文件扩展名不同,Sass是以“.sass”后缀为扩展名,而SCSS是以“.scss”后缀为扩展名
语法书写方式不同,Sass是以
严格的缩进
式语法规则来书写,`不带大括号({})和分号(;) 而 SCSS 的语法书写和我们的 CSS语法书写方式非常类似。
sass语法
SCSS 是 Sass 的新语法格式,从外形上来判断他和 CSS 长得几乎是一模一样,代码都包裹在一对大括号里,并且末尾结束处都有一个分号。其文件名格式常常以“.scss”为扩展名。
提示:
- 不管是 Sass 的语法格式还是 SCSS 的语法格式,他们的功能都是一样的,不同的是其书写格式和文件扩展名不同
- “.sass”只能使用 Sass 老语法规则(缩进规则),“.scss”使用的是 Sass 的新语法规则,也就是 SCSS 语法规则(类似 CSS 语法格式)。
- 后面的语法,都是scss语法
scss的编译
命令编译
GUI工具编译
自动化编译
scss命令编译
- 单文件编译:
sass <要编译的Sass文件路径>/style.scss:<要输出CSS文件路径>/style.css
多文件编译
(对整个项目所有 Sass 文件编译成 CSS 文件)
sass sass/:css/
表示将项目中“sass”文件夹中所有“.scss”(“.sass”)文件编译成“.css”文件,并且将这些 CSS 文件都放在项目中“css”文件夹中。
上述命令的缺点:编译Sass时,开启“watch”功能,这样只要你的代码进行任保修改,都能自动监测到代码的变化,并且给你直接编译出来:
sass –watch
<要编译的Sass文件路径>/style.scss:<要输出CSS文件路径>/style.css
可以理解为 将sass下的style.scss文件,编译成css文件夹下的style.css文件
注:查看scss编译参数
scss -h
实例:
假设我本地有一个项目,我要把项目中“bootstrap.scss”编译出“bootstrap.css”文件,并且将编译出来的文件放在“css”文件夹中,我就可以在我的命令终端中执行:
sass --watch sass/bootstrap.scss:css/bootstrap.css
一旦我的 bootstrap.scss 文件有任何修改,只要我重新保存了修改的文件,命令终端就能监测,并重新编译出文件
GUI 界面编译工具
目前较为流行的主要有:
Koala (http://koala-app.com/)
Compass.app(http://compass.kkbox.com/)
Scout(http://mhs.github.io/scout-app/)
CodeKit(https://incident57.com/codekit/index.html)
Prepros(https://prepros.io/)
相比之下,我比较推荐使用以下两个:
Koala (http://www.w3cplus.com/preprocessor/sass-gui-tool-koala.html)
CodeKit (http://www.w3cplus.com/preprocessor/sass-gui-tool-codekit.html)
sass自动化编译
1、Grunt 配置 Sass 编译的示例代码
module.exports = function(grunt) { grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), sass: { dist: { files: { 'style/style.css' : 'sass/style.scss' } } }, watch: { css: { files: '**/*.scss', tasks: ['sass'] } } }); grunt.loadNpmTasks('grunt-contrib-sass'); grunt.loadNpmTasks('grunt-contrib-watch'); grunt.registerTask('default',['watch']);}
2、Gulp 配置 Sass 编译的示例代码
var gulp = require('gulp');var sass = require('gulp-sass');gulp.task('sass', function () { gulp.src('./scss/*.scss') .pipe(sass()) .pipe(gulp.dest('./css'));});gulp.task('watch', function() { gulp.watch('scss/*.scss', ['sass']);});gulp.task('default', ['sass','watch']);
sass的编译错误
在创建 Sass 文件时,就需要将文件编码设置为“utf-8”。
建议在项目中文件命名或者文件目录命名不要使用中文字符。
sass不同样式风格的输出方法(scss->css)
嵌套输出方式 nested
展开输出方式 expanded
紧凑输出方式 compact
压缩输出方式 compressed
嵌套输出方式
在编译的时候带上参数“ –style nested”:
sass --watch test.scss:test.css --style nested
编译出来的 CSS 样式风格:
nav ul {
margin: 0;
padding: 0;
list-style: none; }
展开输出方式
在编译的时候带上参数“ –style expanded”:
sass --watch test.scss:test.css --style expanded
这个输出的 CSS 样式风格和 nested 类似,只是大括号在另起一行
编译出来:
nav ul {
margin: 0;
padding: 0;
list-style: none;
}
紧凑输出方式
在编译的时候带上参数“ –style compact”:
sass --watch test.scss:test.css --style compact
该方式适合那些喜欢单行 CSS 样式格式的朋友,编译后的代码如下:
nav ul { margin: 0; padding: 0; list-style: none; }
压缩输出方式 compressed
在编译的时候带上参数“ –style compressed”:
sass --watch test.scss:test.css --style compressed
压缩输出方式会去掉注释及空格。也就是压缩好的 CSS 代码样式风格:
nav ul{margin:0;padding:0;list-style:none}nav li{display:inline-block}
scss基础特性
声明变量
普通变量与默认变量
- 普通变量
定义之后可以在全局范围内使用。
$fontSize: 12px;body{ font-size:$fontSize;}
- 默认变量
sass 的默认变量仅需要在值后面加上 !default 即可。
$baseLineHeight:1.5 !default;body{ line-height: $baseLineHeight; }
sass 的默认变量一般是用来设置默认值,然后根据需求来覆盖的,覆盖的方式也很简单,只需要在默认变量之前
重新声明下变量即可。(默认变量的价值在进行组件化开发的时候会非常有用。)
$baseLineHeight: 2;$baseLineHeight: 1.5 !default;body{ line-height: $baseLineHeight; }
全局变量与局部变量
//SCSS$color: orange !default; //定义全局变量(在选择器、函数、混合宏...的外面定义的变量为全局变量).block { color: $color;//调用全局变量}em { $color: red;//定义局部变量 a { color: $color;//调用局部变量 }}span { color: $color;//调用全局变量}
概念:
1、全局变量就是定义在元素外面的变量
2、基本上,局部变量只会在局部范围内覆盖全局变量。
什么时候声明变量?
该值至少重复出现了两次;
该值至少可能会被更新一次;
该值所有的表现都与变量有关(非巧合)。
选择器嵌套
Sass 的嵌套分为三种:
选择器嵌套
属性嵌套
伪类嵌套
选择器嵌套
想选中 header 中的 a 标签,在写 CSS 会这样写:
nav a { color:red;}header nav a { color:green;}
那么在 Sass 中,就可以使用选择器的嵌套来实现:
nav { a { color: red; header & { color:green; } } }
属性嵌套
CSS 有一些属性前缀相同,只是后缀不一样,比如:border-top/border-right,与这个类似的还有 margin、padding、font 等属性。假设你的样式中用到了:
.box { border-top: 1px solid red; border-bottom: 1px solid green;}
在 Sass 中我们可以这样写:
.box { border: { top: 1px solid red; bottom: 1px solid green; }}
伪类嵌套
.clearfix{&:before,&:after { content:""; display: table; }&:after { clear:both; overflow: hidden; }}
编译出来的 CSS:
clearfix:before, .clearfix:after { content: ""; display: table;}.clearfix:after { clear: both; overflow: hidden;}
缺点:代码难以阅读; 语句冗长,引用频繁
混合宏
声明混合宏
1、不带参数混合宏:
在 Sass 中,使用“@mixin”来声明一个混合宏。如:
@mixin border-radius{ -webkit-border-radius: 5px; border-radius: 5px;}
其中 @mixin 是用来声明混合宏的关键词,有点类似 CSS 中的 @media、@font-face 一样。border-radius 是混合宏的名称。大括号里面是复用的样式代码。
2、带参数混合宏:
除了声明一个不带参数的混合宏之外,还可以在定义混合宏时带有参数,如:
@mixin border-radius($radius:5px){ -webkit-border-radius: $radius; border-radius: $radius;}
调用混合宏
例如在你的样式中定义了一个圆角的混合宏“border-radius”:
@mixin border-radius{ -webkit-border-radius: 3px; border-radius: 3px;}
在一个按钮中要调用定义好的混合宏“border-radius”,可以这样使用:
button { @include border-radius;}
这个时候编译出来的 CSS:
button { -webkit-border-radius: 3px; border-radius: 3px;}
混合宏的参数–传一个不带值的参数
声明:
@mixin border-radius(radius){
-webkit-border-radius:radius;
border-radius: $radius;
}
调用:
.box {
@include border-radius(3px);
}
混合宏的参数–传一个不带值的参数
@mixin center($width,$height){ width: $width; height: $height;}.box-center { @include center(500px,300px);}
sass扩展/继承
.btn { border: 1px solid #ccc; padding: 6px 10px; font-size: 14px;}.btn-primary { background-color: #f36; color: #fff; @extend .btn;}
占位符
这段代码未被调用,不占位
//SCSS%mt5 { margin-top: 5px;}%pt5{ padding-top: 5px;}
.btn { @extend %mt5; @extend %pt5;}.block { @extend %mt5; span { @extend %pt5; }}编译出来的CSS//CSS.btn, .block { margin-top: 5px;}.btn, .block span { padding-top: 5px;}
比较
[Sass]插值#{}
$properties: (margin, padding);@mixin set-value($side, $value) { @each $prop in $properties { #{$prop}-#{$side}: $value; }}.login-box { @include set-value(top, 14px);}
.login-box { margin-top: 14px; padding-top: 14px;}
注释
1、类似 CSS 的注释方式,使用 ”/* ”开头,结属使用 ”*/ ”(显示)
2、类似 JavaScript 的注释方式,使用“//”(不会显示)
sass数据类型
在 Sass 中包含以下几种数据类型:
数字: 如,1、 2、 13、 10px;
字符串:有引号字符串或无引号字符串,如,”foo”、 ‘bar’、 baz;
颜色:如,blue、 #04a3f9、 rgba(255,0,0,0.5);
布尔型:如,true、 false; 空值:如,null;
值列表:用空格或者逗号分开,如,1.5em 1em 0 2em 、 Helvetica, Arial, sans-serif。
注意:
SassScript 也支持其他 CSS 属性值(property value),比如 Unicode 范围,或 !important声明。然而,Sass不会特殊对待这些属性值,一律视为无引号字符串 (unquoted strings)。
sass字符串
SassScript 支持 CSS 的两种字符串类型:
- 有引号字符串 (quoted strings),如 “Lucida Grande” 、’http://sass-lang.com‘;
- 无引号字符串 (unquoted strings),如 sans-serifbold。
在编译 CSS 文件时不会改变其类型。
只有一种情况例外,使用#{}插值语句(interpolation)时,有引号字符串将被编译为无引号字符串
sass直列表
1.独立的值也被视为值列表——只包含一个值的值列表。
2.Sass列表函数(Sass list functions)赋予了值列表更多功能(Sass进级会有讲解):
nth函数(nth function) 可以直接访问值列表中的某一项;
join函数(join function) 可以将多个值列表连结在一起;
append函数(append function) 可以在值列表中添加值;
@each规则(@each rule) 则能够给值列表中的每个项目添加样式。
3.
值列表中可以再包含值列表,比如 1px 2px, 5px 6px 是包含 1px 2px 与 5px 6px
两个值列表的值列表。如果内外两层值列表使用相同的分隔方式,要用圆括号包裹内层,所以也可以写成 (1px 2px) (5px 6px)。当值列表被编译为 CSS 时,Sass 不会添加任何圆括号,因为 CSS 不允许这样做。(1px 2px) (5px 6px)与 1px 2px 5px 6px 在编译后的 CSS 文件中是一样的
它们在 Sass 文件中却有不同的意义,前者是包含两个值列表的值列表,而后者是包含四个值的值列表。
可以用 () 表示空的列表,这样不可以直接编译成 CSS,比如编译 font-family: ()时,Sass 将会报错。
如果值列表中包含空的值列表或空值,编译时将清除空值,比如 1px 2px () 3px 或 1px 2px null 3px。
scss的运算
1.加法 30px+20px(同类型)
2.减法 10px-2px(同类型)
3.乘法 两个值单位相同时,只需要为一个数值提供单位即可。(同类型)
4.除法
”/ ”符号被当作除法运算符时有以下几种情况:
• 如果数值或它的任意部分是存储在一个变量中或是函数的返回值。
• 如果数值被圆括号包围。
• 如果数值是另一个数学表达式的一部分。
5.
//SCSSp { font: 10px/8px; // 纯 CSS,不是除法运算 $width: 1000px; width: $width/2; // 使用了变量,是除法运算 width: round(1.5)/2; // 使用了函数,是除法运算 height: (500px/2); // 使用了圆括号,是除法运算 margin-left: 5px + 8px/2px; // 使用了加(+)号,是除法运算}
6.
width: (1000px / 100px);//得到无意义数值
7.所有算数运算都支持颜色值,并且是分段运算的。也就是说,红、绿和蓝各颜色分段单独进行运算。如:
p { color: #010203 + #040506;}
算数运算也能将数字和颜色值 一起运算,同样也是分段运算的。如:
p { color: #010203 * 2;}
7.17记
7.18记
排序算法集锦
冒泡排序
算法原理:利用相邻元素进行比较,使元素如同气泡一样不断移动最终使得最大的元素不断的向右移动直到适当的位置位置。针对所有的元素重复以上的步骤,除了最后一个。持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较
两两开始比较,从而将最大元素冒泡到最后;进而最后元素在每轮中不必参与比较
代码
function sort(arr){ for(var i=0;i<arr.length;i++){ for(var j=0;j<arr.length-i-1;i++){ if(arr[i]>arr[i+1]){ var t=arr[i]; arr[i]=arr[i+1]; arr[i+1]=t; } } }}
3.时间复杂度
:冒泡排序的最坏时间复杂度为O(n2)。
空间复杂度
:o(1)
4.是否借助外部空间的判断: 没有借助第二个数组。
5.稳定排序
6.代码优化:如果在哪一轮比较中,没有元素参与交换,则表示排序已经完成。跳出循环
function sort(arr){ for(var i=0;i<arr.length;i++){ var tag=1; for(var j=0;j<arr.length-i-1;j++){ if(arr[j]>arr[j+1]){ var t=arr[j]; arr[j]=arr[j+1]; arr[j+1]=t; tag=0; } } if(tag){ break; } } return arr;}时间复杂度o(n)~o(n2)
快速排序
1.快速排序是对冒泡排序的一种改进。它的基本思想是:通过一躺排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一不部分的所有数据都要小,然后再按次方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
**思路一** 假设要排序的数组是A[1]……A[N],首先任意选取一个数据(通常选用第一个数据)作为关键数据,然后将所有比它小的数都放到它前面,所有比它大的数都放到它后面,这个过程称为一躺快速排序。一躺快速排序的算法是:
1)、设置两个变量I、J,排序开始的时候I:=1,J:=N;
2)以第一个数组元素作为关键数据,赋值给X,即X:=A[1];
3)、从J开始向前搜索,即由后开始向前搜索(J:=J-1),找到第一个小于X的值,两者交换;
4)、从I开始向后搜索,即由前开始向后搜索(I:=I+1),找到第一个大于X的值,两者交换;
5)、重复第3、4步,直到I=J;
当一趟快速排序截止后,返回刚才的基准元素。然后把基准元素的左边数组和右边数组分别作为新数组,分别进行第一趟的排序算法。
代码:
function quickSort(arr,i,j){ if(i<j){ //传入基准角标,进行第一轮排序 var s=partition(arr,i,j); //0~基准-1 quickSort(arr,i,s-1); //基准+1~末元素 quickSort(arr,s+1,j); } return arr; function partition(arr,i,j){ var std=arr[i]; while(i<j){ //1.j--,找到后交换 while(i<j&&arr[j]>std){ j--; } swap(arr,i,j); //2.i++找到后交换 while(i<j&&arr[i]<std){ i++; } swap(arr,i,j); }//返回基准的角标 return i; } function swap(arr,i,j){ var temp=arr[i]; arr[i]=arr[j]; arr[j]=temp; } } var arr=[2,5,3,1,7,8,78,89]; quickSort(arr,0,arr.length-1); console.log(arr);
思路二
i>同时从头向后遍历i,丛尾向前遍历j;遇到顺序不对的i、j,停下来。
ii>如果i
function quickSort(arr,i,j){//arr 0 n-1 if(i<j){ var s=partition(arr,i,j); quickSort(arr,i,s-1); quickSort(arr,s+1,j); } function partition(arr,i,j){ var std=arr[i]; while(i<j){ while(i<j&&arr[j]>std){//先j-- j--; } while(i<j&&arr[i]<std){//再i-- i++; } swap(arr,i,j);//交换a[i]、a[j] } return i; } function swap(arr,i,j){ var temp=arr[i]; arr[i]=arr[j]; arr[j]=temp; } } var arr=[2,1,8,5,4,3,45,12,51]; quickSort(arr,0,arr.length-1); console.log(arr);
2.优化
将基准选在数组二分位置(分治递归)
代码:
、、、、、、、、、尚未写出来
思路:
1.子区间递归二分
2.子区间合并,并排序(将两个升序合并成一个升序)
三.最差情况
当数组为降序有序,则需要两两比较,类似于冒泡排序。时间复杂度o(n的平方)
四.针对思路二
时间复杂度:
最差o(n2) 平均o(nlog2n)
空间复杂度:
O(log2n)~O(n)
五.快排是极其不稳定的
分析:例如1,2,3,2,5 将第一个2选作基准,从后往前遍历j,则j停在第二个2. 此时将基准交换,即将两个2的位置互换。
直接选择
1.思想:
i>先把整个数组作为一个无序数组, 然后每次从无序数组中选出一个最小的元素,插入到有序区间的结尾
ii>等到无序区间只剩一个元素的时候,不需要比较,因为它就是最大元素。
2.代码
var arr=[3,2,5,8,4,7,6,9]; function selectionSort(arr) { var minIndex, temp; for (var i = 0; i < arr.length- 1; i++) { //外层循环控制趟数,最后一个数字不用排 minIndex = i; for (var j = i + 1; j < arr.length; j++) { //内层循环寻找最小数 if (arr[j] < arr[minIndex]) { // 寻找最小的数 minIndex = j; // 将最小数的索引保存 } } //内层循环结束之后minIndex为最小值,和i交换(i:无序区间第一个值) temp = arr[i]; arr[i] = arr[minIndex]; arr[minIndex] = temp; } return arr; } console.log(selectionSort(arr));
3.时间复杂度:
fn=1+2+….+(n-2)+(n-1)=n2/2-n+1/2 根据推导方法为O(n平方)
最佳情况:T(n) = O(n2) 最差情况:T(n) = O(n2) 平均情况:T(n) = O(n2)
4.稳定性:不稳定 举例:arr=[5,8,5,2,9]
直接插入
1.思想:
i>将数组第一个元素作为有序区间元素,将剩余元素作为无序区间元素
ii>然后每次取出无序区间的第一个值,从后往前遍历有序区间,把它插入到有序区间里合适的位置
2.代码
var arr=[3,2,5,8,4,7,6,9];
function insertSort(a){ var i; var j; var x; for(i=0;i<arr.length;i++){ x=a[i];// for(j=i-1;j>-1&&a[j]>x;a[j+1]=a[j],j--); for(j=i-1;j>-1;j--){ if(a[j]>x){ a[j+1]=a[j]; } else break; } a[j+1]=x; } return a; } arr=insertSort(arr); console.log(arr);
3.时间复杂度: 计算循环嵌套的执行次数 (这种情况相当于等差数列求和)
fn=n+(n-1)+…..+1=n2/2+n/2 按照法则复杂度为O(n平方)
最佳情况:输入数组按升序排列。T(n) = O(n)最坏情况:输入数组按降序排列。T(n) = O(n2)平均情况:T(n) = O(n2)
4.稳定性:稳定
归并排序
1.思想:
i>二分递归
ii>自底向上,合并排序(利用unshift)
2.代码
var arr=[3,2,5,8,4,7,6,9]; function mergeSort(arr) { // 采用自上而下的递归方法 var len = arr.length; if(len < 2) { return arr; } var middle = Math.floor(len / 2), left = arr.slice(0, middle),//数组截取,左闭右开 right = arr.slice(middle); //先不断调左二分递归,再不断调右二分递归。 最后将完全二分后的数组,传给merge return merge(mergeSort(left), mergeSort(right)); //合并两个有序数组 } function merge(left, right) { var result = []; //合并两个有序数组 while (left.length && right.length) { if (left[0] <= right[0]) {//若果 左数组第一个值<=右数组第一个值 //借助一个新的数组,删除方法的返回值刚好是被删除数组的第一个值 result.push(left.shift());//将左数组第一个值压入第三数组 } else { result.push(right.shift());//将右数组第一个值压入第三数组 } } //循环结束的结果有两种,一种是left先结束,一种是right先结束 while (left.length) //right先结束,将剩余元素全部加入到第三方 result.push(left.shift()); while (right.length) //left先结束,将剩余元素加入到第三方 result.push(right.shift()); return result; } alert(mergeSort(arr));
3.时间复杂度:o(nlog2n)
4.稳定性:稳定
5.缺点:需要辅助空间
点评:
数值较少:冒泡(稳定、简单)
时间性能好:快排
堆排序:节省空间,排序快;见堆浪费时间
JQ动画
animate() :
//第一个参数 : {} 运动的值和属性–》终点(必须参数)
//第二个参数 : 时间(运动快慢的) 默认 : 400–》开始到结束
//第三个参数 : 运动形式 只有两种运动形式 ( 默认 : swing(慢快慢) linear(匀速) )
//第四个参数 : 回调函数
$(function(){ $('#div1').click(function(){ $(this).animate({width : 300 , height : 300} , 4000 , 'linear',function(){alert(123);}); });});
$('#div1').stop(); //默认 : 只会阻止当前运动$('#div1').stop(true); //终止当前动画,并阻止后续的运动--暂停效果.$('#div1').stop(true,true);//可以立即停止到指定的目标点(让当前动画立即切换到终点,并终止后续动画)$('#div1').finish();//立即停止到所有动画指定的目标点if(!$(element).is(":animated")){} else{}//判断当前元素是否在执行jq动画animatedelay() 延时(隔多久之后,再执行).animate({width : 300 , height : 300} , 3000 , 'linear').delay(2000)
Velocity.js动画
1.$element.velocity(“scroll”, 1000);
//参数一:对象(json)或者命令(字符串)
1000:时间
这个方法将使浏览器滚动到选定的元素(jQuery对象,$element),过渡时间为1000ms。
仅使用jQuery实现这个功能也会很复杂,而且将用到多个函数操作。
Velocity允许你设置窗口滚动后元素上边缘距窗口上边缘的距离,你只需设置offset选项即可完成(向上为负,向下为正):
$element.velocity(“scroll”, { duration: 1000, offset: “-100px” });
实例:楼层监听
2.让动画回到执行前状态
$element.velocity({ width: "100px" }, 400); $element.velocity("reverse");
3.Velocity 接收一个或更多的参数,只有第一个参数是是强制要求的,它可以是一个命令(就像上面例子中的”scroll”),或者是由CSS属性值组成的对象(这些值是动画的目标值):
$element.velocity({ opacity: “0.5”, left: “500px”});
4.第二个参数是由动画附加选项组成的对象,比如过渡时长,张弛度,延时还有回调函数:
$element.velocity( { left: "500px" }, { delay: 1000, duration :500, easing:[ 1000, 40 ], complete:function(){alert(123)}});
easing
的默认值(可以系统写好,也可以自己添加)是swing
,duration的默认值是400ms,其他的属性则是可选的。
复合属性的写法:{paddingLeft:”10px”,paddingTop:”15px”,….}
5.
easing(缓动)的取值:
“ease-in-out” 动画一开始要逐渐加速最后要逐渐减速。
“ease-in” 动画在一开始就达到加速的目的,然后一直到动画结束
“ease-out”动画以恒定速度开始一段时间,然后在动画结束前减速
6.张尺度和摩擦系数
$element.velocity({ width: “100px”}, {easing:[ 500, 20 ]})
较高的张弛度将提高整体速度和动画的反弹力度,较小的摩擦系数将提高动画结
束时的速度。
7.开始结束
begin设置的函数会在动画开始前触发。与之相反,为complete设置的函数会在动画完成时调用。
8.loop(循环)
$("div").velocity({ property1: value1, property2: value2}, { /* Default options */ duration: 400, easing: "swing", complete: null, loop: false});
9.支持链式调用
10.支持颜色渐变(要求颜色是十六进制字符串)
$element.velocity({ borderColor: "#ffff00", //黄色 backgroundColorAlpha: 0.6,//背景颜色透明度变到60% colorBlue: 200 //rgb 方式 (rgb中的blue:200)});(过渡效果)
11.支持2d,3d的扩大,平移,旋转等
translateX: 从左向右沿x轴移动元素 (正:右)translateY: 从上到下沿y轴移动元素 (正:下)rotateZ: 关于z轴旋转元素 (2d旋转) (z:眼睛垂直看屏幕视线)rotateX: 关于x轴旋转元素(看起来由里向外) (3d旋转)rotateY: 关于y轴旋转元素(由左到右) (3d旋转)scaleX: 成倍数改变元素宽度 scaleY: 成倍数改变元素高度 如:$element.velocity({ rotateZ: “180deg", scaleX: 2.0});
13.停止+动画序列(PPT)
14.预定义动画(例如:fade等,见库)
总结
一.过渡动画
如何在项目中正确、熟练地应用transition动画?
第一步:在目标元素的样式声明中定义元素的初始状态,然后在同一声明中用 transition 属性配置动画的各种参数。
可定义的参数有:
- transition-property:规定对哪个属性进行过渡。
- transition-duration:定义过渡的时间,默认是0。
transition-timing-function:定义过渡动画的缓动效果,如淡入、淡出等。
linear规定以相同速度开始至结束的过渡效果(等于 cubic-bezier(0,0,1,1))。
ease(默认值)规定慢速开始,然后变快,然后慢速结束的过渡效果(cubic-bezier(0.25,0.1,0.25,1))。
ease-in 规定以慢速开始的过渡效果(等于 cubic-bezier(0.42,0,1,1))。
ease-out规定以慢速结束的过渡效果(等于 cubic-bezier(0,0,0.58,1))。
ease-in-out规定以慢速开始和结束的过渡效果(等于cubic-bezier(0.42,0,0.58,1))。
cubic-bezier(n,n,n,n) 在 cubic-bezier 函数中定义自己的值。可能的值是 0 至 1 之间的数值。
transition-delay:规定过渡效果的延迟时间,即在过了这个时间后才开始动画,默认是0。
注意:要在同一代码块中定义元素初始状态(样式),添加transition属性。
如果想要同时过渡多个属性,可以用逗号隔开。
div{transition:width 3s,height 3s; }
第二步:改变元素的状态。
为目标元素添加伪类或添加声明了最终状态的类
注意:单纯的代码不会触发任何过渡操作,需要通过用户的行为(如点击,悬浮等)触发。
可触发的方式有::hover :focus :checked 媒体查询触发 JavaScript触发。
img { height:15px; /*初始值*/ width:15px; transition:1s 1s height; /*过渡*/}img:hover { height:450px; /*最终值*/ width:450px;}
过渡动画的局限性
(1)transition需要事件触发,所以没法在网页加载时自动发生。
(2)transition是一次性的,不能重复发生,除非一再触发。
(3)transition只能定义开始状态和结束状态,不能定义中间状态,也就是说只有两个状态。
二、关键帧动画
第一步:通过类似Flash动画中的帧来声明一个动画。使用了一个关键字 @keyframes 来定义动画。具体格式为:
@keyframes 动画名称 { 时间点 {元素状态} 时间点 {元素状态} …}
一般来说,0%和100%这两个关键帧是必须要定义的。0%可以由from代替,100%可以由to代替。
第二步:在目标元素的样式声明中使用animation属性调用关键帧声明的动画。
可设置的参数:
animation-name:none为默认值,将没有任何动画效果,其可以用来覆盖任何动画。
animation-duration:默认值为0s,意味着动画周期为0s,也就是没有任何动画效果。
animation-timing-function:与transition-timing-function一样。
animation-delay:在开始执行动画时需要等待的时间。
animation-iteration-count:定义动画的播放次数,默认为1,如果为infinite,则无限次循环播放。
animation-direction:默认为nomal,每次循环都是向前播放,(0-100)。另一个值为alternate,动画播放为偶数次则向前播放,如果为基数词就反方向播放。
animation-state:默认为running,播放,paused,暂停。
animation-fill-mode:定义动画开始之前和结束之后发生的操作。
参数如下:
none(默认值),动画结束时回到动画没开始时的状态;forwards,动画结束后继续应用最后关键帧的位置,即保存在结束状态;backwards,让动画回到第一帧的状态;both:轮流应用forwards和backwards规则;
注意:
animation属性到目前为止得到了大多数浏览器的支持,但是,需要添加浏览器前缀!另外,@keyframes必须要加webkit前缀。
div:hover { -webkit-animation:1s changeColor; /*调用动画*/ animation:1s changeColor;}@-webkit-keyframes changeColor { /*声明动画*/ 0% {background:#c00;} 50%{background:orange;} 100%{background:yellowgreen;}}@keyframes changeColor { 0%{background:#c00;} 50%{background:orange;} 100%{background:yellowgreen;}}
区别
animation属性类似于transition,他们都是随着时间改变元素的属性值,其主要区别在于:
transition
需要触发一个事件才会随着时间改变其CSS属性;
animation
在不需要触发任何事件的情况下,也可以显式的随时间变化来改变元素CSS属性,达到一种动画的效果
三、animate.css动画库
大致效果有:
bounce(跳动)、flash(闪光)、pulse(脉冲)、rubberband(橡皮筋)、shake(抖动)、swing(摇摆)、wobble(摇摆不定)fade(淡入或淡出)flip(翻转)rotate(旋转)slide(滑动)zoom(放大或缩小)
如何使用gulp构建符合我们需求的animate.min.css?
第一步:将整个animate.css项目下载下来,作为生产环境的依赖:
npm install animate.css --save
第二步:进入animate.css项目根目录下:
$cd path/to/animate.css/
第三步:加载dev依赖:
npm install
第四步:根据实际需要修改animate-config.json文件:
例如:我们只需要这两个动画效果:bounceIn和bounceOut。
{ "bouncing_entrances": [ "bounceIn" ], "bouncing_exits": [ "bounceOut" ]}
最后一步:进入animate.css项目下,运行gulp任务:gulp default
,生成新的animate.min.css
覆盖默认的animate.min.css
。
(2)你想要哪个元素进行动画,就给那个元素添加上animated类 以及特定的动画类名,animated是每个要进行动画的元素都必须要添加的类。
<script>$("box").addClass('animated shake');</script>
运行一段时间后移除:
setTimeout(function(){ $('.box').removeClass('shake');},3000)
rem
再看看
1.概述:rem 的官方定义『The font size of the root element.』,即以根节点的字体大小作为基准值进行长度计算。一般认为网页中的根节点是 html 元素,所以采用的方式也是通过设置 html 元素的 font-size 来做屏幕适配。
即通过实际稿、设计稿的等比缩放来实现
采用等比缩放的方式 —— 获得目标屏幕宽度和设计稿宽度的比,作为 rem 的基值(缩放系数),设置为html标签的字体大小。
2.以设计稿的宽度为640px,即:designWidth = 640,同时设定在640px屏宽下 1rem=100px ,即:rem2px = 100。
- 方案一(不同屏宽下实现)
@media screen and (min-width: 320px) {html{font-size:50px;}} @media screen and (min-width: 360px) {html{font-size:56.25px;}} @media screen and (min-width: 375px) {html{font-size:58.59375px;}} @media screen and (min-width: 400px) {html{font-size:62.5px;}} @media screen and (min-width: 414px) {html{font-size:64.6875px;}} @media screen and (min-width: 440px) {html{font-size:68.75px;}} @media screen and (min-width: 480px) {html{font-size:75px;}} @media screen and (min-width: 520px) {html{font-size:81.25px;}} @media screen and (min-width: 560px) {html{font-size:87.5px;}} @media screen and (min-width: 600px) {html{font-size:93.75px;}} @media screen and (min-width: 640px) {html{font-size:100px;}} @media screen and (min-width: 680px) {html{font-size:106.25px;}} @media screen and (min-width: 720px) {html{font-size:112.5px;}} @media screen and (min-width: 760px) {html{font-size:118.75px;}} @media screen and (min-width: 800px) {html{font-size:125px;}} @media screen and (min-width: 960px) {html{font-size:150px;}}
- 方案二(html实际字体 = 默认字体16px * font-size)
@media screen and (min-width: 320px) {html{font-size:312.5%;}}@media screen and (min-width: 360px) {html{font-size:351.5625%;}}@media screen and (min-width: 375px) {html{font-size:366.211%;}}@media screen and (min-width: 400px) {html{font-size:390.625%;}}@media screen and (min-width: 414px) {html{font-size:404.2969%;}}@media screen and (min-width: 440px) {html{font-size:429.6875%;}}@media screen and (min-width: 480px) {html{font-size:468.75%;}}@media screen and (min-width: 520px) {html{font-size:507.8125%;}}@media screen and (min-width: 560px) {html{font-size:546.875%;}}@media screen and (min-width: 600px) {html{font-size:585.9375%;}}@media screen and (min-width: 640px) {html{font-size:625%;}}@media screen and (min-width: 680px) {html{font-size:664.0625%;}}@media screen and (min-width: 720px) {html{font-size:703.125%;}}@media screen and (min-width: 760px) {html{font-size:742.1875%;}}@media screen and (min-width: 800px) {html{font-size:781.25%;}}@media screen and (min-width: 960px) {html{font-size:937.5%;}}
例如:实际屏幕640px,实际字体=16px*625%=100px
方案三
var designWidth = 640, rem2px = 100; document.documentElement.style.fontSize = ((window.innerWidth / designWidth) * rem2px) + 'px';
思路:实际宽度/设计宽度 = 实际字体 / 100
- 方案四
var designWidth = 640, rem2px = 100;document.documentElement.style.fontSize = ((((window.innerWidth / designWidth) * rem2px) / 16) * 100) + '%';
思路:实际宽度 / 设计宽度= (16*实际字体) / 100
实际案例:
假设屏宽=360px,设定默认浏览器字体=18px,
方案三
则根字体=实际宽度 / 设计宽度 * 100=360/640*100=56.25px(目标字体)
设计后根字体=65px????
所以,目标 6.4*rem = 6.4 * 56.25=360px
然而,实际 6.4*rem = 6.4 * 65=414px
方案四
利用方案四公式,html font-size = 360/640*100/16 = 351.5625%
即:1rem = 1 * htmlFontSize * defaultFontSize = 351.5625% * 18px = 63.28125px
- 改进方案(动态获取浏览器字体大小,进而不需写死默认字体16px等)
var designWidth = 640, rem2px = 100;var h = document.getElementsByTagName('html')[0];//通过getComputedStyle(h, null).getPropertyValue('font-size')获取浏览器默认字体var htmlFontSize = parseFloat(window.getComputedStyle(h, null).getPropertyValue('font-size'));document.documentElement.style.fontSize = window.innerWidth / designWidth * rem2px / htmlFontSize * 100 + '%';
问题:
html的font-size使用的是: getPropertyValue(‘font-size’) ,而 1rem 使用的是 getPropertyValue(‘width’),偏差出在计算 font-size 的时候浏览器进行了四舍五入。????
- 再次改进
var designWidth = 640, rem2px = 100;//创建一个div元素,方便getComputedStyle(d, null)获取其宽度,假定宽度=1remvar d = window.document.createElement('div');d.style.width = '1rem'; //1rem=1 html font-sized.style.display = "none";//保证在页面不占位var head = window.document.getElementsByTagName('head')[0];head.appendChild(d);//插入头部,将不在页面显示var defaultFontSize = parseFloat(window.getComputedStyle(d, null).getPropertyValue('width'));//语法:parseInt( window.getComputedStyle(element,null)[attr]) )优点:可以获取元素的最终样式,包括浏览器的默认值,并且可被用于检测元素的样式(包括style内部样式和外部样式)注意:不能获取复合样式如background属性值,只能获取单一样式如background-color等;getComputedStyle获取的值是只读的;//style:只能获取行间样式d.remove();//获取默认字体后,将刚生成的div铲除掉document.documentElement.style.fontSize = window.innerWidth / designWidth * rem2px / defaultFontSize * 100 + '%'; //通过该语句,更改根节点字体大小rem
- 最终设计方案(考虑到了屏幕旋转问题)
function adapt(designWidth, rem2px){ var d = window.document.createElement('div'); d.style.width = '1rem'; d.style.display = "none"; var head = window.document.getElementsByTagName('head')[0]; head.appendChild(d); var defaultFontSize = parseFloat(window.getComputedStyle(d, null).getPropertyValue('width')); d.remove(); document.documentElement.style.fontSize = window.innerWidth / designWidth * rem2px / defaultFontSize * 100 + '%'; var st = document.createElement('style'); var portrait = "@media screen and (min-width: "+window.innerWidth+"px) {html{font-size:"+ ((window.innerWidth/(designWidth/rem2px)/defaultFontSize)*100) +"%;}}"; var landscape = "@media screen and (min-width: "+window.innerHeight+"px) {html{font-size:"+ ((window.innerHeight/(designWidth/rem2px)/defaultFontSize)*100) +"%;}}" st.innerHTML = portrait + landscape; head.appendChild(st); return defaultFontSize};var defaultFontSize = adapt(640, 100);
7.19记
this(默认ES6)
参考: 你不知道的javascript上卷
1.每一个函数的this是在调用时被绑定的,它取决于函数的调用位置。
寻找函数的调用位置:分析调用栈(就是为了到达当前执行位置所调用的所有函数)---具体实现:插入断点debugger;
2.绑定规则
1. 默认绑定
function foo(){ console.log(this.a);//全局变量a}var a=2;foo();//2
function foo(){ console.log(this.a); }var a=2;(function(){ 'use strict';//对于foo,非严格模式 foo();//2})();
function foo(){ 'use strict'; console.log(this.a);}var a=2;foo();//语法错误:this is undefined
非严格模式下,默认绑定到全局对象严格模式下,this会绑定到undefined注:这里的是否严格,针对的是函数体
2.隐式绑定
要考虑到调用位置是否有上下文对象,或者是被某个对象拥有或包含—–即对象里所包含的某个值,是引用类型(函数名),指向了一个具体的函数。
无论是直接在对象中定义,还是先定义再添加为引用属性,严格来讲,这个函数读不属于对象。
思考1:
function foo(){ console.log(this.a);//隐式指向obj}var obj={ a:2, foo:foo};obj.foo();//2
隐式绑定规则会把函数中的this,绑定到这个上下文对象。
思考2:
function foo(){ console.log(this.a);}var obj2={ a:42, foo:foo};var obj1={ a:2, obj2:obj2};obj1.obj2.foo();//42
对象属性引用链,只有最后一层在调用位置中起作用。
思考3:
function foo(){ comsole.log(this.a);}var obj={ a:2, foo:foo}var bar=obj.foo;//函数别名(bar 是obj.foo的一个引用)var a='global';//a是全局对象的属性bar();//"global"
注意存在函数引用时,引用的是函数本身,因此此时调用其实是一个不带修饰的函数调用,故应用了默认绑定。
思考4:
function foo(){ console.log(this.a);}function doFoo(fn){ //fn引用的是foo fn();//调用位置}var obj={ a:2, foo:foo}var a="golbal";//a是全局对象的属性doFoo(obj.foo);//"global"
上例中,obj.foo隐式指向obj;调用位置确定了它属于全局对象,使用默认绑定规则
存在参数传递时,若隐式绑定与调用位置存在冲突,以调用位置为主。
思考5:
function setTimeout(fn,delay){ //等待delay毫秒 fn();//调用位置}等效于:setTimeout(obj.foo,100)// obj.foo里,this指向全局
3.显式绑定
1.使用call和appl方法。他们的第一个参数是一个对象,接着在调用函数时将其绑定到this.因为可以直接指定this的绑定对象,故称之为显式绑定。
2.从this的绑定角度讲,call(..)和apply(..)是一样的,区别体现在其他参数上,暂不考虑这些。
3.装箱:调用call或apply时,如果你传入一个原始值(字符串、布尔值、数字)来当作this的绑定对象,这个原始值将被转化成它的对象形式。类似于new string(…)等。这被统称为“装箱”。
显示绑定的变种:
1.硬绑定
思考1:
function foo(){ console.log(thi.a);}var obj={ a:2}var bar=function(){//函数表达式 foo.call(obj);//强制把this绑定到obj}bar();//2setTimeout(bar,1000);//2//硬绑定的bar不可能再修改它的this指向bar.call(window);//2
上例,将foo硬绑定到obj,无论之后如何调用函数bar,他总会在obj上调用foo。因他是显示的强制绑定,故称它:硬绑定。
思考2:
function foo(sth){ console.log(this.a,sth); return this.a+sth;}//简单的辅助函数function bind(fn,obj){//bind(执行函数,要绑定的对象):确保函数使用指定this return function(){ return fn.apply(obj,arguments); }}var obj={ a:2}var bar=bind(obj,foo);var b=bar(3);//2 3 (将3传给执行函数)console.log(b);//5
上例:创建一个可以重复使用的辅助函数。
思考3:
function foo(sth){ console.log(this.a,sth); return this.a + sth;}var obj={ a:2}var bar=foo.bind(obj);var b=bar(3); //2 3 (将3传给执行函数)console.log(b);//5
上例:ES5
内置的方法:function.prototype.bind
2.API调用的上下文
第三方库许多函数,度提供了一个可选参数,被称为“上下文”,目的是确保回调函数使用指定this。
function foo(el){ console.log(el,this.id);}var obj={ id:"cat"}//调用foo时,把this绑定到obj[1,2,3].forEach(foo,obj);//1 cat 2 cat 3 cat
上例函数,实际通过call或apply方法实现了显示绑定。通过库函数,可减少代码量。
4.new绑定
1.使用new来调用,会自动执行下列的操作:
1,创建(或者说构造)一个全新的对象。2,这个新对象会被执行[Prototype]连接。3,这个新对象会绑定到函数调用的this4,如果函数没有返回其他对象,那么new表达式中的函数调用,会自动返回这个新对象。
function foo(a){ this.a=a;}var bar=new foo(2);console.log(bar.a);//2
使用new时,我们可以构造一个新对象,并把它绑定到函数调用的this上。
根据优先级判断this
按照下面的顺序来进行判断
(1)函数是否在new中调用(new绑定)?如果是的话 ,this绑定的是新创建的对象。
var bar=new foo();
(2)函数是否通过call、apply(显式绑定)或者硬绑定调用?如果是的话,this绑定的是指定的对象。
var bar=foo.call(obj2);
(3)函数是否在某个上下文对象中调用(或隐式绑定)?若果是的话,this绑定的是那个上下文对象。
var bar=obj1.foo();
(4)如果度不是的话,使用默认绑定。若果在严格模式下,就绑定到undefined,否则绑定到全局。
var bar=foo();
被忽略的this
1.
function foo(){ console.log(this.a);}var a=2;foo.call(null);//2
如果你把null或undefined作为this的绑定对象传入call、apply或者bind,这些值在调用时会被忽略,实际应用的是默认绑定规则。
2.
function foo(a,b){ console.log("a"+a,"b"+b);}//把数组展开成参数foo.apply(null,[2,3]);//a:2 b:3//使用bind(..)进行柯里化var bar=foo.bind(null,2);bar(3);// a:2 b:3
使用apply(..)来展开一个数组,并当作参数传入一个函数。类似,bind可以对参数进行柯里化(即传入一个参数后,返回传参后的数组,再传入一个参数)
上例1,2属于使用null来忽略this绑定,因其容易导致bug,故不建议。
3.推荐做法
function foo(a,b){ console.log("a"+a,"b"+b);}//创建一个安全隔离区对象var Φ=Object.create(null);//把数组展开成参数foo.apply(Φ,[2,3]);//a:2 b:3//使用bind初始化var bar=foo.bind(Φ,2);bar(3); a:2 ,b:3
此处使用Object.create(null),它和{}不同在于:它不会创建Object.prototype这个委托
使用变量名Φ优点:提高安全性(保护全局变量),提高代码可读性。
间接引用
function foo(){ console.log(this.a);}var a=2;var o={a:3,foo:foo};var p={a:4};o.foo();//3(p.foo=o.foo)//2赋值表达式p.foo=o.foo的返回值是目标函数的引用,因此调用位置是foo,这里应用默认绑定。
间接引用最容易在赋值时发生。
软绑定softBind(再看书例题??)
首先检查调用时的this,若this绑定到了全局对象或undefined,那就吧指定的默认对象绑定到this。
this词法
1.箭头函数=>的词法作用域:
function foo(){ //返回一个箭头函数 return (a)=>{ //this继承自foo() console.log(this.a); }}var obj1={ a:2}var obj2={ a:3}var bar=foo.call(obj1);bar.call(obj2);//2
2.箭头函数的绑定无法被修改
3.箭头函数最常用于回调函数中,如 时间处理器或定时器:
小结:ES6中的箭头函数并不使用四条标准的绑定规则,而是根据当前词法作用域来决定this。—箭头函数会继承外层函数的this绑定(无论this绑定到什么)。
这和ES6之前的self=this
机制相同。
this使用:完全采用this风格,必要时采用bind(..),尽量避免self=this和箭头函数。
面向对象
构造函数
类的继承
多态
超类
一.构造函数
1.定义:类构造函数属于类,通常和类同名。此外,构造函数可以通过new来构建一个新的类实例。
2.实例:
class coolguy{ spetrick=nothing coolguy(trick){ spetrick=trick } showoff(){ output("here is my trick",spetrick); }}//调用类的构造函数来生成一个coolguy实例joe=new coolguy('jump rope');joe.showoff();//这是我的绝技:跳绳
二.类的继承
1.继承与重写例子:
三。多态
1.相对多态:虽然任何层次都可以引用继承层次中中高层的方法(无论高层方法名和当前方法名是否相同),但我们并不想要访问绝对的继承层次(或类)。
2.在上面二中的实例,可用super来代替inherited,它的含义:超类,表示当前的父类或祖先类。
3.super功能:从子类的构造函数中通过super来调用父类的构造函数。
真正来说,构造函数属于类。
js中,“类”属于构造函数。(类似于Foo.prototype..这样的类型引用)
4.多态并不表示子类和父类有关联,子类得到的只是父类的一份副本。类的继承其实就是复制。
5.多重继承(js中不提供“多重继承”的功能)
四。混入
(一)显式混入
伪多态
1.实例:
2.显式多态:vehicle.drive.call(this)
相对多态:inherited:drive()
3.伪多态:在上例中,若car(子类)中没有的方法,在vehicle(父类)出现了,此时不需要方法多态,因为调用mixin(..)时会把父类方法复制到子类中
4.伪多态缺点:代码复杂;难以阅读;难以维护。
混合复制
思路:先进行复制,再对子类进行特殊化
注意:js中的函数无法真正得进行复制,所以你只能复制对共享函数对象的引用(函数就是对象)。如果你修改了共享的函数对象,那么由他所构造出的父类和子类都会受影响。
寄生继承
注:报错??
//父类vehiclefunction vehicle(){ this.engine=1; }vehicle.prototype.ignition=function(){ console.log("turn on my engine"); }vehicle.prototype.drive=function(){ this.ignition(); console.log("steering and moving forward"); }//寄生类(子类)function car(){ //首先,ca是一个vehicle var car=new vehicle(); //接着,对ca进行定制 car.wheels=4; //保存到vehicle::drive()的特殊引用 var vehDrive=car.drive(); //重写vehicle::drive car.drive=function(){ vehDrive.call(this); console.log("rolling on all "+this.wheels+" wheels!"); } return car; }//方法一var mycar=new car();mycar.drive();//方法二(因最后使用的是mycar,所以最初的new car()将被丢弃。所以建议采用方法二,来避免因为创建而丢弃多于对象的情况。)car.drive
思路:首先复制一份vehicle父类(对象的定义),然后混入子类(对象)的定义(如果需要的话保留到父类的特殊引用),最后用这个复合对象来构建实例。
(二)隐式混入
var sth={ cool:funcction(){ this.greet="hello world"; this.count= this.count? this.count:1; }}sth.cool();sth.greeting();//"hello world"sth.count;//1var ano={ cool:functuin(){ //隐式把sth混入ano,即让sth的this指针指向ano sth.cool.call(this); }}ano.cool();ano.greeting();ano.count();//1 (不是共享状态)
评价:虽然更改this指向,但尽量不要用,来保证代码的整洁与可维护性。
总点评:在js中模拟类可能会埋下大的隐患,故少用。
原型链
一。基础知识
1.值类型的判断用typeof,引用类型的判断用instanceof。
console.log(typeof null);//objectvar fn=function(){};console.log(fn instanceof object);//true
2.函数就是一个对象。
3.只要是对象,他就是属性的集合。js中的对象可任意扩展属性。对象里的属性表示为键值对的形式。
var obj={ a:10, b:function(){ alert(this.a); }}
4.在jQuery中,“jQuery”或者“$”变量,属于函数类型。
alert(typeof $)
5.强类型:假设你在c#代码中,你定义了一个整型变量后,就不能赋一个字符型数据给这个变量(除非你用强制类型转换)
弱类型就像javascript;var 可以接受任何类型
6.一切引用类型,都是对象。
7.
构造函数中: 属性与方法为当前实例单独拥有,只能被当前实例访问,并且每声明一个实例,其中的方法都会被重新创建一次。原型中: 属性与方法为所有实例共同拥有,可以被所有实例访问,新声明实例不会重复创建方法。模块作用域(闭包)中:属性和方法不能被任何实例访问,但是能被内部方法访问,新声明的实例,不会重复创建相同的方法。
二。对象与函数
1.函数、对象的关系:
对象都是通过函数来创建
例如:
创建对象的方式://(语法糖,便捷方式)var obj={a:10,b:20}var arr={5,"x",true}实际等效于:var obj=new Object();obj.a=10;obj.b=20;var arr=new Array();arr[0]=5;arr[1]="x";arr[2]=true;
其中,Array和Object属于构造函数。—-结论:对象都是通过函数来构建。
- 每个函数读有一个属性叫做prototype,它的属性值是一个对象。在函数原型里默认得只有一个叫做constructor的属性,指向这个函数本身。
原型作为对象,是属性的集合。
- 在原型prototype中,添加自定义属性
function Fn() { } Fn.prototype.name = '王福朋'; Fn.prototype.getYear = function () { return 1988; };
- $(“div”)创建对象原理
myjQuery.prototype.attr = function () { //……};$('div') = new myjQuery();
具体代码演示
function Fn() { }Fn.prototype.name = '王福朋';Fn.prototype.getYear = function () { return 1988;};var fn = new Fn();console.log(fn.name);console.log(fn.getYear());
即,Fn是一个函数,fn对象是从Fn函数new出来的,这样fn对象就可以调用Fn.prototype中的属性。
三。函数原型、对象原型
1.每一个对象都拥有一个隐藏的属性,proto,这个属性引用了创建这个对象的函数prototype,可成为隐式原型。
–即,每个对象都有一个proto__属性,指向创建该对象的函数的prototype。
2.定律一:构造函数创建的对象.__proto__===原函数(或创建它的函数).prototype
3.
通过下面代码查看原型属性:
var obj={};console.log(obj.__proto__)
4.根据定律一,有:
obj这个对象本质上是被Object函数创建的,因此obj.__proto=== Object.prototype。我们可以用一个图来表示。
5.定律二:Object.prototype的__proto__指向的是null
,切记切记!
6.函数是被Function创建的。
//不推荐var fn1=new Function("x","y","return x+y;");console.log(fn1(5,6));
上图中,很明显的标出了:自定义函数Foo._proto指向Function.prototype,Object.___proto指向Function.prototype, 函数实例.___proto指向Function.prototype;
分析:Function也是一个函数,函数是一种对象,也有proto属性。既然是函数,那么它一定是被Function创建。所以——Function是被自身创建的。所以它的proto指向了自身的Prototype。
注意:Function.prototype指向的对象也是一个普通的被Object创建的对象
如下图:
四。instanceof
1.
上图中,f1这个对象是被Foo创建,但是“f1 instanceof Object”为什么是true呢?
原因如下:
Instanceof运算符的第一个变量是一个对象,暂时称为A;第二个变量一般是一个函数,暂时称为B。
Instanceof的判断队则是:沿着A的proto这条线来找,同时沿着B的prototype这条线来找,如果两条线能找到同一个引用,即同一个对象,那么就返回true。如果找到终点还未重合,则返回false。
同理可以解释:
具体原因请看下图(结合图+之前图片):
五。继承
1.javascript中的继承是通过原型链来体现的。先看几句代码
2.上述现象的解释:
访问一个对象的属性时,先在基本属性中查找,如果没有,再沿着__proto__这条链向上找,这就是原型链。
上例图:
上图中,访问f1.b时,f1的基本属性中没有b,于是沿着proto找到了Foo.prototype.b。
3.如何区分一个属性到底是基本的还是从原型中找到的呢?大家可能都知道答案了——hasOwnProperty,特别是在for…in…循环中,一定要注意。
注意: f1的这个hasOwnProperty方法来源
它是从Object.prototype中来的,请看图:
对象的原型链是沿着proto这条线走的,因此在查找f1.hasOwnProperty属性时,就会顺着原型链一直查找到Object.prototype。
由于所有的对象的原型链都会找到Object.prototype,因此所有的对象都会有Object.prototype的方法。这就是所谓的“继承”。
当然这只是一个例子,你可以自定义函数和对象来实现自己的继承。
7.21记
闭包与作用域
1.浏览器预解析机制:
变量、函数表达式——变量声明,默认赋值为undefined;this——赋值;函数声明——赋值;
这三种数据的准备情况我们称之为“执行上下文”或者“执行上下文环境”。
2.我们在声明变量时,全局代码要在代码前端声明,函数中要在函数体一开始就声明好。除了这两个地方,其他地方都不要出现变量声明。而且建议用“单var”形式。
3.作用域
作用域有上下级的关系,上下级关系的确定就看函数是在哪个作用域下创建的。例如,fn作用域下创建了bar函数,那么“fn作用域”就是“bar作用域”的上级。
4.块级作用域实例:
作用域中变量的值是在执行过程中产生的确定的,而作用域却是在函数创建时就确定了。
所以,如果要查找一个作用域下某个变量的值,就需要找到这个作用域对应的执行上下文环境,再在其中寻找变量的值。
5.作用域链
第一步,现在当前作用域查找a,如果有则获取并结束。如果没有则继续;
第二步,如果当前作用域是全局作用域,则证明a未定义,结束;否则继续;
第三步,(不是全局作用域,那就是函数作用域)将创建该函数的作用域作为当前作用域;
第四步,跳转到第一步。
以上代码中:第13行,fn()返回的是bar函数,赋值给x。执行x(),即执行bar函数代码。取b的值时,直接在fn作用域取出。取a的值时,试图在fn作用域取,但是取不到,只能转向创建fn的那个作用域中去查找,结果找到了。
6.闭包应用的两种情况:
(一)第一,函数作为返回值
如上代码,bar函数作为返回值,赋值给f1变量。执行f1(15)时,用到了fn作用域下的max变量的值。
(二)函数作为参数被传递
如上代码中,fn函数作为一个参数被传递进入另一个函数,赋值给f参数。执行f(15)时,max变量的取值是10,而不是100。
7.当一个函数被调用完成之后,其执行上下文环境将被销毁,其中的变量也会被同时销毁。
但是有些情况下,函数调用完成之后,其执行上下文环境不会接着被销毁。这就是需要理解闭包的核心内容。
8.闭包实例:
最后,执行完20行就是上下文环境的销毁过程,这里就不再赘述了。
9.if和for的花括号,不是域。
10.在浏览器中,全局执行环境被默认为Window对象,因此所有全局变量和函数都是作为Window对象的属性和方法创建的。某个执行环境中的所有代码执行完毕后,该环境被销毁,保存在其中的所有变量和函数定义也随之销毁。(全局执行环境直到应用程序退出,例如关闭网页或浏览器时,才销毁。)
11.每个函数独有自己的执行环境。
12.如果这个环境是函数,则将其活动对象作为变量对象。
13.内部环境可以通过作用域链访问所有的外部环境,但外部环境不能访问内部环境的任何变量和函数。—-每个环境都可以向上搜索作用域链,以查询变量和函数名。但任何环境都不能通过向下搜索作用域链而进入另一个执行环境。
14.函数参数也被当作变量来对待,因此其访问规则与执行环境中的其他变量相同。
闭包的使用情况
1.外部函数调用之后,其变量对象本应该被销毁,但闭包的存在使我们仍然可以访问外部函数的变量对象。
function outer(){ var a=1; return function(){ return a; }}var b=outer();console.log(b());//1
2.模块级作用域
(function(){ var now=new Data(); if(now.getMonth()==0&&now.getData()==1){ alert("happy"); }})()
它用来限制向全局作用域中,添加过多的函数和变量。
3.创建私有变量
function outer(){ var a=1; return { setA:function(val){ a=val; } getA:function(){ return a; } }}var closure=outer();console.log(a);//报错console.log(closure.getA());//1closure.setA(2);console.log(closure.getA());//2
通过闭包,创建具有私有属性的实例对象。
4.闭包只能取得包含函数中,任何变量的最后一个值。
function arrFunc(){ var arr=[]; for(var i=0;i<6;i++){ arr[i]=function(){ return i; } } return arr;}
arr数组包含了6个匿名函数,每个匿名函数都能访问外部的变量i;
当arrFun执行完毕后,其作用与被销毁,但他的变量对象仍保存在内存,得以被匿名访问,这时i的值为6.
要想保存在循环过程中每一个i的值,需要在匿名函数外部再套用一个匿名函数,在这个匿名函数中定义另一个变量,并且立即执行来保存i的值。(如下)
function arrFunc(){ var arr=[]; for(var i=0;i<10;i++){ arr[i]=function(num){ return function(){ return num; } }(i) } return arr;}console.log(arrFunc()[1]());
这是最内部的匿名函数访问的是num值,所以数组中10个匿名函数的返回值就是1~1-10.
5.闭包中的this
(1)
var name='window'; var obj={ name:'object', getName:function(){ return function(){ rerurn this.name; } } } console.log(obj.getName()());//window
obj.getName()()
实际是在全局作用域中调用了匿名函数,this指向了Window。即window才是匿名函数功能执行的环境。
(2)
若想要this指向外部函数的执行环境,可以这样改写:
var name="window";var obj={ name:"object", getName:function(){ var that=this; return function(){ return that.name; } }}console.log(obj.getName()());//object
(3)arguments与this也有相同的问题,注意下面情况:
var name="window";var obj={ name:"object", getName:function(){ return this.name; }}obj.getName();//object (此时getName()是在对象obj的环境中执行的,所以this指向obj)(obj.getName=obj.getName)();//window非严格模式赋值语句返回的是等号右边的值,在全局作用域中返回,所以(obj.getName=obj.getName)();的this指向全局。要把函数名和函数功能分割开
6.内存泄漏
闭包会引用包含函数的整个变量对象,如果闭包的作用域链中保存着一个HTML元素,那么就意味着该元素无法被销毁。所以我们要在元素操作完后,主动销毁:
function assignHandler(){ var element=document.getElementById("someElement"); var id=element.id; element.onclick=function(){ alert(id); } element=null;//见分析}
分析:getElementById返回对拥有指定 ID 的第一个对象的引用 ,当不再使用该对象时,只需给指定该对象的指针复制为null,利用js垃圾回收机制(会自动回收无引用的对象),来释放其所占内存
7.函数内部的定时器
当函数内部的定时器,引用了外部函数的变量对象时,该变量不会被销毁。
(function(){ var a=0; setInterval(function(){ console.log(a++); },1000)})()
运用闭包的关键:
1.闭包引用外部函数变量对象中的值。
2.在外部函数的外部调用闭包。
7.23
构造函数
设计模式
1.
工厂模式构造函数模式原型模式组合使用构造函数模式和原型模式动态原型模式寄生构造函数模式稳妥构造函数模式
2.引入:建对象可以使用 Object构造函数 或者 字面量的形式 创建单个对象,但是这样会产生大量重复的代码。
// Object构造函数var obj = new Object();// 字面量var obj = {};// 例如我们要记录小明跟小红的姓名跟年龄var xiaoming = { name: "xiaoming", age: 11};var xiaohong = { name: "xiaohong", age: 10};
很明显上面例子代码重复,要是记录的信息多一点,代码重复率则越高,为了解决这个问题,就有了工厂模式
工厂模式
function createPerson(name, age) { var person = { name: name, age: age, getName: function() { console.log(this.name); } }; return person;}var xiaoming = createPerson("xiaoming", 11);xiaoming.getName() // xiaoming
点评:工厂模式无法解决对象识别的问题,其实是无法辨认实例是由哪个构造函数创建
console.log(xiaoming instanceof createPerson); // false console.log(xiaoming instanceof Object); // true
改进: 构造函数模式
构造函数
1.
// 按照惯例,构造函数以大写字母开头,以区别普通函数。function Person(name) { this.name = name; this.getName = function() { console.log(this.name); };}var xiaoming = new Person("xiaoming");xiaoming.getName(); // xiaoming// 成功解决工厂模式问题console.log(xiaoming instanceof Person); // true
2.之所以成为构造函数是因为 new 操作符,要创建构造函数实例就必须使用 new 操作符,并且经历以下4个步骤
1.创建一个新对象
2.将构造函数的作用于赋给新对象(因此this指向了这个新对象)
3.执行构造函数中的代码(为这个新对象添加属性)
4.返回这个新对象
既然说到了构造函数,那自然要说说它的返回值。这其中分三种情况。
①: 没有指明具体的返回值,则返回该构造函数的实例化对象
function Person(name) { this.name = name; this.getName = function() { console.log(this.name); };}var xiaoming = new Person("xiaoming");console.log(xiaoming);
②:若指明返回值,但不是引用类型的话,与情况①结果相同,返回该构造函数的实例化对象。
function Person(name) { this.name = name; this.getName = function() { console.log(this.name); }; return 12;}var xiaoming = new Person("xiaoming");console.log(xiaoming);//object { name:xiaoming,getName:function() }
③:若指明返回值且是引用类型,则返回该引用类型
function Person(name) { this.name = name; this.getName = function() { console.log(this.name); }; return { age: 12 };}var xiaoming = new Person("xiaoming");console.log(xiaoming);//object{agge:12}
原型模式
1.所有实例共享它所包含的属性和方法。
2.
function Person(){}Person.prototype.name = "wei";Person.prototype.age = 27;Person.prototype.job = "Software";Person.prototype.sayName = function(){ alert(this.name);}var person1 = new Person();person1.sayName(); var person2 = new Person();person2.sayName(); alert(person1.sayName == person2.sayName);
构造函数+原型
1.构造函数模式用于定义实例属性,原型模式用于定义方法和共享的属性。结果,每个实例都会有自己的一份实例属性的副本,但同时又共享着对方法的引用,最大限度的节省了内存。另外,这种混成模式还支持向构造函数传递参数;
2.
function Person(name, age){ this.name = name; this.age = age; this.friends = ["乾隆","康熙"];}Person.prototype = { constructor:Person, sayName:function(){ alert(this.name); }}var person1 = new Person("wei",29);var person2 = new Person("bu",25);person1.friends.push("嬴政");console.log(person1.friends); //["乾隆", "康熙", "嬴政"]console.log(person2.friends); //["乾隆", "康熙"]console.log(person1.friends === person2.friends); //falseconsole.log(person1.sayName === person2.sayName); //true
动态原型模式
function Person(name, age){ this.name = name; this.age = age; this.friends = ["乾隆","康熙"]; if(typeof this.sayName!="function"){ Person.prototype.sayName = function(){ alert(this.name); } }}var person1 = new Person("wei",29);person1.friends.push("嬴政");person1.sayName();
注意构造函数代码中的if语句,这里只在sayName()方法不存在的情况下才会将它添加到原型中。这断代码只有在第一次调用构造函数的时候才会被执行。此后,原型已经被初始化,不需要再做什么修改。不过要记住,这里所做的修改能立即在所有实例中得到反映。因此,这种方法可以说确实非常完美。
注意:使用动态原型模式时,不能使用对象字面量(属性名)重写原型。如果在已经创建了实例的情况下重写原型,那么就会切断现有的实例与新原型之间的联系。
寄生构造函数模式
1.基本思想是创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回新创建的对象,但从表面看,这个函数又很像典型的构造函数。
2.
function Person(name, age, job){ var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function(){ alert(this.name); } return o;}var person = new Person("wei",29,"banzhuan");person.sayName();
稳妥构造函数模式
1.所谓稳妥对象,是指没有公共属性,而且其方法也不引用this对象。稳妥对象最适合在一些安全环境中(这些环境会禁止使用this和new),或者在防止数据被其他应用程序改动时使用。稳妥构造函数遵循的与寄生构造函数类似的模式,但又两点不同:一是新创建对象的实例方法不引用this;二是不使用new操作符调用构造函数。
2.
function Person(name, age, job){ //创建要返回的新对象 var o = new Object(); //可以在这里定义私有变量和函数 //添加方法 o.sayName = function(){ alert(this.name); }; //返回对象 return o;}var person =Person("weiqi",22,"banzhuan");person.sayName();
3.注意:与寄生构造函数模式类似,使用稳妥构造函数模式创建的对象与构造函数之间没有什么关系,因此instanceof操作符对这种对象也没有意义。
继承
(一)原型链继承
1.为了让子类继承父类的属性(也包括方法),首先需要定义一个构造函数。然后,将父类的新实例赋值给子类的原型。
核心:将父类实例作为子类的原型。
2.例子:
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>原型链继承</title></head><body><script src="myJS.js"></script><script> <!--定义一个动物类--> function Animal(name){// 属性 this.name=name || Animal;// 实力方法 this.sleep=function(){ console.log(this.name+"正在睡觉"); } }// 原型方法 Animal.prototype.eat=function(food){ console.log(this.name+"正在吃"+food); };// 原型链继承 function Cat(){ } Cat.prototype=new Animal; Cat.prototype.name="Cat";// Test code var cat=new Cat(); console.log(cat.name);//cat console.log(cat.eat('fish'));//猫吃鱼 console.log(cat.sleep());//猫正在睡觉 console.log(cat instanceof Animal);//true // instanceof它的作用是判断其左边对象是否为其右边类的实例,返回boolean类型的数据。 console.log(cat instanceof Cat);//true</script></body></html>
3.特点:
(1)非常纯粹的继承关系,既有父类的实例,也有子类的实例
(2)父类新增的原型方法/原型属性,子类都能访问到
(3)简单,也易于实现
(4)若父类具有引用属性,子类可以访问到
缺点:
(1)要想为子类新增属性和方法,必须要在newAnimal()这样的语句执行之后,不能放在构造器中。
(2)无法实现多继承。—多继承:子类继承自多个父亲
(3)创建子类实例,无法向构造传参。 —可以传参,看下一个
(二)构造继承
核心:使用父类构造函数来增强子类实例,等于是复制父类的实力属性给子类(没用到原型)—-类似于C++中继承
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>构造函数</title></head><body><script src="myJS.js"></script><script> <!--定义一个动物类--> function Animal(name){// 属性 this.name=name || Animal;// 实力方法 this.sleep=function(){ console.log(this.name+"正在睡觉"); } } //构造函数 function Cat(name){ Animal.call(this); this.name=name||"tom"; } //Test Code var cat=new Cat(); console.log(cat.name); console.log(cat.sleep);//打印出来是sleep的构造函数,如果加()表示调用 console.log(cat instanceof Animal);//false console.log(cat instanceof Cat);//true</script></body></html>
特点:
(1)解决了1中,子类实例共享父类引用属性的问题(2)创建子类实例时,可以向父类传递参数。(3)可以实现多继承(call多个父类的对象)
缺点
(1)只有子类的实例(2)只能继承父类构造的属性和方法,不能继承父类原型属性和方法(3)无法实现函数的复用,每个子类都有父类实力函数的副本,影响性能。(致命)
(三)实例继承
1.核心:为父类实例添加新特性,作为子类实例返回
2.代码
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>实力继承</title></head><body><script src="myJS.js"></script><script> <!--定义一个动物类--> function Animal(name){// 属性 this.name=name || Animal;// 实力方法 this.sleep=function(){ console.log(this.name+"正在睡觉"); } } //构造函数 function Cat(name){ //实例是父类的实例 var instance=new Animal(); instance.name=name||"tom"; return instance; } //Test Code var cat=new Cat(); console.log(cat.name); console.log(cat.sleep()); console.log(cat instanceof Animal);//true console.log(cat instanceof Cat);//false</script></body></html>
3.特点:
不限制调用方式,不管是new子类()还是子类(),都返回父类的实例。
缺点:
(1)实例是父类的实例,不是子类的实例。(2)不支持多继承。
(四)拷贝继承
核心:将父类的原型,枚举赋值给子类
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>拷贝继承</title></head><body><script src="myJS.js"></script><script> <!--定义一个动物类--> function Animal(name){// 属性 this.name=name || Animal;// 实力方法 this.sleep=function(){ console.log(this.name+"正在睡觉"); } } //构造函数 function Cat(name){ var animal=new Animal(); for( var p in animal){ Cat.prototype[p]=animal[p]; } Cat.prototype.name=name||"Tom"; } //Test Code var cat=new Cat(); console.log(cat.name);//Tom console.log(cat.sleep());//Tom正在睡觉 console.log(cat instanceof Animal);//false console.log(cat instanceof Cat);//true</script></body></html>
特点:
(1)支持多继承 缺点:(1)效率较低,内容占用高(因为要拷贝父类的属性)(2)只能获取父类可以枚举的方法(不可枚举的方法,不能使用for in访问到)
(五)组合继承(原型链+借用构造函数)
1.核心思想:使用原型链实现对原型属性和方法
的继承,而通过借用构造函数来实现对实例属性
的继承。这样,既通过在原型上定义方法实现了函数复用,又保证每个实例都有它自己的属性
2.核心:通用调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类型,实现函数复用。
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>组合继承 </title></head><body><script src="myJS.js"></script><script> <!--定义一个动物类--> function Animal(name){// 属性 this.name=name || Animal;// 实力方法 this.sleep=function(){ console.log(this.name+"正在睡觉"); } } //构造函数 function Cat(name){ //1.继承父类构造的属性,并保留传参的优点(第一次调父类构造) Animal.call(this); this.name=name||"Tom"; } //2.将父类实例作为子类原型,实现函数复用(第二次调父类构造) Cat.prototype=new Animal(); //Test Code var cat=new Cat(); console.log(cat.name); console.log(cat.sleep()); console.log(cat instanceof Animal);//true console.log(cat instanceof Cat);//false</script></body></html>
特点:
(1)弥补了方式2的缺陷,可以继承实例属性/方法,也可以继承原型属性/方法。(2)即是子类的实例,也是父类的实例(3)不存在引用属性的共享问题(4)可传参(5)函数可复用
缺点:
调用了两次父类构造函数,生成了两份实例。
(六)寄生组合继承
(1)核心:通过寄生方式,砍掉父类的实例属性(通过声明超类,且他仅仅等效于父类原型来实现),这样,在调用两次父类的构造的时候,就不会初始化两次实例属性/方法,避免了组合继承的缺点
//父类function Animal(name){ this.name=name; this.sleep=function(){ console.log(this.name+"正在睡觉"); }}Animal.prototype.eat=function(food){ console.log(this.name+"正在吃"+food);}//子类function Cat(name){ Animal.call(this);//子类第一次继承 this.name=name||'Tom';}(function(){ //创建一个没有实例方法的超类 var Super=function(){}; //让超类的原型==父类的原型 Super.prototype=Animal.prototype; //将超类实例作为子类的原型 Cat.prototype=new Super();})();//Test Codevar cat=new Cat();console.log(cat.name);console.log(cat.sleep());console.log(cat.eat("fish"));console.log(cat instanceof Cat);//trueconsole.log(cat instanceof Animal);//true
(2)特点:堪称完美
(3)缺点:实现较为复杂
递归
1.递归函数:不改变问题性质,逐渐缩小问题规模。
不改变问题性质:解决问题的方法相同
缩小问题规模:逐层返回原规模问题
2.递归函数:一个问题的自我调用
实例1 递归求和
function sum(n){ if(n==1) return 1; return sum(n-1) + n;}fac(5);
分析:上面是一个累加函数,最多需要保存n个记录,复杂度为o(n)。当n较大时,需要保存成百上千个调用帧(函数调用时会在内存形成一个“调用记录”,又称“调用帧”),很容易发生“栈溢出”,耗费内存。
优化如下(尾递归):
function sum(n,total) { if(n===0){ return total; } return sum(n-1,n+total) }sum(5,0);
利用尾递归,仅保存一个调用记录,复杂度为o(1)。
实例2 Fibonacci数列(斐波那契数列)
1,1,2,3,5,8,13,21,34,55,89…求第 n 项
递归
分析
- 假设已知 fib(n) 为第 n 项
- 递归关系
- fib(n) = fib(n-1) + fib(n-2)
- 递归体
function fib(n){
return fib(n-1)+fib(n-2);
}- 临界条件
- fib(1) == 1
- fib(2) == 1
- 递归函数
function fib(n){ if(n == 0 || n ==1) return 1; return fib(n-1) + fib(n-2);}
非递归
function fib(n){ //起始两个值 var f1; var f2; f1=f2=1; console.log(f1,f2); //因为已经输出前两个,所以i从2开始;因为最后一个的fn由它前面的加和,所以循环继续条件i<n,即i==n-1 for(i=2;i<n;i++){ fn=f1+f2; console.log(fn); f1=f2; f2=fn; } } console.log(fib(8));
求幂
1.概念:
求幂就是求 某一个数 几次方2*2 =2 的 平方, 2 的 2 次方求 n 的 m 次方,最终要得到一个函数 power( n, m )n 的 m 次方就是 m 个 n 相乘, 即 n 乘以 (m-1) 个 n 相乘
分析:
1. 假设已知函数 power(n,m) 为 n 的 m 次幂2. 递归关系* power(n,m-1) * n3. 递归体function power(n,m){ return power(n,m-1) * n;}4. 临界条件 * m == 1 ,return n * m == 0 ,reutnr 15. 递归函数function power(n,m){ if(m == 1) return n; return power(n,m-1) * n;}
克隆
(1)浅克隆:浅拷贝只能拷贝值类型的数据,对于引用类型,只会拷贝引用地址,如果有引用类型,多个拷贝对象会共用同一个引用类型的数据,造成混乱。
function clone(parent, child){ var i, child = child || {}; for(i in parent){ parent.hasOwnProperty(i) && (child[i] = parent[i]); } return child;}var o1 = { arr: [1,2,4], name:'jack'};var o2 = clone(o1);o2.arr.push(3);log(o1.arr); //[1, 2, 4, 3]log(o2.arr); //[1, 2, 4, 3]
两个拷贝对象共用同一个引用类型,会相互影响
(2)深拷贝
function cloneDeep(parent, child){ var i, child = child || {}; for(i in parent){ //若果父元素的第i个属性为真 if(parent.hasOwnProperty(i)){ if(typeof parent[i]==='object'){ child[i] = Array.isArray(parent[i]) ? []:{}; cloneDeep(parent[i], child[i]); }else{ child[i] = parent[i]; } } } return child;}var o3 = { arr: [1,2,4], name:'jack'};var o4 = cloneDeep(o3);o4.arr.push(3);log(o3.arr);//[1, 2, 4]log(o4.arr);//[1, 2, 4, 3]
两个对象引用不同的引用地址,互不影响
7.25
移动端轮播图
<!DOCTYPE html><html id="html"><head><meta charset="utf-8"><title>无标题文档</title><meta name="keywords" content="" /><meta name="description" content="" /><meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" /><meta name="format-detection" content="telephone=no" /><meta name="apple-mobile-web-app-capable" content="yes" /><meta name="apple-mobile-web-app-status-bar-style" content="black"><style type="text/css">body{margin:0;}ul{padding:0; margin:0; height:200px; position:absolute; left:0; top:0;}li{list-style:none; height:100%; float:left;}li img{width:100%; height:100%;}#box{width:100%; height:200px; overflow:hidden; position:relative;}</style></head><body><div id="box"> <ul id="ul1"> <li><img src="image/pic1.jpg" /></li> <li><img src="image/pic2.jpg" /></li> </ul></div><script src="js/jquery-1.10.1.min.js"></script><script src="js/velocity.min.js"></script><script>var iNow = 0;//li加倍(ul1里4个li)$("#ul1").html($("#ul1").html()+$("#ul1").html())//box宽度,赋值给li的宽度(即窗口一张图片)$("li").css("width",$("#box")[0].offsetWidth+"px");//li加倍(ul1==4个li宽度)$("#ul1").css("width",$("li")[0].offsetWidth*4+"px");touchFunc($("#box")[0],"left",function(){toLeft()})touchFunc($("#box")[0],"right",function(){toRight()})function toLeft(){ iNow++; //动画 $("#ul1").velocity({"left":-$("li")[0].offsetWidth*iNow+"px"},{duration:400,easing:"linear",complete:function(){ //如果是第二张,切到第0张 if(iNow==$("li").length/2){ iNow=0; $("#ul1").css("left",0); } }})}function toRight(){ //如果第0张,切换到第二张 if(iNow==0){ iNow=$("li").length/2; $("#ul1").css("left",-$("li")[0].offsetWidth*iNow+"px"); } iNow--; $("#ul1").velocity({"left":-$("li")[0].offsetWidth*iNow+"px"},{duration:400,easing:"linear"})}function touchFunc(obj,type,func) { var init = {x:5,y:5,sx:0,sy:0,ex:0,ey:0}; var sTime = 0, eTime = 0; type = type.toLowerCase(); //1.添加手指落下事件 obj.addEventListener("touchstart",function(){ sTime = new Date().getTime(); //获取并记录:起始手指位置 init.sx = event.targetTouches[0].pageX; init.sy = event.targetTouches[0].pageY; init.ex = init.sx; init.ey = init.sy; if(type.indexOf("start") != -1) func(); }, false); //2.添加手指滑动事件 obj.addEventListener("touchmove",function() { event.preventDefault(); //获取并记录:手指抬起位置 init.ex = event.targetTouches[0].pageX; init.ey = event.targetTouches[0].pageY; if(type.indexOf("move")!=-1) func(); }, false); //3.触摸结束,根据时间、方向,判断执行事件的类型 obj.addEventListener("touchend",function() { var changeX = init.sx - init.ex;//起始-结束 var changeY = init.sy - init.ey; if(Math.abs(changeX)>Math.abs(changeY)&&(changeY)>init.y) {//滑动范围大于5 且偏于水平滑动 Math.abs:绝对值 if(changeX > 0) { if(type.indexOf("left")!=-1) func();//此代码中没有写鼠标上下滑事件,可根据左右滑动类推 }else{ if(type.indexOf("right")!=-1) func(); } } else if(Math.abs(changeY)>Math.abs(changeX)&&Math.abs(changeX)>init.x){//同上 判断垂直 if(changeY > 0) { if(type.indexOf("top")!=-1) func(); }else{ if(type.indexOf("down")!=-1) func(); } } else if(Math.abs(changeX)<init.x && Math.abs(changeY)<init.y){//若改变范围<5,则默认为点击事件 eTime = new Date().getTime(); if((eTime - sTime) > 300) {//长按事件 if(type.indexOf("long")!=-1) func(); } else {//点击事件 if(type.indexOf("click")!=-1) func(); } } if(type.indexOf("end")!=-1) func(); }, false);};</script></body></html>
call,apply,bind
一、总结:
apply 、 call 、bind 三者都是用来改变函数的this对象的指向的;
apply 、 call 、bind 三者第一个参数都是this要指向的对象,也就是想指定的上下文;
apply 、 call 、bind 三者都可以利用后续参数传参;
bind是返回对应函数,便于稍后调用;apply、call则是立即调用 。
二、call、apply区别:
func.call(this, arg1, arg2);func.apply(this, [arg1, arg2])
call(this)、 apply(this) 指的是func()这个函数本身
三、使用
1、数组之间追加
var array1 = [12 , "foo" , {name "Joe"} , -2458];var array2 = ["Doe" , 555 , 100];Array.prototype.push.apply(array1, array2);/* array1 值为 [12 , "foo" , {name "Joe"} , -2458 , "Doe" , 555 , 100] */
让array1 具备Array的push方法
2、获取数组中的最大值和最小值
var numbers = [5, 458 , 120 , -215 ];var maxInNumbers = Math.max.apply(Math, numbers), //458maxInNumbers = Math.max.call(Math,5, 458 , 120 , -215); //458
让numbers 具备Math的max方法,number 本身没有 max 方法,但是 Math 有,我们就可以借助 call 或者 apply 使用其方法。
3、验证是否是数组(前提是toString()方法没有被重写过)
function isArray(obj){ var bool; if (Object.prototype.toString.call(obj) === '[object Array]') { bool = "true"; } else { bool = "false"; } return bool; }alert(isArray([1,2,3,4]));
4、类(伪)数组 => 真正数组 具备数组方法
var domNodes = Array.prototype.slice.call(document.getElementsByTagName("*"));
Javascript中存在一种名为伪数组的对象结构。比较特别的是 arguments 对象,还有像调用 getElementsByTagName, document.childNodes 之类的,它们返回NodeList对象都属于伪数组。不能应用 Array下的 push , pop 等方法。
5、例子
定义一个 log 方法,让它可以代理 console.log 方法,常见的解决方法是:
function log(msg) { console.log(msg);}log(1); //1log(1,2); //1
上面方法可以解决最基本的需求,但是当传入参数的个数是不确定的时候,上面的方法就失效了,这个时候就可以考虑使用 apply 或者 call,注意这里传入多少个参数是不确定的,所以使用apply是最好的,方法如下:
function log(){ console.log.apply(console, arguments);};log(1); //1log(1,2); //1 2
接下来的要求是给每一个 log 消息添加一个”(app)”的前辍,比如:
log("hello world"); //(app)hello world
该怎么做比较优雅呢?这个时候需要想到arguments参数是个伪数组,通过 Array.prototype.slice.call 转化为标准数组,再使用数组方法unshift,像这样:
function log(){ //伪类数组 => 正真的数组 var args = Array.prototype.slice.call(arguments); args.unshift('(app)'); console.log.apply(console, args);};
6、bind
var bar = function(){ console.log(this.x);}var foo = { x:3}var sed = { x:4}var func = bar.bind(foo).bind(sed);func(); //3var fiv = { x:5}var func = bar.bind(foo).bind(sed).bind(fiv);func(); //3
答案是,两次都仍将输出 3 ,而非期待中的 4 和 5 。原因是,在Javascript中,多次 bind() 是无效的。更深层次的原因, bind() 的实现,相当于使用函数在内部包了一个 call / apply ,第二次 bind() 相当于再包住第一次 bind() ,故第二次以后的 bind 是无法生效的。
caller、callee
1.caller
作用:返回当前这个函数的调用者,在函数中调用有效,否则就返回null
var a = function(){ console.log(a.caller);}a();//nullvar b = function(){ a();}b();//ƒ (){a()};
第一个打印是null,第二个打印的是b所对应的函数.第一次打印是window去调用的a函数,window是顶层调用,所以打印的是null,第二个打印是在b函数里进行,这样就能通过a函数找到对应的函数调用者.
2.callee
(1)作用:callee是函数arguments的属性,它会返回当前这个正在被调用的函数
function test(){ console.log(arguments.callee);}test(10, 20)
打印的就是test这个函数
(2)callee只有一个属性,length,这个属性的作用是函数的形参个数
arguments.length返回的是实参个数
function test(a){ console.log(arguments.callee.length); // 1 console.log(arguments.length); // 2}test(10, 20)
(3)通过这个属性,能实现递归函数的作用,下面就通过他来实现以下5的阶乘
function test(a){ if(a == 1){ return 1; } return arguments.callee(a - 1) * a;}console.log(test(5)); // 120
No way is impossible to courage!
前端基础进阶@简书波同学
(一):内存空间详细图解
1.在操作对象时,实际上是在操作对象的引用而不是实际的对象。因此,引用类型的值都是按引用访问的。
2.
var a1 = 0; // 变量对象var a2 = 'this is string'; // 变量对象var a3 = null; // 变量对象var b = { m: 20 }; // 变量b存在于变量对象中,{m: 20} 作为对象存在于堆内存中var c = [1, 2, 3]; // 变量c存在于变量对象中,[1, 2, 3] 作为对象存在于堆内存中
3.内存空间管理
(1)JavaScript的内存生命周期
1. 分配你所需要的内存2. 使用分配到的内存(读、写)3.不需要时将其释放、归还
(2)为了便于理解,我们使用一个简单的例子来解释这个周期。
var a = 20; // 在内存中给数值变量分配空间 alert(a + 100); // 使用内存 a = null; // 使用完毕之后,释放内存空间
(3)JavaScript有自动垃圾收集机制,那么这个自动垃圾收集机制的原理是什么呢?其实很简单,就是找出那些不再继续使用的值,然后释放其占用的内存。垃圾收集器会每隔固定的时间段就执行一次释放操作。
(4)在JavaScript中,最常用的是通过标记清除的算法来找到哪些对象是不再继续使用的,因此a = null其实仅仅只是做了一个释放引用的操作,让 a 原本对应的值失去引用,脱离执行环境,这个值会在下一次垃圾收集器执行操作时被找到并释放。而在适当的时候解除引用,是为页面获得更好性能的一个重要方式。
(5)在局部作用域中,局部变量当执行完后就自动释放。而全局变量释放时候不确定。建议:尽量避免使用全局变量,以确保性能问题。
(二)执行上下文
1.因此在一个JavaScript程序中,必定会产生多个执行上下文,JavaScript引擎会以堆栈的方式来处理它们,这个堆栈,我们称其为函数调用栈
(call stack)。栈底永远都是全局上下文,而栈顶就是当前正在执行的上下文。
当代码在执行过程中,遇到以下三种情况,都会生成一个执行上下文,放入栈中,而处于栈顶的上下文执行完毕之后,就会自动出栈。
JavaScript中的运行环境大概包括三种情况。全局环境:JavaScript代码运行起来会首先进入该环境函数环境:当函数被调用执行时,会进入当前函数中执行代码eval
2.
var color = 'blue';function changeColor() { var anotherColor = 'red'; function swapColors() { var tempColor = anotherColor; anotherColor = color; color = tempColor; } swapColors();}changeColor();
3.结论:
单线程
同步执行,只有栈顶的上下文处于执行中,其他上下文需要等待
全局上下文只有唯一的一个,它在浏览器关闭时出栈
函数的执行上下文的个数没有限制
每次某个函数被调用,就会有个新的执行上下文为其创建,即使是调用的自身函数,也是如此。
4.
function f1(){ var n=999; function f2(){ alert(n); } return f2;}var result=f1();result(); // 999
(三)闭包
1.简要概念:假设函数A在函数B的内部进行定义了,并且当函数A在执行时,访问了函数B内部的变量对象,那么B就是一个闭包。
2.例子:
var fn = null;function foo() { var a = 2; function innnerFoo() { console.log(c); //not define console.log(a);//2 } fn = innnerFoo; // 将 innnerFoo的引用,赋值给全局变量中的fn}function bar() { var c = 100; fn(); // 此处的保留的innerFoo的引用}foo();bar();
3.所以,通过闭包,我们可以在其他的执行上下文中,访问到函数的内部变量。需要注意的是,虽然例子中的闭包被保存在了全局变量中,但是闭包的作用域链并不会发生任何改变。在闭包中,能访问到的变量,仍然是作用域链上能够查询到的变量。
4.应用:
function fn() { console.log('this is test.')}var timer = setTimeout(fn, 1000);/*注:setTimeout的返回值是一个整形id(从1开始),也就是这个setTimeout的唯一标示符。即作为一个引用,指向setTimeout。*/console.log(timer);
结果:变量timer的值,会立即输出出来,表示setTimeout这个函数本身已经执行完毕了。但是一秒钟之后,fn才会被执行。
分析:这是在函数的内部实现中,setTimeout通过特殊的方式,保留了fn的引用,让setTimeout的变量对象,并没有在其执行完毕后被垃圾收集器回收。因此setTimeout执行结束后一秒,我们任然能够执行fn函数。
(四)函数声明与表达式
1.JavaScript中,有两种声明方式,一个是使用var的变量声明,另一个是使用function的函数声明。
变量对象的创建过程中,函数声明比变量声明具有更为优先的执行顺序,即我们常常提到的函数声明提前。因此我们在执行上下文中,无论在什么位置声明了函数,我们都可以在同一个执行上下文中直接使用该函数。
fn(); // functionfunction fn() { console.log('function');}
2.函数表达式
与函数声明不同,函数表达式使用了var进行声明,那么我们在确认他是否可以正确使用的时候就必须依照var的规则进行判断,即变量声明。我们知道使用var进行变量声明,其实是进行了两步操作。
// 变量声明var a = 20;// 实际执行顺序var a = undefined; // 变量声明,初始值undefined,变量提升,提升顺序次于function声明a = 20; // 变量赋值,该操作不会提升
同样的道理,当我们使用变量声明的方式来声明函数时,就是我们常常说的函数表达式。函数表达的提升方式与变量声明一致。
fn(); // 报错var fn = function() { console.log('function');}
上例子的执行顺序为:
var fn = undefined; // 变量声明提升fn(); // 执行报错fn = function() { // 赋值操作,此时将后边函数的引用赋值给fn console.log('function');}
3.匿名函数
使用场景:多作为一个参数传入另一个函数中
var a = 10;var fn = function(bar, num) { return bar() + num;}fn(function() { return a;}, 20)
4.函数自执行与块级作用域
利用闭包,我们可以访问到执行上下文内部的变量和方法,因此,我们只需要根据闭包的定义,创建一个闭包,将你认为需要公开的变量和方法开放出来。
(function() { // 私有变量 var age = 20; var name = 'Tom'; // 私有方法 function getName() { return `your name is ` + name; } // 共有方法 function getAge() { return age; } // 将引用保存在外部执行环境的变量中,形成闭包,防止该执行环境被垃圾回收 window.getAge = getAge;})();
(五).柯里化
(1)map(): 对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组。
通俗来说,就是遍历数组的每一项元素,并且在map的第一个参数(回调函数)中进行运算处理后返回计算结果。返回一个由所有计算结果组成的新数组。
// 回调函数中有三个参数// 第一个参数表示newArr的每一项,第二个参数表示该项在数组中的索引值// 第三个表示数组本身var newArr = [1, 2, 3, 4].map(function(item, i, arr) { console.log(item, i, arr, this); // 可运行试试看 return item + 1; // 每一项加1})console.log(newArr); // [2, 3, 4, 5]
(2)柯里化(英语:Currying),又称为部分求值,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回一个新的函数的技术,新函数接受余下参数并返回运算结果。
接收单一参数,因为要携带不少信息,因此常常以回调函数的理由来解决。将部分参数通过回调函数等方式传入函数中返回一个新函数,用于处理所有的想要传入的参数
(3)例子:
function add() { // 第一次执行时,定义一个数组专门用来存储所有的参数 var _args = [].slice.call(arguments); // 在内部声明一个函数,利用闭包的特性保存_args并收集所有的参数值 var adder = function () { var _adder = function() { [].push.apply(_args, [].slice.call(arguments)); return _adder; }; // 利用隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回 _adder.toString = function () { return _args.reduce(function (a, b) { return a + b; }); } return _adder; } return adder.apply(null, [].slice.call(arguments)); } // 输出结果,可自由组合的参数 console.log(add(1, 2, 3, 4, 5)); // 15 console.log(add(1, 2, 3, 4)(5)); // 15 console.log(add(1)(2)(3)(4)(5)); // 15
(六)拖拽封装
1.让元素动起来:我们常常会通过修改元素的top,left,translate来其的位置发生改变。由于修改一个元素top/left值会引起页面重绘,而translate不会,因此从性能优化上来判断,我们会优先使用translate属性。如下:
var ele1=document.querySelector(".ele1"); var ele2=document.querySelector(".ele2"); var btn1=document.querySelector(".btn1"); var btn2=document.querySelector(".btn2"); var left=0; var translateX=0; btn1.onclick=function () { left=left+5; ele1.style.left=left+"px"; } btn2.onclick=function () { ele2.style.transform = 'translateX('+ translateX +'px)'; //alert(translateX) translateX+=5; }
2.拖拽
// 获取当前浏览器支持的transform兼容写法/* function getTransform() { var transform = '', divStyle = document.createElement('div').style, // 判断浏览器是否支持transforma,若是,用transform;否则,通过position来设定元素位置 transformArr = ['transform', 'webkitTransform', 'MozTransform', 'msTransform', 'OTransform'], i = 0, len = transformArr.length; for(; i < len; i++) { if(transformArr[i] in divStyle) { // 找到之后立即返回,结束函数 return transform = transformArr[i]; } } // 如果没有找到,就直接返回空字符串 return transform;}//alert(getTransform()) transform//封装获取元素的属性(兼容)function getStyle(elem, property) { // ie通过currentStyle来获取元素的样式,其他浏览器通过getComputedStyle来获取 return document.defaultView.getComputedStyle ? document.defaultView.getComputedStyle(elem, false)[property] : elem.currentStyle[property];}//获取起始位置function getTargetPos(elem) { var pos = {x: 0, y: 0}; var transform = getTransform(); if(transform) { var transformValue = getStyle(elem, transform); if(transformValue == 'none') { elem.style[transform] = 'translate(0, 0)'; return pos; } else { var temp = transformValue.match(/-?\d+/g); return pos = {//? x: parseInt(temp[4].trim()), y: parseInt(temp[5].trim()) } } } else { if(getStyle(elem, 'position') == 'static') { elem.style.position = 'relative'; return pos; } else { var x = parseInt(getStyle(elem, 'left') ? getStyle(elem, 'left') : 0); var y = parseInt(getStyle(elem, 'top') ? getStyle(elem, 'top') : 0); return pos = { x: x, y: y } } }}//设置位置// pos = { x: 200, y: 100 }function setTargetPos(elem, pos) { var transform = getTransform(); if(transform) { elem.style[transform] = 'translate('+ pos.x +'px, '+ pos.y +'px)'; } else { elem.style.left = pos.x + 'px'; elem.style.top = pos.y + 'px'; } return elem;}var oElem = document.getElementById('target');//保存鼠标起始位置var startX = 0;var startY = 0;//保存元素起始位置var sourceX = 0;var sourceY = 0;oElem.addEventListener('mousedown', start, false);function start(event) { // 获取鼠标初始位置 startX = event.pageX; startY = event.pageY; // 获取元素初始位置 var pos = getTargetPos(oElem); sourceX = pos.x; sourceY = pos.y; // 绑定 document.addEventListener('mousemove', move, false); document.addEventListener('mouseup', end, false);}function move(event) { // 获取鼠标当前位置 var currentX = event.pageX; var currentY = event.pageY; // 获取差值 var distanceX = currentX - startX; var distanceY = currentY - startY; // 计算并设置元素当前位置 setTargetPos(oElem, { x: (sourceX + distanceX).toFixed(),//toFixed():四舍五入,不保留小数部分 y: (sourceY + distanceY).toFixed() })}function end(event) { document.removeEventListener('mousemove', move); document.removeEventListener('mouseup', end); // do other things}*/
3.拖拽封装
完整封装—不推荐,因为若拖拽中使两元素重叠,则在拖拽元素上抬起时,可能丢失对该元素的拖拽事件,或者鼠标抬起不能释放元素
;(function() { // 这是一个私有属性,不需要被实例访问 var transform = getTransform(); function Drag(selector) { // 放在构造函数中的属性,都是属于每一个实例单独拥有 this.elem = typeof selector == 'Object' ? selector : document.getElementById(selector); this.startX = 0; this.startY = 0; this.sourceX = 0; this.sourceY = 0; this.init(); } // 原型 Drag.prototype = { constructor: Drag, init: function() { // 初始时需要做些什么事情 this.setDrag(); }, // 稍作改造,仅用于获取当前元素的属性,类似于getName getStyle: function(property) { return document.defaultView.getComputedStyle ? document.defaultView.getComputedStyle(this.elem, false)[property] : this.elem.currentStyle[property]; }, // 用来获取当前元素的位置信息,注意与之前的不同之处 getPosition: function() { var pos = {x: 0, y: 0}; if(transform) { var transformValue = this.getStyle(transform); if(transformValue == 'none') { this.elem.style[transform] = 'translate(0, 0)'; } else { //获取transform已有属性值 var temp = transformValue.match(/-?\d+/g); pos = { x: parseInt(temp[4].trim()), y: parseInt(temp[5].trim()) } } } else { if(this.getStyle('position') == 'static') { this.elem.style.position = 'relative'; } else { pos = { x: parseInt(this.getStyle('left') ? this.getStyle('left') : 0), y: parseInt(this.getStyle('top') ? this.getStyle('top') : 0) } } } return pos; }, // 用来设置当前元素的位置 setPostion: function(pos) { if(transform) { this.elem.style[transform] = 'translate('+ pos.x +'px, '+ pos.y +'px)'; } else { this.elem.style.left = pos.x + 'px'; this.elem.style.top = pos.y + 'px'; } }, // 该方法用来绑定事件 setDrag: function() { var self = this; this.elem.addEventListener('mousedown', start, false); function start(event) { self.startX = event.pageX; self.startY = event.pageY; var pos = self.getPosition(); self.sourceX = pos.x; self.sourceY = pos.y; document.addEventListener('mousemove', move, false); document.addEventListener('mouseup', end, false); } function move(event) { var currentX = event.pageX; var currentY = event.pageY; var distanceX = currentX - self.startX; var distanceY = currentY - self.startY; self.setPostion({ x: (self.sourceX + distanceX).toFixed(), y: (self.sourceY + distanceY).toFixed() }) } function end(event) { document.removeEventListener('mousemove', move); document.removeEventListener('mouseup', end); // do other things } } } // 私有方法,仅仅用来获取transform的兼容写法 function getTransform() { var transform = '', divStyle = document.createElement('div').style, transformArr = ['transform', 'webkitTransform', 'MozTransform', 'msTransform', 'OTransform'], i = 0, len = transformArr.length; for(; i < len; i++) { if(transformArr[i] in divStyle) { return transform = transformArr[i]; } } return transform; } // 一种对外暴露的方式 window.Drag = Drag;})();// 使用:声明2个拖拽实例new Drag('target');new Drag('target2');不推荐,因为若拖拽中使两元素重叠,则在拖拽元素上抬起时,可能丢失对该元素的拖拽事件,或者鼠标抬起不能释放元素
4.改进(清除浏览器默认事件+元素抬起问题)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><title>无标题文档</title><style type="text/css">body{margin:0;}#div1{width:150px; height:150px; background-color:red; position:absolute; left:0; top:0;}#div2{width:150px; height:150px; background-color:blue; position:absolute; left:250px; top:250px;}</style></head><body style="height:2000px;"><div id="div1">div div div</div><div id="div2"></div><script src="myJS.js"></script><script>$("div1").onmousedown = function(ev){ var ev = ev || event; var scrollTop = document.body.scrollTop || document.documentElement.scrollTop; var scrollLeft = document.body.scrollLeft || document.documentElement.scrollLeft; var disX = ev.clientX + scrollLeft - $("div1").offsetLeft; var disY = ev.clientY + scrollTop - $("div1").offsetTop; document.onmousemove = function(ev){ var ev = ev || event; var scrollTop = document.body.scrollTop || document.documentElement.scrollTop; var scrollLeft = document.body.scrollLeft || document.documentElement.scrollLeft; $("div1").style.left = ev.clientX + scrollLeft - disX + "px"; $("div1").style.top = ev.clientY +scrollTop - disY + "px"; } document.onmouseup = function(){ if($("div1").releaseCapture){$("div1").releaseCapture()}; document.onmousemove = document.onmouseup = null; } if($("div1").setCapture){$("div1").setCapture()}; return false;}</script></body></html>
5.带有范围的拖拽
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><title>无标题文档</title><style type="text/css">body{margin:0;}#div1{width:150px; height:150px; background-color:red; position:absolute; left:0; top:0;}#div2{width:150px; height:150px; background-color:blue; position:absolute; left:250px; top:250px;}</style></head><body style="height:2000px;"><div id="div1">div div div</div><div id="div2"></div><script src="myJS.js"></script><script>$("div1").onmousedown = function(ev){ var ev = ev || event; var scrollTop = document.body.scrollTop || document.documentElement.scrollTop; var scrollLeft = document.body.scrollLeft || document.documentElement.scrollLeft; var disX = ev.clientX + scrollLeft - $("div1").offsetLeft; var disY = ev.clientY + scrollTop - $("div1").offsetTop; document.onmousemove = function(ev){ var ev = ev || event; var scrollTop = document.body.scrollTop || document.documentElement.scrollTop; var scrollLeft = document.body.scrollLeft || document.documentElement.scrollLeft; var winW = document.documentElement.clientWidth; var winH = document.documentElement.clientHeight; var nowLeft = ev.clientX + scrollLeft - disX; var nowTop = ev.clientY +scrollTop - disY; if(nowLeft<=0){ nowLeft = 0; }else if(nowLeft>=winW-$("div1").offsetWidth){ nowLeft = winW-$("div1").offsetWidth; } $("div1").style.left = nowLeft + "px"; if(nowTop<=0){ nowTop = 0; }else if(nowTop>=winH-$("div1").offsetHeight){ nowTop = winH-$("div1").offsetHeight; } $("div1").style.top = nowTop + "px"; } document.onmouseup = function(){ if($("div1").releaseCapture){$("div1").releaseCapture()}; document.onmousemove = document.onmouseup = null; } if($("div1").setCapture){$("div1").setCapture()}; return false;}</script></body></html>
(七)promise
- 当我们想要确保某代码在谁谁之后执行时,我们可以利用函数调用栈,将我们想要执行的代码放入回调函数中。
// 一个简单的封装 function want() { console.log('这是你想要执行的代码'); } function fn(want) { console.log('这里表示执行了一大堆各种代码'); // 其他代码执行完毕,最后执行回调函数 want && want(); } fn(want); //这里表示执行了一大堆各种代码 //这是你想要执行的代码
还可以利用队列机制。
function want() { console.log('这是你想要执行的代码');}function fn(want) { // 将想要执行的代码放入队列中,根据事件循环的机制,我们就不用非得将它放到最后面了,由你自由选择 want && setTimeout(want, 0); console.log('这里表示执行了一大堆各种代码');}fn(want);//这里表示执行了一大堆各种代码//这是你想要执行的代码
2.promise机制
function want() { console.log('这是你想要执行的代码'); } function fn(want) { console.log('这里表示执行了一大堆各种代码'); // 返回Promise对象 return new Promise(function(resolve, reject) { if (typeof want == 'function') { resolve(want); } else { reject('TypeError: '+ want +'不是一个函数') } }) } fn(want).then(function(want) { want(); }) //这里表示执行了一大堆各种代码 //这是你想要执行的代码 fn('1234').catch(function(err) { console.log(err); }) //这是你想要执行的代码 //TypeError: 1234不是一个函数
(1)Promise对象有三种状态,他们分别是:
pending: 等待中,或者进行中,表示还没有得到结果resolved(Fulfilled): 已经完成,表示得到了我们想要的结果,可以继续往下执行rejected: 也表示得到结果,但是由于结果并非我们所愿,因此拒绝执行这三种状态不受外界影响,而且状态只能从pending改变为resolved或者rejected,并且不可逆。
(2)Promise对象的构造函数中,将一个函数作为第一个参数。而这个函数,就是用来处理Promise的状态变化。
new Promise(function(resolve, reject) { if(true) { resolve() }; if(false) { reject() };})
上面的resolve和reject都为一个函数,他们的作用分别是将状态修改为resolved和rejected。
(3)Promise对象中的then方法,可以接收构造函数中处理的状态变化,并分别对应执行。then方法有2个参数,第一个函数接收resolved状态的执行,第二个参数接收reject状态的执行。
function fn(num) { return new Promise(function(resolve, reject) { if (typeof num == 'number') { resolve(); } else { reject(); } }).then(function() { console.log('参数是一个number值'); }, function() { console.log('参数不是一个number值'); })}fn('hahha');//参数不是一个number值fn(1234);//参数是一个number值
then方法的执行结果也会返回一个Promise对象。因此我们可以进行then的链式执行,这也是解决回调地狱的主要方式。
(4)
function fn(num) { return new Promise(function(resolve, reject) { if (typeof num == 'number') { resolve(); } else { reject(); } }) .then(function() { console.log('参数是一个number值'); }) .then(null, function() { console.log('参数不是一个number值'); })}fn('hahha');fn(1234);//then(null, function() {}) 就等同于catch(function() {})
(4)promise数据传递
var fn = function(num) { return new Promise(function(resolve, reject) { if (typeof num == 'number') { resolve(num); } else { reject('TypeError'); } })}fn(2).then(function(num) { console.log('first: ' + num); return num + 1;}).then(function(num) { console.log('second: ' + num); return num + 1;}).then(function(num) { console.log('third: ' + num); return num + 1;});// 输出结果first: 2second: 3third: 4
(5)基于promise对ajax封装
var url = 'https://hq.tigerbrokers.com/fundamental/finance_calendar/getType/2017-02-26/2017-06-10';// 封装一个get请求的方法function getJSON(url) { return new Promise(function(resolve, reject) { var XHR = new XMLHttpRequest(); XHR.open('GET', url, true); XHR.send(); XHR.onreadystatechange = function() { if (XHR.readyState == 4) { if (XHR.status == 200) { try { var response = JSON.parse(XHR.responseText); resolve(response); } catch (e) { reject(e); } } else { reject(new Error(XHR.statusText)); } } } })}getJSON(url).then(resp => console.log(resp));
3.Promise.all
当有一个ajax请求,它的参数需要另外2个甚至更多请求都有返回结果
之后才能确定,那么这个时候,就需要用到Promise.all来帮助我们应对这个场景。
Promise.all接收一个Promise对象组成的数组
作为参数,当这个数组所有的Promise对象状态都变成resolved或者rejected
的时候,它才会去调用then
方法。
var p1 = Promise.resolve(3);var p2 = 1337;var p3 = new Promise((resolve, reject) => {setTimeout(resolve, 100, "foo");}); Promise.all([p1, p2, p3]).then(values => { console.log(values); // [3, 1337, "foo"] });
4.Promise.race
与Promise.all相似的是,Promise.race都是以一个Promise对象组成的数组
作为参数,不同的是,只要当数组中的其中一个Promsie状态变成resolved或者rejected时
,就可以调用.then方法了。而传递给then方法的值也会有所不同,大家可以再浏览器中运行下面的例子与上面的例子进行对比。
var p1 = new Promise(function(resolve, reject) { setTimeout(resolve, 500, "one"); });var p2 = new Promise(function(resolve, reject) { setTimeout(resolve, 100, "two"); });Promise.race([p1, p2]).then(function(value) { console.log(value); // "two" // 两个都完成,但 p2 更快});var p3 = new Promise(function(resolve, reject) { setTimeout(resolve, 100, "three");});var p4 = new Promise(function(resolve, reject) { setTimeout(reject, 500, "four"); });Promise.race([p3, p4]).then(function(value) { console.log(value); // "three" // p3 更快,所以它完成了 }, function(reason) { // 未被调用});var p5 = new Promise(function(resolve, reject) { setTimeout(resolve, 500, "five"); });var p6 = new Promise(function(resolve, reject) { setTimeout(reject, 100, "six");});Promise.race([p5, p6]).then(function(value) { // 未被调用 }, function(reason) { console.log(reason); // "six" // p6 更快,所以它失败了});
(八)循环机制
1.(1)Javascript有一个main thread 主进程和call-stack(一个调用堆栈),在对一个调用堆栈中的task处理的时候,其他的都要等着。
(2)当在执行过程中遇到一些类似于setTimeout等异步操作的时候,会交给浏览器的其他模块(以webkit为例,是webcore模块)进行处理
(3)当到达setTimeout指定的延时执行的时间之后,task(回调函数)会放入到任务队列之中。一般不同的异步任务的回调函数会放入不同的任务队列之中。
(4)等到调用栈
中所有task执行完毕之后,接着去执行任务队列
之中的task(回调函数)。
2.
(1)调用栈中遇到DOM操作、ajax请求以及setTimeout等WebAPIs的时候就会交给浏览器内核的其他模块进行处理(webkit内核在Javasctipt执行引擎之外,有一个重要的模块是webcore模块。对于图中WebAPIs提到的三种API,webcore分别提供了DOM Binding、network、timer模块来处理底层实现。)
(2)等到这些模块处理完这些操作的时候,将回调函数放入任务队列中
(3)等栈中的task执行完之后,再去执行任务队列之中的回调函数。
3.例子:事件循环机制究竟是怎么执行setTimeout的。
1.首先main()函数的执行上下文入栈
2.代码接着执行,遇到console.log(‘Hi’),此时log(‘Hi’)入栈,console.log方法只是一个webkit内核支持的普通的方法,所以log(‘Hi’)方法立即被执行。此时输出’Hi’。
3.当遇到setTimeout的时候,执行引擎将其添加到栈中。
4.调用栈发现setTimeout是之前提到的WebAPIs中的API,因此将其出栈之后将延时执行的函数交给浏览器的timer模块进行处理。
5.timer模块去处理延时执行的函数,此时执行引擎接着执行将log(‘SJS’)添加到栈中,此时输出’SJS’。
6.当timer模块中延时方法规定的时间到了之后就将其放入到任务队列之中,此时调用栈中的task已经全部执行完毕。
7.调用栈中的task执行完毕之后,执行引擎会接着看执行任务队列中是否有需要执行的回调函数。这里的cb函数被执行引擎添加到调用栈中,接着执行里面的代码,输出’there’。等到执行结束之后再出栈。
4.总结:
(1)所有代码都要在函数调用栈中执行(2)当遇到那三种APIs的时候,会交给浏览器内核的其他模块进行处理(3)任务队列用来存放回调函数(4)等到调用栈中任务执行完,再回去执行任务队列中任务
5.例子
for (var i = 0; i < 5; i++) { setTimeout(function() { console.log(new Date, i); }, 1000);}console.log(new Date, i);
(1)当i=0,1,2,3,4,执行栈执行循环体里面的代码,发现是setTimeout,将其出栈之后把延时执行的函数交给Timer模块进行处理。
(2)当i=5的时候,不满足条件,因此for循环结束,console.log(new Date, i)入栈,此时的i已经变成了5。因此输出5。
(3)此时原设定的等待时间1s已经过去,timer模块将5个回调函数按照注册的顺序返回给任务队列。
(4)因为调用栈中任务已执行完,所以执行引擎去执行任务队列中的函数,5个function依次入栈执行之后再出栈,此时的i已经变成了5。因此几乎同时输出5个5。
(5)其实只有输出第一个5之后需要等待1s,这1s的时间是timer模块需要等到的规定的1s时间之后才将回调函数交给任务队列。等执行栈执行完毕之后再去执行任务对列中的5个回调函数。这期间是不需要等待1s的。结果:5 -> 5,5,5,5,5,即第1个 5 直接输出,1s之后,输出 5个5;
6.宏任务与微任务例子
(function test() { setTimeout(function() {console.log(4)}, 0); new Promise(function executor(resolve) { console.log(1); for( var i=0 ; i<10000 ; i++ ) { i == 9999 && resolve(); } console.log(2); }).then(function() { console.log(5); }); console.log(3);})()
(1)setTimeout和Promise都被称之为任务源,来自不同任务源的回调函数会被放进不同的任务队列里面。
(2)Promise的第一个参数并不会被放进Promise的任务队列之中,而会在当前队列就执行。
(3)setTimeout和Promise(不含then)的任务队列叫做macro-task(宏任务)
小结:
★macro-task包括:script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering。★micro-task包括:process.nextTick, Promises, Object.observe, MutationObserver
其中上面的setImmediate和process.nextTick是Node.JS里面的API,浏览器里面并没有
事件循环顺序
(1)script->全局->宏任务(先将它交给处理他的模块,在将它放入宏任务队列)->微任务(将回调放入微任务队列),知道函数调用栈只剩全局
(2)先执行宏任务的一个队列,再执行所有的微任务
(3)循环(2)
案例分析
(1)首先,script任务源先执行,全局上下文入栈。
(2)script任务源的代码在执行时遇到setTimeout,作为一个macro-task,将其回调函数放入自己的队列之中。
(3)script任务源的代码在执行时遇到Promise实例。Promise构造函数中的第一个参数是在当前任务直接执行不会被放入队列之中,因此此时输出 1 。
(4)在for循环里面遇到resolve函数,函数入栈执行之后出栈,此时Promise的状态变成Fulfilled(执行)。代码接着执行遇到console.log(2),输出2。
(5)接着执行,代码遇到then方法,其回调函数作为micro-task入栈,进入Promise的任务队列之中。
(6).代码接着执行,此时遇到console.log(3),输出3。
(7)输出3之后第一个宏任务script的代码执行完毕。接着执行所有微任务(then),输出5
(8).这时候所有的micro-task执行完毕,第一轮循环结束。第二轮循环从setTimeout的任务队列开始,setTimeout的回调函数入栈执行完毕之后出栈,此时输出4。
==》如下图:
结论:
(1)不同的任务会放进不同的任务队列之中。
(2)当有多个macro-task(micro-task)队列时,事件循环的顺序是按上文macro-task(micro-task)的分类中书写的顺序执行的。
面试小结
不适用箭头函数的时候
下面我就总结一下什么情况下不该使用箭头函数。
1.在对象上定义函数
先来看下面这段代码
var obj = { array: [1, 2, 3], sum: () => { console.log(this === window); // => true return this.array.reduce((result, item) => result + item); }};// Throws "TypeError: Cannot read property 'reduce' of undefined"obj.sum();
sum 方法定义在 obj 对象上,当调用的时候我们发现抛出了一个 TypeError ,因为函数中的 this 是 window 对象,所以 this.array 也就是 undefined 。原因也很简单,相信只要了解过es6 箭头函数的都知道
箭头函数没有它自己的this值,箭头函数内的this值继承自外围作用域
解决方法也很简单,就是不用呗。这里可以用es6里函数表达式的简洁语法,在这种情况下,this值就取决于函数的调用方式了。
var obj = { array: [1, 2, 3], sum() { console.log(this === obj); // => true return this.array.reduce((result, item) => result + item); }};obj.sum(); // => 6
通过object.method()语法调用的方法使用非箭头函数定义,这些函数需要从调用者的作用域中获取一个有意义的this值。
2.在原型上定义函数
在对象原型上定义函数也是遵循着一样的规则
function Person (pName) { this.pName = pName;}Person.prototype.sayName = () => { console.log(this === window); // => true return this.pName;}var person = new Person('wdg');person.sayName(); // => undefined
使用function函数表达式
function Person (pName) { this.pName = pName;}Person.prototype.sayName = function () { console.log(this === person); // => true return this.pName;}var person = new Person('wdg');person.sayName(); // => wdg
所以给对象原型挂载方法时,使用function函数表达式
3.动态上下文中的回调函数
this 是js中非常强大的特点,他让函数可以根据其调用方式动态的改变上下文,然后箭头函数直接在声明时就绑定了this对象,所以不再是动态的。
在客户端,在dom元素上绑定事件监听函数是非常普遍的行为,在dom事件被触发时,回调函数中的this指向该dom,可当我们使用箭头函数时:
var button = document.getElementById('myButton'); button.addEventListener('click', () => { console.log(this === window); // => true this.innerHTML = 'Clicked button';});
因为这个回调的箭头函数是在全局上下文中被定义的,所以他的this是window。所以当this是由目标对象决定时,我们应该使用函数表达式:
var button = document.getElementById('myButton'); button.addEventListener('click', function() { console.log(this === button); // => true this.innerHTML = 'Clicked button';});
4.构造函数中
在构造函数中,this指向新创建的对象实例
this instanceOf MyFunction === true
需要注意的是,构造函数不能使用箭头函数,如果这样做会抛出异常
var Person = (name) => { this.name = name;}// Uncaught TypeError: Person is not a constructorvar person = new Person('wdg');
理论上来说也是不能这么做的,因为箭头函数在创建时this对象就绑定了,更不会指向对象实例。
5.太简短的(难以理解)函数
箭头函数可以让语句写的非常的简洁,但是一个真实的项目,一般由多个开发者共同协作完成,就算由单人完成,后期也并不一定是同一个人维护,箭头函数有时候并不会让人很好的理解,比如
let multiply = (a, b) => b === undefined ? b => a * b : a * b;let double = multiply(2);double(3); // => 6multiply(2, 3); // =>6
这个函数的作用就是当只有一个参数 a 时,返回接受一个参数 b 返回 a*b 的函数,接收两个参数时直接返回乘积,这个函数可以很好的工作并且看起很简洁,但是从第一眼看去并不是很好理解。
为了让这个函数更好的让人理解,我们可以为这个箭头函数加一对花括号,并加上 return 语句,或者直接使用函数表达式:
function multiply(a, b) { if (b === undefined) { return function (b) { return a * b; } } return a * b;}let double = multiply(2);double(3); // => 6multiply(2, 3); // => 6
总结
毫无疑问,箭头函数带来了很多便利。恰当的使用箭头函数可以让我们避免使用早期的 .bind() 函数或者需要固定上下文的地方并且让代码更加简洁。
箭头函数也有一些不便利的地方。我们在需要动态上下文的地方不能使用箭头函数:定义需要动态上下文的函数,构造函数,需要 this 对象作为目标的回调函数以及用箭头函数难以理解的语句。在其他情况下,请尽情的使用箭头函数。
关于Call,apply能否给箭头函数赋值
1.使用call,apply,bind改变函数的this指向
let obj = { name: "qianzhixiang", func: function(a,b){ console.log(this.name,a,b); }};obj.func(1,2); // qianzhixiang 1 2let func = obj.func;func(1,2); // 1 2let func_ = func.bind(obj);func_(1,2);// qianzhixiang 1 2func(1,2);// 1 2func.call(obj,1,2);// qianzhixiang 1 2func.apply(obj,[1,2]);// qianzhixiang 1 2
2.使用call,apply,bind改变箭头函数的this指向?
let obj = { name: "qianzhixiang", func: (a,b) => { console.log(this.name,a,b); }};obj.func(1,2); // 1 2let func = obj.func;func(1,2); // 1 2let func_ = func.bind(obj);func_(1,2);// 1 2func(1,2);// 1 2func.call(obj,1,2);// 1 2func.apply(obj,[1,2]);// 1 2
因为箭头函数中的,apply、call方法指向的对象会被默认忽略。故对箭头函数,call、apply不能更改其this指向。
- wiki
- Wiki
- wiki
- wiki
- wiki
- WIKI?
- wiki
- WIKI
- wiki
- wiki
- wiki
- wiki
- Wiki
- wiki
- wiki
- WIKI系统 ScrewTurn WIKI
- WIKI简介
- 有关wiki
- OnSize、OnSizing和OnGetMinMaxInfo区别分析
- hive 函数
- [C#] Direct2D 学习笔记 (一)vb.net转换为c#
- iOS 图片压缩----微信图片处理策略
- rem布局,移动H5页面布局规范(1)。
- wiki
- 安卓实现扫一扫识别数字
- 目标管理之SMART法则
- Spark运行架构
- jQuery效果
- 如何通过JCenter 和 Mavenue Central 从 Android Studio 分发自己的Library库
- 树的前/中/后遍历
- SDUT_2118 数据结构实验之链表三:链表的逆置
- Android和javascript相互调用2