wiki

##gulp(注:mac系统中用到了管理员权限,故命令行开头要加上sudo)


ps:以下属于伪代码


1.常用步骤:

(1)因为gulp是基于node开发的,所以要先全局安装node,若已安装,则下一步:

全局安装gulp
npm install -g gulp

gulp -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。(即overflow的值不为 visible)
  • position的值不为relative和static。
  • display的值为table-cell,table-caption,inline-block中任意一个。

###BFC的规则:

  • 内部的Box会在垂直方向,一个接一个地放置。
  • Box垂直方向的距离由margin决定。属于同一个BFC的两个相邻Box的margin会发生重叠。
  • 每个元素的margin box的左边, 与包含块border box的左边相接触(对于从左往右的格式化,否则相反)。即使存在浮动也是如此。
  • BFC的区域不会与float box重叠。
  • BFC就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。反之也如此。
  • 计算BFC的高度时,浮动元素也参与计算。

###解决问题

  • 浮动元素父元素高度崩塌
  • 文字环绕(overflow:hidden)
  • margin collapse

##两栏布局—左边固定宽度,右边自适应

####方法一:

假如左边部分宽度为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)添加额外标签
–通过在浮动元素末尾添加一个空的标签,例如

,其他标签br等亦可。
实例:

<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(线性渐变,使用角度)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MB2Gvajw-1598887833497)(https://git.oschina.net/uploads/images/2017/0721/123708_d44931e3_1364780.png “1.png”)]

渐变方向与角度关系见上图(注意区分渐变方向、渐变界线)–渐变界限指产生渐变效果的某两种颜色的交线

示例代码:

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的转换方法:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Heixg8dG-1598887833500)(https://git.oschina.net/uploads/images/2017/0721/124119_0cb856c0_1364780.png “2.png”)]

  • 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方向位移b
transform: translateX(a);                  元素x方向位移a,y方向不变
transform: translateY(b);                  元素y方向位移b,x方向不变

skew()
设置元素倾斜的角度,用法包括:

transform: skew(a, b);元素x方向逆时针倾斜角度a,y方向顺时针倾斜角度b
transform: 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);
//0
ls.setItem('name','Byron');
ls.setItem('age','24');
console.log(ls.length);
//2
for(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:修改之前的value
newValue:修改之后的value
url:触发改动的页面url
StorageArea:发生改变的Storage
在使用 web 存储前,应检查浏览器是否支持 localStorage 和sessionStorage
if(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实现。

63

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还为本地存储提供了非常直观的显示方式,调试的时候很方便

64

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>

65

常见效果

1.自制单选框:label标签是和input配合使用的,运行代码,点击label中内容,使得input获得焦点

2.小球惯性运动

利用拖拽原理
当鼠标在小球上落下时,记录旧的事件位置old
当鼠标在窗口不断被拖动时,不断记录新的事件位置new
当鼠标抬起时,计算old、new差值(即为小球运动方向),然后不断利用差值给小球赋值

3.图片拉近效果
鼠标移入transform:scale(1.2);

4.立体盒子

7.11记

7.13记 瀑布流(图片等宽不等高)


  1. JS实现
    单行排列不下时,接着最矮的那一列向下排列

    检测是否具有滚动条加载事件,
    最后元素.offsetTop+最后元素.offsetHeight/2<页面滚走距离+窗口高度

    **注意:**开始时,页面的图片必须足够,才能触发onscroll加载

  2. JQuery
    注意:

jQuery方法,只给jQuery对象用
.get(i)/[i]    //把jq对象转化为原生
width//仅仅获取所定义的宽度
outerwidth//获取width+padding+border
jQuery获取宽度,对象.width(无单位的数值)
**jQuery支持连缀,隐式迭代**

理解:对象后可跟多个方法 query选择器,得到的是这个对象的集合,所以不用再循环去找了

jQuqry遍历:

类数组.each(function(index,value){
    
})
注:value指每一个参与遍历的dom对象
jQuery.inArray( value, array [, fromIndex ] )

描述: 在数组中查找指定值并返回它的索引(如果没有找到,则返回-1)。

JS入门

标签(空格分隔): 自定义属性+数组,字符串方法+ajax


  1. 入门须知:

    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请求过程的七大步骤:

  1. 建立TCP连接
  2. 浏览器向服务器发送请求命令
  3. 浏览器发送请求头信息
  4. 服务器应答
  5. 服务器发送应答头信息
  6. 服务器给浏览器发送数据
  7. 服务器关闭TCP连接

4.HTTP请求有以下组成:

  1. 方法、动作。例如:get/post
  2. url:请求地址
  3. 请求头(包含客户端环境信息,身份验证信息等)
  4. 请求体(请求正文,包含客户提交的查询字符串、表单信息等)

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 Document
function 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脚本以<?php 开头,?>结尾
  • PHP文件的默认文件扩展名是.php
  • PHP语句以分号;结尾

例如:

<?php
echo "hallo world"
?>

10.json

  • json是存储和交换文本信息的语法,类似XML。

  • 不管什么语言,都可以解析json。

  • json长度很短小

  • json的值可以是下列类型:

    数字(整数或浮点数)
    字符串
    逻辑值
    数组
    对象
    null

  • json.parse//将字符串解析成json对象

11.jQuery.ajax([setting])

  1. type:类型,“post”或“get”,默认为get

  2. url:请求发送的地址

  3. data:是一个对象,连同请求发送到服务器的数据

  4. dataType:预期服务器返回的数据类型。
    如果不指定,jQuery会自动根据HTTP包MIME信息来智能判断,一般采用JSON格式,可以设置成JSON

  5. success:请求成功后的回调函数。传入返回后的数据,以及包含成功代码的字符串

  6. error:请求失败时调用的函数。传入XHR对象。


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的核心则是动态添加

  • jsonp是一种方式或者说非强制性协议,如同ajax一样,它也不一定非要用json格式来传递数据,如果你愿意,字符串都行,只不过这样不利于用jsonp提供公开服务。

  • 其实ajax与jsonp的区别不在于是否跨域,ajax通过服务端代理一样可以实现跨域,jsonp本身也不排斥同域的数据的获取

7.基于Promise和原生JavaScript的Ajax,dataType支持json和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<Element(s)>。)
jQuery(“#divTest a”).text();
  • parent>child选择器(其返回值为Array<Element(s)>。)

parent>child选择器:代表父元素下的一级子元素
jQuery(“#divTest>input”).val();

  • prev+next选择器

匹配所有紧接在prev元素后的next元素

jQuery(“#next+input”).val();
匹配#next元素的,下一个同级元素

  • prev ~ siblings选择器

匹配prev元素之后的所有siblings元素

3.基本过滤选择器

(1):first/:last选择器。

(2):not选择器。

(3):even和:odd选择器。

(4):eq:gt、:lt、选择器

(5):header选择器。

(6):animated选择器。

//第一个li内容
jQuery(“li:first”).text();

//最后一个li内容
jQuery(“li:last”).text();

//input未被选中的值 
jQuery(“li input:not(:checked)”).val();

//索引为偶数的li
jQuery(“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的li
jQuery(“li:contains(‘hyip’)”)

//选中内容为空的li的后一个li
jQuery(“li:empty+li”)

//选中包含(嵌套)a标签的li
jQuery(“li:has(a)”)

5.可见性过滤选择器

(1):hidden选择器。

(2):visible选择器。

//选中不可见的li
jQuery(“li:hidden”).text();

//选中可见的li
jQuery(“li:visible”).text();

6.属性过滤选择器

(1) [attribute]选择器。

(2)[attribute=value]、[attribute!=value]选择器(此处包含两种)。

(3)[attribute^=value]、[attribute$=value]、[attribute*=value]选择器(此处包含三种)。

(4)[selector][selector2]选择器。

//name为hyipinvest
jQuery(“input[name=’hyipinvest’]”)

//name以hyip开始
jQuery(“input[name^=’hyip’]”)

//name以hyip结束
jQuery(“input[name$=’hyip’]”)

//name包含oo
jQuery(“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基础特性

声明变量

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DIDd7b2z-1598887833508)(https://git.oschina.net/uploads/images/2017/0721/124721_2d7744f7_1364780.jpeg “3.jpg”)]

普通变量与默认变量

  • 普通变量

定义之后可以在全局范围内使用。

$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;
}

比较

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CDZZCwvU-1598887833512)(https://git.oschina.net/uploads/images/2017/0721/124911_867d8e7a_1364780.jpeg “4.jpg”)]


[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) 则能够给值列表中的每个项目添加样式。

  • 值列表中可以再包含值列表,比如 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.除法

 ”/  ”符号被当作除法运算符时有以下几种情况:

• 如果数值或它的任意部分是存储在一个变量中或是函数的返回值。

• 如果数值被圆括号包围。

• 如果数值是另一个数学表达式的一部分。

//SCSS
p {
  font: 10px/8px;             // 纯 CSS,不是除法运算
  $width: 1000px;
  width: $width/2;            // 使用了变量,是除法运算
  width: round(1.5)/2;        // 使用了函数,是除法运算
  height: (500px/2);          // 使用了圆括号,是除法运算
  margin-left: 5px + 8px/2px; // 使用了加(+)号,是除法运算
}
 width: (1000px / 100px);//得到无意义数值

7.所有算数运算都支持颜色值,并且是分段运算的。也就是说,红、绿和蓝各颜色分段单独进行运算。如:

p {
  color: #010203 + #040506;
}

算数运算也能将数字和颜色值 一起运算,同样也是分段运算的。如:

p {
  color: #010203 * 2;
}

7.17记

7.18记

排序算法集锦


冒泡排序


  1. 算法原理:利用相邻元素进行比较,使元素如同气泡一样不断移动最终使得最大的元素不断的向右移动直到适当的位置位置。针对所有的元素重复以上的步骤,除了最后一个。持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较

    两两开始比较,从而将最大元素冒泡到最后;进而最后元素在每轮中不必参与比较

  2. 代码

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<j,交换a[i]、a[j]

ii>如果不满足i<j,则交换a[j]、a[0]

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的位置互换。

解决: 如果比较的两个值相等,则认为合理,不再交换。

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;
 }

直接选择


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.缺点:需要辅助空间


点评:

数值较少:冒泡(稳定、简单)

时间性能好:快排

堆排序:节省空间,排序快;见堆浪费时间

排序方法时间复杂度(平均)时间复杂度(最坏)时间复杂度(最好)空间复杂度稳定性复杂性
直接插入o(n2)o(n2)o(n)o(1)稳定性简单
希尔排序o(nlog2n)o(n2)o(n)o(1)不稳定较复杂
直接选择o(n2)o(n2)o(n2)o(1)不稳定简单
堆排序o(nlog2n)o(nlog2n)o(nlog2n)o(1)不稳定较复杂
冒泡o(n2)o(n2)o(n)o(1)稳定简单
快排o(nlog2n)o(n2)o(nlog2n)o(nlog2n)不稳定较复杂
归并o(nlog2n)o(nlog2n)o(nlog2n)o(n)稳定较复杂
基数o( d(n+r) )o( d(n+r) )o( d(n+r) )o(n+r)稳定较复杂

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动画animate

delay()  延时(隔多久之后,再执行)
.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”,….}

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(脉冲)、rubber
band(橡皮筋)、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)获取其宽度,假定宽度=1rem

var d = window.document.createElement('div');

d.style.width = '1rem'; //1rem=1 html font-size

d.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();//2
setTimeout(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,这个新对象会绑定到函数调用的this

4,如果函数没有返回其他对象,那么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

function foo(){
    console.log(this.a);
}

var a=2;
foo.call(null);//2

如果你把null或undefined作为this的绑定对象传入call、apply或者bind,这些值在调用时会被忽略,实际应用的是默认绑定规则。

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.继承与重写例子:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3mM0ErZU-1598887833514)(https://git.oschina.net/uploads/images/2017/0720/112748_0306cbaa_1364780.jpeg “继承.jpg”)]

三。多态

1.相对多态:虽然任何层次都可以引用继承层次中中高层的方法(无论高层方法名和当前方法名是否相同),但我们并不想要访问绝对的继承层次(或类)。

2.在上面二中的实例,可用super来代替inherited,它的含义:超类,表示当前的父类或祖先类。

3.super功能:从子类的构造函数中通过super来调用父类的构造函数。

真正来说,构造函数属于类。

js中,“类”属于构造函数。(类似于Foo.prototype…这样的类型引用)

4.多态并不表示子类和父类有关联,子类得到的只是父类的一份副本。类的继承其实就是复制。

5.多重继承(js中不提供“多重继承”的功能)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VDjTLC0X-1598887833515)(https://git.oschina.net/uploads/images/2017/0720/112610_19327d06_1364780.jpeg “多重继承.jpg”)]

四。混入

(一)显式混入

伪多态

1.实例:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lE1RKaoB-1598887833516)(https://git.oschina.net/uploads/images/2017/0720/112625_43299046_1364780.jpeg “伪多态.jpg”)]

2.显式多态:vehicle.drive.call(this)

相对多态:inherited:drive()

3.伪多态:在上例中,若car(子类)中没有的方法,在vehicle(父类)出现了,此时不需要方法多态,因为调用mixin(…)时会把父类方法复制到子类中

4.伪多态缺点:代码复杂;难以阅读;难以维护。

混合复制

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bL9SOod7-1598887833518)(https://git.oschina.net/uploads/images/2017/0720/112701_b91aa28b_1364780.jpeg “混合复制.jpg”)]

思路:先进行复制,再对子类进行特殊化

注意:js中的函数无法真正得进行复制,所以你只能复制对共享函数对象的引用(函数就是对象)。如果你修改了共享的函数对象,那么由他所构造出的父类和子类都会受影响。

寄生继承

注:报错??

//父类vehicle
function 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;//1

var 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);//object

var 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.一切引用类型,都是对象。

构造函数中: 属性与方法为当前实例单独拥有,只能被当前实例访问,并且每声明一个实例,其中的方法都会被重新创建一次。

原型中: 属性与方法为所有实例共同拥有,可以被所有实例访问,新声明实例不会重复创建方法。

模块作用域(闭包)中:属性和方法不能被任何实例访问,但是能被内部方法访问,新声明的实例,不会重复创建相同的方法。

二。对象与函数

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的属性,指向这个函数本身。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-65Y1ZCxz-1598887833519)(https://git.oschina.net/uploads/images/2017/0721/130643_29678f33_1364780.png “6.png”)]

原型作为对象,是属性的集合。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fBqUQtUf-1598887833520)(https://git.oschina.net/uploads/images/2017/0721/130756_c30edc68_1364780.png “7.png”)]

  • 在原型prototype中,添加自定义属性

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h59nucpt-1598887833521)(https://git.oschina.net/uploads/images/2017/0721/130908_c2c57b7d_1364780.png “8.png”)]

  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

通过下面代码查看原型属性:

var obj={};
console.log(obj.__proto__)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xMgSGWpk-1598887833521)(https://git.oschina.net/uploads/images/2017/0721/131032_9ca1422c_1364780.png “9.png”)]

4.根据定律一,有:

obj这个对象本质上是被Object函数创建的,因此obj.__proto=== Object.prototype。我们可以用一个图来表示。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uGY556FT-1598887833523)(https://git.oschina.net/uploads/images/2017/0721/131820_068fe064_1364780.png “10.png”)]

5.定律二:Object.prototype的__proto__指向的是null,切记切记!

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jANBTfTi-1598887833523)(https://git.oschina.net/uploads/images/2017/0721/131853_d732aa71_1364780.png “11.png”)]

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。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QHVsTvz5-1598887833524)(https://git.oschina.net/uploads/images/2017/0721/132029_d2978382_1364780.png “12.png”)]

注意:Function.prototype指向的对象也是一个普通的被Object创建的对象

如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CObA9ASv-1598887833525)(https://git.oschina.net/uploads/images/2017/0721/134935_ac195e2a_1364780.png “14.png”)]

四。instanceof

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TFuf1XKN-1598887833526)(https://git.oschina.net/uploads/images/2017/0721/135114_1feb25b0_1364780.png “15.png”)]

上图中,f1这个对象是被Foo创建,但是“f1 instanceof Object”为什么是true呢?

原因如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q8CqwKT4-1598887833527)(https://git.oschina.net/uploads/images/2017/0721/135323_1b00193d_1364780.png “16.png”)]

Instanceof运算符的第一个变量是一个对象,暂时称为A;第二个变量一般是一个函数,暂时称为B。

Instanceof的判断队则是:沿着A的__proto__这条线来找,同时沿着B的prototype这条线来找,如果两条线能找到同一个引用,即同一个对象,那么就返回true。如果找到终点还未重合,则返回false。

同理可以解释:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xvxWol9E-1598887833527)(https://git.oschina.net/uploads/images/2017/0721/135558_96fc7d88_1364780.png “17.png”)]

具体原因请看下图(结合图+之前图片):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a0f27DYu-1598887833528)(https://git.oschina.net/uploads/images/2017/0721/135749_d0408aa1_1364780.png “18.png”)]

——————————————

原型链规则总结:

1.每个对象下都有一个属性__proto__,这个__proto__就叫原型链。

2.关系

实例下的__proto__ === 构造函数的原型(默认情况)

3.先看对象上有没有,如果没有,就通过对象上的原型链,默认情况找实例的原型链。就等同于找构造函数的原型。

注意:实例对象的原型链有可能被修改,修改之后,按照修改的关系来

规则: f1.proto -> Fn.prototype

如果还找不到,会从Fn.prototype.__proto__上查找,而Fn.prototype。 又是一个对象,对象的__proto__指向Object.prototype,如果还没有,因为Object.prototype没有原型链了(Object.prototype.__proto__为null),所以就真的没有了。

五。继承

1.javascript中的继承是通过原型链来体现的。先看几句代码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-34x8gj5U-1598887833529)(https://git.oschina.net/uploads/images/2017/0721/140035_482c11ff_1364780.png “19.png”)]

2.上述现象的解释:
访问一个对象的属性时,先在基本属性中查找,如果没有,再沿着__proto__这条链向上找,这就是原型链。

上例图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cU0Lxbhd-1598887833530)(https://git.oschina.net/uploads/images/2017/0721/140143_2335937e_1364780.png “20.png”)]

上图中,访问f1.b时,f1的基本属性中没有b,于是沿着__proto__找到了Foo.prototype.b。

3.如何区分一个属性到底是基本的还是从原型中找到的呢?大家可能都知道答案了——hasOwnProperty,特别是在for…in…循环中,一定要注意。

注意: f1的这个hasOwnProperty方法来源

它是从Object.prototype中来的,请看图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aXKf8EOQ-1598887833540)(https://git.oschina.net/uploads/images/2017/0721/140608_6a6ed157_1364780.png “21.png”)]

对象的原型链是沿着__proto__这条线走的,因此在查找f1.hasOwnProperty属性时,就会顺着原型链一直查找到Object.prototype。

由于所有的对象的原型链都会找到Object.prototype,因此所有的对象都会有Object.prototype的方法。这就是所谓的“继承”。

当然这只是一个例子,你可以自定义函数和对象来实现自己的继承。

7.21记

文章目录

闭包与作用域

引入

1.if和for的花括号,不是域。

2.在浏览器中,全局执行环境被默认为Window对象,因此所有全局变量和函数都是作为Window对象的属性和方法创建的。某个执行环境中的所有代码执行完毕后,该环境被销毁,保存在其中的所有变量和函数定义也随之销毁。(全局执行环境直到应用程序退出,例如关闭网页或浏览器时,才销毁。)

3.每个函数独有自己的执行环境。

4.如果这个环境是函数,则将其活动对象作为变量对象。

5.内部环境可以通过作用域链访问所有的外部环境,但外部环境不能访问内部环境的任何变量和函数。----每个环境都可以向上搜索作用域链,以查询变量和函数名。但任何环境都不能通过向下搜索作用域链而进入另一个执行环境。

6.函数参数也被当作变量来对待,因此其访问规则与执行环境中的其他变量相同。

作用域(链)

1.浏览器预解析机制:

  • 变量、函数表达式——变量声明,默认赋值为undefined;
  • this——赋值;
  • 函数声明——赋值;

这三种数据的准备情况我们称之为“执行上下文”或者“执行上下文环境”。

2.我们在声明变量时,全局代码要在代码前端声明,函数中要在函数体一开始就声明好。除了这两个地方,其他地方都不要出现变量声明。而且建议用“单var”形式。

3.作用域

image

作用域有上下级的关系,上下级关系的确定就看函数是在哪个作用域下创建的。例如,fn作用域下创建了bar函数,那么“fn作用域”就是“bar作用域”的上级。

4.块级作用域实例:

image

作用域中变量的值是在执行过程中产生的确定的,而作用域却是在函数创建时就确定了。

所以,如果要查找一个作用域下某个变量的值,就需要找到这个作用域对应的执行上下文环境,再在其中寻找变量的值。

5.作用域链

第一步,现在当前作用域查找a,如果有则获取并结束。如果没有则继续;

第二步,如果当前作用域是全局作用域,则证明a未定义,结束;否则继续;

第三步,(不是全局作用域,那就是函数作用域)将创建该函数的作用域作为当前作用域;

第四步,跳转到第一步。

image

以上代码中:第13行,fn()返回的是bar函数,赋值给x。执行x(),即执行bar函数代码。取b的值时,直接在fn作用域取出。取a的值时,试图在fn作用域取,但是取不到,只能转向创建fn的那个作用域中去查找,结果找到了。

闭包的写法

(一)第一,函数作为返回值

image

如上代码,bar函数作为返回值,赋值给f1变量。执行f1(15)时,用到了fn作用域下的max变量的值。

(二)函数作为参数被传递

image

如上代码中,fn函数作为一个参数被传递进入另一个函数,赋值给f参数。执行f(15)时,max变量的取值是10,而不是100。

1.当一个函数被调用完成之后,其执行上下文环境将被销毁,其中的变量也会被同时销毁。

但是有些情况下,函数调用完成之后,其执行上下文环境不会接着被销毁。这就是需要理解闭包的核心内容。

2.闭包例子:

image

最后,执行完20行就是上下文环境的销毁过程,这里就不再赘述了。

闭包的应用

1. 惰性函数

外部函数调用之后,其变量对象本应该被销毁,但闭包的存在使我们仍然可以访问外部函数的变量对象。

function outer(){
    var a=1;
    return function(){
        return a;
    }
}

var b=outer();
console.log(b());//1
let obj=function(){
	let flag='getComputedStyle' in window; // 1.兼容性判断
	return { // 2.利用闭包结构,将flag保存在内存中。
		fn(){ // 3.因为obj值是fn,此后直接调用fn,读取外层flag,就无需再判断浏览器兼容性了
			if(flag){ 

			}
		}
	};
}();

function ajax() {
    if (window.XMLHttpRequest) {
        ajax = function () {
            let xhr = new XMLHttpRequest();
            //...
        }
    } else {
        ajax = function () {
            let xhr = new ActiveXObject();
            //...
        }
    }
    ajax();
}
ajax();
ajax();

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());//1
closure.setA(2);
console.log(closure.getA());//2

通过闭包,创建具有私有属性的实例对象。

特点

1.闭包只能取得包含函数中,任何变量的最后一个值。

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.

2.闭包中的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指向全局。

要把函数名和函数功能分割开

3.闭包的缺点

内存泄漏

闭包会引用包含函数的整个变量对象,如果闭包的作用域链中保存着一个HTML元素,那么就意味着该元素无法被销毁。所以我们要在元素操作完后,主动销毁:

function assignHandler(){
    var element=document.getElementById("someElement");
    var id=element.id;
    element.onclick=function(){
        alert(id);
    }
    element=null;//见分析
}

分析:getElementById返回对拥有指定 ID 的第一个对象的引用 ,当不再使用该对象时,只需给指定该对象的指针复制为null,利用js垃圾回收机制(会自动回收无引用的对象),来释放其所占内存

小结

写法

1.内部函数作为返回值,引用外部函数中的变量。 2.函数作为参数被传入

好处

1.希望一个变量长期保存内存中;

2.避免全局变量污染;

3.创建私有变量。

缺点

1.常驻内存,增加内存使用量;

2.使用不当造成内存泄漏。

三个特性

1.函数嵌套函数;

2.内部函数使用外部函数的参数和变量;

3.参数和变量不会被垃圾回收机制回收。

使用场景

(1)命名空间 (2) 惰性函数 (3)redux源码之: createStore combineReducers、中间件 (4)遍历子元素,绑定事件【见 你不知道的js】 (5)wbepack

7.23

构造函数

设计模式

工厂模式
构造函数模式
原型模式
组合使用构造函数模式和原型模式
动态原型模式
寄生构造函数模式
稳妥构造函数模式

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

改进: 构造函数模式


构造函数

// 按照惯例,构造函数以大写字母开头,以区别普通函数。
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.所有实例共享它所包含的属性和方法。

 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.构造函数模式用于定义实例属性,原型模式用于定义方法和共享的属性。结果,每个实例都会有自己的一份实例属性的副本,但同时又共享着对方法的引用,最大限度的节省了内存。另外,这种混成模式还支持向构造函数传递参数;

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);   //false
console.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.基本思想是创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回新创建的对象,但从表面看,这个函数又很像典型的构造函数。

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操作符调用构造函数。

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 Code
var cat=new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat.eat("fish"));
console.log(cat instanceof Cat);//true
console.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 项

递归

分析

  1. 假设已知 fib(n) 为第 n 项
  2. 递归关系
    • fib(n) = fib(n-1) + fib(n-2)
  3. 递归体
    function fib(n){
    return fib(n-1)+fib(n-2);
    }
  4. 临界条件
    * fib(1) == 1
    * fib(2) == 1
  5. 递归函数
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) * n
3. 递归体
function power(n,m){
    return power(n,m-1) * n;
}
4. 临界条件
    * m == 1 ,return n
    * m == 0 ,reutnr 1
5. 递归函数
function power(n,m){
    if(m == 1) return n;
    return power(n,m-1) * n;
}

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), //458
maxInNumbers = 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); //1
log(1,2); //1

上面方法可以解决最基本的需求,但是当传入参数的个数是不确定的时候,上面的方法就失效了,这个时候就可以考虑使用 apply 或者 call,注意这里传入多少个参数是不确定的,所以使用apply是最好的,方法如下:

function log(){
    console.log.apply(console, arguments);
};
log(1); //1
log(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(); //3

var 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();//null

var 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!

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6iwuvDUu-1598887833576)(https://git.oschina.net/uploads/images/2017/0725/150832_0965038a_1364780.jpeg “28.jpg”)]


前端基础进阶@简书波同学


(一):内存空间详细图解

1.在操作对象时,实际上是在操作对象的引用而不是实际的对象。因此,引用类型的值都是按引用访问的。

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] 作为对象存在于堆内存中

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yykSnI2v-1598887833578)(https://git.oschina.net/uploads/images/2017/0814/165454_f145cbb6_1364780.png “29.png”)]

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
var color = 'blue';

function changeColor() {
    var anotherColor = 'red';

    function swapColors() {
        var tempColor = anotherColor;
        anotherColor = color;
        color = tempColor;
    }

    swapColors();
}

changeColor();

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3aTgMfnW-1598887833581)(https://git.oschina.net/uploads/images/2017/0814/165517_7bf15cc5_1364780.png “30.png”)]

3.结论:

  • 单线程

同步执行,只有栈顶的上下文处于执行中,其他上下文需要等待

全局上下文只有唯一的一个,它在浏览器关闭时出栈

函数的执行上下文的个数没有限制

每次某个函数被调用,就会有个新的执行上下文为其创建,即使是调用的自身函数,也是如此。

function f1(){
    var n=999;
    function f2(){
        alert(n); 
    }
    return f2;
}
var result=f1();
result(); // 999

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9iDoxUZ3-1598887833586)(https://git.oschina.net/uploads/images/2017/0814/165540_8ed30da8_1364780.png “31.png”)]

(三)闭包

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();  // function

function 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)例子

  let add = (...args) => {
    // 收集所有参数
    let f = (...newArgs) => add(...([...args, ...newArgs]))
    // 最终调用toString时,计算最终的值
    f.toString = () => args.reduce((a, b) => a + b)
    return f
  }
  console.log(add(2)(6)(1).toString()) //22
 

参考: https://www.jianshu.com/p/2975c25e4d71 评论中20楼主

使用:redux思想 + redux源码之combineReducers


(六)拖拽封装

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

  1. 当我们想要确保某代码在谁谁之后执行时,我们可以利用函数调用栈,将我们想要执行的代码放入回调函数中。
    // 一个简单的封装
    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: 2
second: 3
third: 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.[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rvmYBQkY-1598887833590)(https://git.oschina.net/uploads/images/2017/0814/165608_3417ce16_1364780.jpeg “32…jpg”)]

(1)调用栈中遇到DOM操作、ajax请求以及setTimeout等WebAPIs的时候就会交给浏览器内核的其他模块进行处理(webkit内核在Javasctipt执行引擎之外,有一个重要的模块是webcore模块。对于图中WebAPIs提到的三种API,webcore分别提供了DOM Binding、network、timer模块来处理底层实现。)

(2)等到这些模块处理完这些操作的时候,将回调函数放入任务队列中

(3)等栈中的task执行完之后,再去执行任务队列之中的回调函数。

3.例子:事件循环机制究竟是怎么执行setTimeout的。

1.首先main()函数的执行上下文入栈

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sKbVhHVk-1598887833594)(https://git.oschina.net/uploads/images/2017/0814/165627_f10fb6c2_1364780.jpeg “33.jpg”)]

2.代码接着执行,遇到console.log(‘Hi’),此时log(‘Hi’)入栈,console.log方法只是一个webkit内核支持的普通的方法,所以log(‘Hi’)方法立即被执行。此时输出’Hi’。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DjRhGLqY-1598887833596)(https://git.oschina.net/uploads/images/2017/0814/165642_b634999c_1364780.png “34.png”)]

3.当遇到setTimeout的时候,执行引擎将其添加到栈中。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jbwsAuRP-1598887833600)(https://git.oschina.net/uploads/images/2017/0814/165655_4df65b12_1364780.png “35.png”)]

4.调用栈发现setTimeout是之前提到的WebAPIs中的API,因此将其出栈之后将延时执行的函数交给浏览器的timer模块进行处理。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aPIzcQh1-1598887833602)(https://git.oschina.net/uploads/images/2017/0814/165712_abe82207_1364780.jpeg “36.jpg”)]

5.timer模块去处理延时执行的函数,此时执行引擎接着执行将log(‘SJS’)添加到栈中,此时输出’SJS’。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7633la3G-1598887833604)(https://git.oschina.net/uploads/images/2017/0814/165727_e5e0b41a_1364780.png “37.png”)]

6.当timer模块中延时方法规定的时间到了之后就将其放入到任务队列之中,此时调用栈中的task已经全部执行完毕。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rTtcyP79-1598887833606)(https://git.oschina.net/uploads/images/2017/0814/165742_47600b16_1364780.png “38.png”)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AoYCM7Hs-1598887833609)(https://git.oschina.net/uploads/images/2017/0814/165759_5a52411c_1364780.png “39.png”)]

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。

==》如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Lu1QxqT5-1598887833612)(https://git.oschina.net/uploads/images/2017/0814/165819_166c453e_1364780.jpeg “40…jpg”)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KsDQuzHe-1598887833614)(https://git.oschina.net/uploads/images/2017/0814/165847_1de16b34_1364780.jpeg “41.jpg”)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SgkDNGP6-1598887833614)(https://git.oschina.net/uploads/images/2017/0814/165902_0800a078_1364780.jpeg “42…jpg”)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FKuCMABm-1598887833618)(https://git.oschina.net/uploads/images/2017/0814/165920_bc78ce8d_1364780.jpeg “43.jpg”)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sCQ2Ut43-1598887833619)(https://git.oschina.net/uploads/images/2017/0814/165934_3f715831_1364780.jpeg “44…jpg”)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FeoS0L6e-1598887833619)(https://git.oschina.net/uploads/images/2017/0814/165949_d76b5e0a_1364780.jpeg “45.jpg”)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Cz3PJNaq-1598887833621)(https://git.oschina.net/uploads/images/2017/0814/170005_307ed5ce_1364780.jpeg “46.jpg”)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pE6LqLxV-1598887833624)(https://git.oschina.net/uploads/images/2017/0814/170018_05e234b3_1364780.jpeg “47.jpg”)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IWZfN5cW-1598887833625)(https://git.oschina.net/uploads/images/2017/0814/170031_9318de3f_1364780.jpeg “48.jpg”)]

结论:

(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 constructor
var person = new Person('wdg');

理论上来说也是不能这么做的,因为箭头函数在创建时this对象就绑定了,更不会指向对象实例。

5.太简短的(难以理解)函数

箭头函数可以让语句写的非常的简洁,但是一个真实的项目,一般由多个开发者共同协作完成,就算由单人完成,后期也并不一定是同一个人维护,箭头函数有时候并不会让人很好的理解,比如

let multiply = (a, b) => b === undefined ? b => a * b : a * b;

let double = multiply(2);

double(3); // => 6

multiply(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); // => 6
multiply(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 2
let func = obj.func;
func(1,2); //   1 2
let func_ = func.bind(obj);
func_(1,2);// qianzhixiang 1 2
func(1,2);//   1 2
func.call(obj,1,2);// qianzhixiang 1 2
func.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 2
let func = obj.func;
func(1,2); //   1 2
let func_ = func.bind(obj);
func_(1,2);//  1 2
func(1,2);//   1 2
func.call(obj,1,2);// 1 2
func.apply(obj,[1,2]);//  1 2

因为箭头函数中的,apply、call方法指向的对象会被默认忽略。故对箭头函数,call、apply不能更改其this指向。


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值