整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

关于Css的所有讲解内容

SS课堂笔记

1、CSS的概念

层叠样式表(英文全称:Cascading Style Sheets) *层叠:多个样式可以作用在同一个html的元素上,同时生效

是一种用来表现HTML或XML(标准通用标记语言的一个子集)等文件样式的计算机语言。

  • 样式定义如何显示 HTML 元素
  • 样式通常存储在样式表
  • 把样式添加到 HTML 4.0 中,是为了解决内容与表现分离的问题
  • 外部样式表可以极大提高工作效率
  • 外部样式表通常存储在 CSS 文件
  • 多个样式定义可层叠为一个

CSS很像化妆,通过不同的CSS将同样的HTML内容打造为不同的呈现结果。

所以,前端程序员相互表白的时候可以说:you are the CSS to my HTML.

这是不是CSS是对HTML进行美化和布局作用的最好总结?

2、CSS的优势

  1. 功能强大
  2. 将内容展示和样式控制分离
  • 降低耦合度。解耦
  • 让分工协作更容易
  • 提高开发效率

3、CSS的使用:CSS与html结合使用

根据定义CSS的位置不同,分为行内样式、内部样式和外部样式

3.1 行内样式

也称为内联样式

直接在标签中编写样式,通过使用标签内部的style属性;

一般在测试的时候使用居多:
语法:
<html标签 style="样式1:值1;样式2:值2;....样式N:值N;">hello my css</html标签>
案例:
<div style="color: red;">hello my css</div>

弊端:只能对当前的标签生效,没有做到内容和样式相分离,耦合度太高。

3.2 内部样式

定义在head标签内,通过style标签,该标签内容就是CSS代码

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>内部样式</title>
        <style>
            div{
                color: red;
            }
        </style>
    </head>
    <body>
        <div>hello my css</div>
    </body>
</html>

3.3 外部样式

1、提前定义css资源文件

2、在head标签内,定义link标签引入外部样式文件。

lina.css文件,放在与html页面同级的css文件夹中:
div {
    color: red;
}

html页面中的引入
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>外部样式</title>
        <link rel="stylesheet" href="css/lina.css" />
    </head>
    <body>
        <div>hello my css</div>
    </body>
</html>

作用域的范围:外部样式表>内部样式表>行内样式表

优先级:外部样式表<内部样式表<行内样式表;

同样的样式作用在同一个标签身上:就近原则;不同样式作用在同一个标签身上:叠加生效。

4、CSS语法

基本格式:由两个主要的部分构成:选择器,以及一条或多条声明:

选择器 {
        属性1:值1;
        属性2:值2;
        ...
}
选择器:筛选具有相似特征的元素
属性和属性值之间用冒号分割,不同的属性之间用分号隔开。

例如:

5、CSS注释

注释是用来解释你的代码,并且可以随意编辑它,浏览器会忽略它。

CSS注释以 /* 开始, 以 */ 结束

/*这是CSS的注释*/
div {
    color: red;  /*文字颜色是红色*/
}

6、基本选择器:筛选具有相似特征的元素

6.1 id选择器

选择具有相同id属性值的元素,建议html页面中的id值唯一

id 选择器可以为标有特定 id 的 HTML 元素指定特定的样式。

HTML元素以id属性来设置id选择器,CSS 中 id 选择器以 "#" 来定义。

PS: ID属性不要以数字开头,数字开头的ID在 Mozilla/Firefox 浏览器中不起作用。

虽然多个元素可以使用同一个id选择器控制样式,但是不推荐。如果需要同样的样式对多个标签生效,使用class选择器。

6.2 class选择器

选择具有相同的class属性值的元素。

class 选择器用于描述一组元素的样式,class 选择器有别于id选择器,class可以在多个元素中使用。

class 选择器在HTML中以class属性表示, 在 CSS 中,类选择器以一个点"."号显示:

PS:类名的第一个字符不能使用数字!它无法在 Mozilla 或 Firefox 中起作用。

6.3 元素选择器/标签选择器

选择具有相同标签名称的元素。

定义选择器语法:标签名称{};PS:标签名称必须是html提供好的标签。

使用标签选择器:自动使用在所有的同名的标签上

7 优先级

7.1 选择器的优先级

ID选择器 > 类选择器 > 标签选择器

当多个选择器作用在同一个标签上的时候,如果属性冲突,看优先级;如果不冲突,样式叠加生效。

7.2 样式表的优先级

行内样式 > 内部样式 >外部样式

同样,三个样式表中都有内容作用在同一个html标签的时候,如果属性冲突,看优先级;如果不冲突,样式叠加生效。

8 CSS常用样式

8.1 color :字体颜色

跟颜色相关的取值分3种:

1、颜色的单词 red blue...

2、rgb(红,绿,蓝)三色的取值范围是0-255 rgb(255,0,0)

rgba(红,绿,蓝,透明度),透明度取值:0-1 0 全透明 1-不透明 0.5 半透明rgba(255,0,0,0.4)

3、#值1值2值3 :值的范式是00-FF 十六进制数字组成的 例如:#FF0000

8.2 width height:宽高

PS:只有块状元素可以设置宽高,行级元素设置不生效。

取值方式有2种:

1:数值 绝对数字 单位是像素PX

2:百分比:占据父元素的比例

8.3 背景样式

8.4 文本样式

8.5 列表样式

8.6 边框样式

10 HTML&CSS调试利器

以谷歌浏览器为例说明。

快捷键F12或者工具条中的开发者工具调出以下内容。

在elements中可以看到当前页面的所有标签,在styles中可以看到html元素对应的样式。


11 盒子模型

11.1 概念

所有HTML元素可以看作盒子,在CSS中,"box model"这一术语是用来设计和布局时使用。

CSS盒模型本质上是一个盒子,封装周围的HTML元素,它包括:边距,边框,填充,和实际内容。

盒模型允许我们在其它元素和周围元素边框之间的空间放置元素。

盒子模型说明图:

  • Margin(外边距) - 清除边框外的区域,外边距是透明的。
  • Border(边框) - 围绕在内边距和内容外的边框。
  • Padding(内边距) - 清除内容周围的区域,内边距是透明的。
  • Content(内容) - 盒子的内容,显示文本和图像。


11.2 盒子的宽度和高度

元素的实际宽度和高度:

当我们计算一个元素实际在页面占有的总宽度计算公式是这样的:

总元素宽度=宽度+左填充+右填充+左边框+右边框+左边距+右边距

元素的总高度最终计算公式是这样的:

总元素的高度=高度+顶部填充+底部填充+上边框+下边框+上边距+下边距

11.3 如果想要设置的宽度直接就是元素的实际宽度,通过box-sizing属性

12 补充常用样式

12.1 float 浮动

12.1.1 什么是浮动

CSS 的 Float(浮动),会使元素向左或向右移动,其周围的元素也会重新排列。

Float(浮动),往往是用于图像,但它在布局时一样非常有用。

12.1.2 元素怎样浮动

元素的水平方向浮动,意味着元素只能左右移动而不能上下移动。

一个浮动元素会尽量向左或向右移动,直到它的外边缘碰到包含框或另一个浮动框的边框为止。

浮动元素之后的元素将围绕它。

浮动元素之前的元素将不会受到影响。

12.1.3 彼此相邻的浮动元素

如果你把几个浮动的元素放到一起,如果有空间的话,它们将彼此相邻。

12.1.4 clear--清除浮动

元素浮动之后,周围的元素会重新排列,为了避免这种情况,使用 clear 属性。

clear 属性指定元素两侧不能出现浮动元素。

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>浮动</title>
        <style>
            div{
                width: 400px;
                height: 200px;
                margin-bottom: 10px;
            }
        </style>
    </head>
    <body>
        <!--没有浮动属性的元素都属于常规文档流:从上往下从左往右依次显示
            浮动的元素都脱离了常规文档流;
            为了好理解:大家可以认为浮动元素属于一层,非浮动元素属于一层
            如果想要非浮动元素不受浮动元素的影响,需要使用clear属性
        -->
        <div style="background: rgba(255,0,0,0.5); float: left;">
            div1-左浮动,脱离常规文档流,紧贴父元素或者上一个同方向浮动
        </div>
        <div style="background: lawngreen; width: 600px; height: 350px; ">
            div2-未浮动,常规文档流,
            <br/>PS:此时div1在div2的上方显示,因为div1和div2是不同文档流中的元素,显示互不影响
            如果不想让div2被浮动元素影响,需要添加clear属性。
            添加clear: left;之后div2就会忽略div1浮动的影响,在div1层后面显示,不会重叠了,大家可以自己试验一下
        </div>
        <div style="background: lightblue; float: right; width: 1800px;">
            div3-右浮动,脱离常规文档流,紧贴父元素或者上一个同方向浮动
        </div>
        <div style="background: lightcoral; width: 600px; height: 350px; ">
            div4-未浮动,常规文档流,
            <br/>PS:此时div3在div4的上方显示,因为div3和div4是不同文档流中的元素,显示互不影响
            如果不想让div4被浮动元素影响,需要添加clear属性。
            添加clear: right;之后div4就会忽略div3浮动的影响,在div3层后面显示,不会重叠了,大家可以自己试验一下
            
            clear属性有三个取值:left、right、both;分别是取出左浮动、有浮动和所有浮动元素的影响
        </div>
        <div style="background: lavender;">
            div5-未浮动,常规文档流,
        </div>
    </body>
</html>

12.2 overflow

控制内容溢出元素框时显示的方式。

overflow属性有以下值:

描述

visible

默认值。内容不会被修剪,会呈现在元素框之外。

hidden

内容会被修剪,并且其余内容是不可见的。

scroll

内容会被修剪,但是浏览器会显示滚动条以便查看其余的内容。

auto

如果内容被修剪,则浏览器会显示滚动条以便查看其余的内容。

inherit

规定应该从父元素继承 overflow 属性的值。

注意:overflow 属性只工作于指定高度的块元素上。

注意: 在 OS X Lion ( Mac 系统) 系统上,滚动条默认是隐藏的,使用的时候才会显示 (设置 "overflow:scroll" 也是一样的)。

12.3 Display(显示) 与 Visibility(可见性)

12.3.1 两者的区别

display属性设置一个元素应如何显示,visibility属性指定一个元素应可见还是隐藏。

隐藏一个元素可以通过把display属性设置为"none",或把visibility属性设置为"hidden"。但是请注意,这两种方法会产生不同的结果。

visibility:hidden可以隐藏某个元素,但隐藏的元素仍需占用与未隐藏之前一样的空间。也就是说,该元素虽然被隐藏了,但仍然会影响布局。

display:none可以隐藏某个元素,且隐藏的元素不会占用任何空间。也就是说,该元素不但被隐藏了,而且该元素原本占用的空间也会从页面布局中消失。

12.3.2 display 改变元素的类型

CSS样式有以下三个:

  • display:block -- 显示为块级元素
  • display:inline -- 显示为内联元素
  • display:inline-block -- 显示为内联块元素,表现为同行显示并可修改宽高内外边距等属性

13 复合选择器

由两个或多个基础选择器,通过不同方式组合而成的。

可以更准确更精细的选择目标元素标签。

13.1 全局选择器

语法:* {} 一般去掉标签的一些默认效果的时候使用,或者整站通用效果时使用。但是不推荐,一般将 * 替换为常用标签的名称,并用逗号分隔,其实就是使用并集选择器。

13.2 并集选择器

并集选择器(CSS选择器分组)是各个选择器通过,连接而成的,通常用于集体声明。

语法:选择器1,选择器2,......选择器N{}

意思是多个选择器都是通用的样式。任何形式的选择器(包括标签选择器、class类选择器id选择器等),都可以作为并集选择器的一部分。

13.3 交集选择器

条件:交集选择器由两个选择器构成,找到的标签必须满足:既有标签一的特点,也有标签二的特点。

语法:h3.class{ color:red; }

其中第一个为标签选择器,第二个为class选择器,两个选择器之间不能有空格,例如div.list。

交集选择器是并且的意思。 即...又...的意思

例如:   table.bg   选择的是: 类名为 .bg  的 表格标签,但用的相对来说比较少。

13.4 后代选择器

概念:后代选择器又称为包含选择器。

作用:用来选择元素或元素组的子孙后代。

其写法就是把外层标签写在前面,内层标签写在后面,中间用空格分隔,先写父亲爷爷,在写儿子孙子。

  格式:父级 子级{属性:属性值;属性:属性值;}

语法:.class h3{color:red;font-size:16px;}

当标签发生嵌套时,内层标签就成为外层标签的后代。

子孙后代都可以这么选择。 或者说,它能选择任何包含在内 的标签。

13.5 子元素选择器

作用:子元素选择器只能选择作为某元素子元素(亲儿子)的元素。

其写法就是把父级标签写在前面,子级标签写在后面,中间跟一个 > 进行连接。

语法:.class>h3{color:red;font-size:14px;}

 比如:  .demo > h3 {color: red;}   说明  h3 一定是demo 亲儿子。  demo 元素包含着h3。

13.6 实现代码

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title>复合选择器</title>
		<style>
			/*全局选择器:
			 	一般去掉标签的一些默认效果的时候使用,或者整站通用效果时使用。但是不推荐,
			 */
			*{
				color: #333;/*定义全局文字颜色,统一色彩基调*/
			}
			/*并集选择器:通常用于集体声明
			 	替换全局选择器;
			 */
			div,p,dldt,dd{
				/*去掉浏览器的默认样式*/
				margin: 0;
				padding: 0;
				color: #333;/*定义全局文字颜色,统一色彩基调*/
			}
			/*交集选择器*/
			li.myli{
				color: red;
			}
			/*后代选择器*/
			ul li{
				font-size: 28px;
			}
			.myUL li{
				font-family: "微软雅黑";
			}
			.myUL li a{
				text-decoration: line-through;
			}
			/*子元素选择器*/
			.demo>h3{
				color: red;
			}			
		</style>
		
	</head>
	<body>
		<ul>
			<li>li11111111111</li>
			<li class="myli">li22222222222</li>
			<li>li33333333333</li>
			<li>li44444444444<a href="">点击我试试</a></li>
			<li class="myUL">
				<ul>
					<li>li11111111111</li>
					<li class="myli">li22222222222</li>
					<li>li33333333333</li>
					<li>li44444444444
						<a href="">点击我试试</a>
					</li>
				</ul>
			</li>
		</ul>
		<ol>
			<li>li11111111111</li>
			<li>li22222222222</li>
			<li>li33333333333</li>
			<li>li44444444444</li>
		</ol>
		<div class="demo">
			div1
			<h3>静夜思</h3>	
			<ul>
				<li><h3>静夜思----li</h3></li>
			</ul>
		</div>
		
	</body>
</html>

13.6 伪类选择器

伪类选择器:和类选择器相区别类选择器是一个点 比如 .demo {} 而我们的伪类 用 2个点 就是 冒号 比如 :link{} 。

作用:用于向某些选择器添加特殊的效果。比如给链接添加特殊效果, 比如可以选择 第1个,第n个元素。

因为伪类选择器很多,比如链接伪类,结构伪类等等。我们这里先给大家讲解链接伪类选择器。

  • a:link /* 未访问的链接 */
  • a:visited /* 已访问的链接 */
  • a:hover /* 鼠标移动到链接上 */
  • a:active /* 选定的链接 */
<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title>伪类选择器</title>
		<style>			
			/*伪类选择器*/			
			a:link{
				color: red;/*默认颜色是红色*/	
			}
			a:visited{
				color: blue;/*访问过的页面是蓝色*/	
			}
			a:hover{
				color: green;/*鼠标悬浮是绿色*/	
				font-size: 28px;
			}
			a:active{
				color: gold;/*按下鼠标不放手是金色*/	
				font-family: "微软雅黑";
			}
		</style>
		
	</head>
	<body>
		<a href="03-常用样式.html" target="_blank">常用样式</a>
		<a href="04-盒子模型1.html" target="_blank">盒子模型</a>
		<a href="05-综合练习.html" target="_blank">综合练习</a>
	</body>
</html>

注意**

  • 写的时候,他们的顺序尽量不要颠倒 按照 lvha(四类的首字母) 的顺序。否则可能引起错误。
  • 因为叫链接伪类,所以都是利用交集选择器 a:link a:hover
  • 因为a链接浏览器具有默认样式,所以我们实际工作中都需要给链接单独指定样式。
  • 实际开发中,我们很少写全四个状态,一般我们写法如下:
a {   /* a是标签选择器  所有的链接 */
       font-weight: 700;
       font-size: 16px;
       color: gray;
}
a:hover {   /* :hover 是链接伪类选择器 鼠标经过 */
       color: red; /*  鼠标经过的时候,由原来的 灰色 变成了红色 */
}

13.7 复合选择器比对

选择器

作用

特征

使用情况

隔开符号及用法

后代选择器

用来选择元素后代

是选择所有的子孙后代

较多

符号是空格 p .one

子代选择器

选择 最近一级元素

只选亲儿子

较少

符号是> .nav>p

交集选择器

选择两个标签交集的部分

既是 又是

较少

没有符号 p.one

并集选择器

选择某些相同样式的选择器

可以用于集体声明

较多

符号是逗号 .nav, .header

链接伪类选择器

给链接更改状态


较多

重点记住 a{} 和 a:hover 实际开发的写法


his是JavaScript中的一个关键字,但是又一个相对比较特别的关键字,不像function、var、for、if这些关键字一样,可以很清楚的搞清楚它到底是如何使用的。

this会在执行上下文中绑定一个对象,但是是根据什么条件绑定的呢?在不同的执行条件下会绑定不同的对象,这也是让人捉摸不定的地方。

这一次,我们一起来彻底搞定this到底是如何绑定的吧!

一. 理解this

1.1. 为什么使用this

在常见的编程语言中,几乎都有this这个关键字(Objective-C中使用的是self),但是JavaScript中的this和常见的面向对象语言中的this不太一样:

  • 常见面向对象的编程语言中,比如Java、C++、Swift、Dart等等一系列语言中,this通常只会出现在类的方法中。
  • 也就是你需要有一个类,类中的方法(特别是实例方法)中,this代表的是当前调用对象。
  • 但是JavaScript中的this更加灵活,无论是它出现的位置还是它代表的含义。

使用this有什么意义呢?下面的代码中,我们通过对象字面量创建出来一个对象,当我们调用对象的方法时,希望将对象的名称一起进行打印。

如果没有this,那么我们的代码会是下面的写法:

  • 在方法中,为了能够获取到name名称,必须通过obj的引用(变量名称)来获取。
  • 但是这样做有一个很大的弊端:如果我将obj的名称换成了info,那么所有的方法中的obj都需要换成info。
var obj = {
  name: "why",
  running: function() {
    console.log(obj.name + " running");
  },
  eating: function() {
    console.log(obj.name + " eating");
  },
  studying: function() {
    console.log(obj.name + " studying");
  }
}

事实上,上面的代码,在实际开发中,我们都会使用this来进行优化:

  • 当我们通过obj去调用running、eating、studying这些方法时,this就是指向的obj对象
var obj = {
  name: "why",
  running: function() {
    console.log(this.name + " running");
  },
  eating: function() {
    console.log(this.name + " eating");
  },
  studying: function() {
    console.log(this.name + " studying");
  }
}

所以我们会发现,在某些函数或者方法的编写中,this可以让我们更加便捷的方式来引用对象,在进行一些API设计时,代码更加的简洁和易于复用。

当然,上面只是应用this的一个场景而已,开发中使用到this的场景到处都是,这也是为什么它不容易理解的原因。

1.2. this指向什么

我们先说一个最简单的,this在全局作用域下指向什么?

  • 这个问题非常容易回答,在浏览器中测试就是指向window
  • 所以,在全局作用域下,我们可以认为this就是指向的window
console.log(this); // window

var name = "why";
console.log(this.name); // why
console.log(window.name); // why

但是,开发中很少直接在全局作用域下去使用this,通常都是在函数中使用

所有的函数在被调用时,都会创建一个执行上下文:

  • 这个上下文中记录着函数的调用栈、函数的调用方式、传入的参数信息等;
  • this也是其中的一个属性;

我们先来看一个让人困惑的问题:

  • 定义一个函数,我们采用三种不同的方式对它进行调用,它产生了三种不同的结果
// 定义一个函数
function foo() {
  console.log(this);
}

// 1.调用方式一: 直接调用
foo(); // window

// 2.调用方式二: 将foo放到一个对象中,再调用
var obj = {
  name: "why",
  foo: foo
}
obj.foo() // obj对象

// 3.调用方式三: 通过call/apply调用
foo.call("abc"); // String {"abc"}对象

上面的案例可以给我们什么样的启示呢?

  • 1.函数在调用时,JavaScript会默认给this绑定一个值;
  • 2.this的绑定和定义的位置(编写的位置)没有关系;
  • 3.this的绑定和调用方式以及调用的位置有关系;
  • 4.this是在运行时被绑定的;

那么this到底是怎么样的绑定规则呢?一起来学习一下吧

二. this绑定规则

我们现在已经知道this无非就是在函数调用时被绑定的一个对象,我们就需要知道它在不同的场景下的绑定规则即可。

2.1. 默认绑定

什么情况下使用默认绑定呢?独立函数调用。

  • 独立的函数调用我们可以理解成函数没有被绑定到某个对象上进行调用;

案例一:普通函数调用

  • 该函数直接被调用,并没有进行任何的对象关联;
  • 这种独立的函数调用会使用默认绑定,通常默认绑定时,函数中的this指向全局对象(window);
function foo() {
  console.log(this); // window
}

foo();

案例二:函数调用链(一个函数调用另外一个函数)

  • 所有的函数调用都没有被绑定到某个对象上;
// 2.案例二:
function test1() {
  console.log(this); // window
  test2();
}

function test2() {
  console.log(this); // window
  test3()
}

function test3() {
  console.log(this); // window
}
test1();

案例三:将函数作为参数,传入到另一个函数中

function foo(func) {
  func()
}

function bar() {
  console.log(this); // window
}

foo(bar);

我们对案例进行一些修改,考虑一下打印结果是否会发生变化:

  • 这里的结果依然是window,为什么呢?
  • 原因非常简单,在真正函数调用的位置,并没有进行任何的对象绑定,只是一个独立函数的调用;
function foo(func) {
  func()
}

var obj = {
  name: "why",
  bar: function() {
    console.log(this); // window
  }
}

foo(obj.bar);

2.2. 隐式绑定

另外一种比较常见的调用方式是通过某个对象进行调用的:

  • 也就是说它的调用位置中,是通过某个对象发起的函数调用。

案例一:通过对象调用函数

  • foo的调用位置是obj.foo()方式进行调用的
  • 那么foo调用时this会隐式地被绑定到obj对象上
function foo() {
  console.log(this); // obj对象
}

var obj = {
  name: "why",
  foo: foo
}

obj.foo();

案例二:案例一的变化

  • 我们通过obj2又引用了obj1对象,再通过obj1对象调用foo函数;
  • 那么foo调用的位置上其实还是obj1被绑定了this;
function foo() {
  console.log(this); // obj对象
}

var obj1 = {
  name: "obj1",
  foo: foo
}

var obj2 = {
  name: "obj2",
  obj1: obj1
}

obj2.obj1.foo();

案例三:隐式丢失

  • 结果最终是window,为什么是window呢?
  • 因为foo最终被调用的位置是bar,而bar在进行调用时没有绑定任何的对象,也就没有形成隐式绑定;
  • 相当于是一种默认绑定;
function foo() {
  console.log(this);
}

var obj1 = {
  name: "obj1",
  foo: foo
}

// 讲obj1的foo赋值给bar
var bar = obj1.foo;
bar();

2.3. 显示绑定

隐式绑定有一个前提条件:

  • 必须在调用的对象内部有一个对函数的引用(比如一个属性);
  • 如果没有这样的引用,在进行调用时,会报找不到该函数的错误;
  • 正是通过这个引用,间接地将this绑定到了这个对象上;

如果我们不希望在 对象内部 包含这个函数的引用,同时又希望在这个对象上进行强制调用,该怎么做呢?

  • JavaScript所有的函数都可以使用call和apply方法(这个和Prototype有关)。
    • 它们两个的区别这里不再展开;
    • 其实非常简单,第一个参数是相同的,后面的参数,apply为数组,call为参数列表;
  • 这两个函数的第一个参数都要求是一个对象,这个对象的作用是什么呢?就是给this准备的。
  • 在调用这个函数时,会将this绑定到这个传入的对象上。

因为上面的过程,我们明确地绑定了this指向的对象,所以称之为 显示绑定

2.3.1. call、apply

通过call或者apply绑定this对象

  • 显示绑定后,this就会明确地指向绑定的对象
function foo() {
  console.log(this);
}

foo.call(window); // window
foo.call({name: "why"}); // {name: "why"}
foo.call(123); // Number对象,存放时123

2.3.2. bind函数

如果我们希望一个函数总是显示的绑定到一个对象上,可以怎么做呢?

方案一:自己手写一个辅助函数(了解)

  • 我们手动写了一个bind的辅助函数
  • 这个辅助函数的目的是在执行foo时,总是让它的this绑定到obj对象上
function foo() {
  console.log(this);
}

var obj = {
  name: "why"
}

function bind(func, obj) {
  return function() {
    return func.apply(obj, arguments);
  }
}

var bar = bind(foo, obj);

bar(); // obj对象
bar(); // obj对象
bar(); // obj对象

方案二:使用Function.prototype.bind

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

var obj = {
  name: "why"
}

var bar = foo.bind(obj);

bar(); // obj对象
bar(); // obj对象
bar(); // obj对象

2.3.3. 内置函数

有些时候,我们会调用一些JavaScript的内置函数,或者一些第三方库中的内置函数。

  • 这些内置函数会要求我们传入另外一个函数;
  • 我们自己并不会显示的调用这些函数,而且JavaScript内部或者第三方库内部会帮助我们执行;
  • 这些函数中的this又是如何绑定的呢?

案例一:setTimeout

  • setTimeout中会传入一个函数,这个函数中的this通常是window
setTimeout(function() {
  console.log(this); // window
}, 1000);

为什么这里是window呢?

  • 这个和setTimeout源码的内部调用有关;
  • setTimeout内部是通过apply进行绑定的this对象,并且绑定的是全局对象;

案例二:数组的forEach

数组有一个高阶函数forEach,用于函数的遍历:

  • 在forEach中传入的函数打印的也是Window对象;
  • 这是因为默认情况下传入的函数是自动调用函数(默认绑定);
var names = ["abc", "cba", "nba"];
names.forEach(function(item) {
  console.log(this); // 三次window
});

我们是否可以改变该函数的this指向呢?

forEach参数

var names = ["abc", "cba", "nba"];
var obj = {name: "why"};
names.forEach(function(item) {
  console.log(this); // 三次obj对象
}, obj);

案例三:div的点击

如果我们有一个div元素:

  • 注意:省略了部分代码
  <style>
    .box {
      width: 200px;
      height: 200px;
      background-color: red;
    }
  </style>

  <div class="box"></div>

获取元素节点,并且监听点击:

  • 在点击事件的回调中,this指向谁呢?box对象;
  • 这是因为在发生点击时,执行传入的回调函数被调用时,会将box对象绑定到该函数中;
var box = document.querySelector(".box");
box.onclick = function() {
  console.log(this); // box对象
}

所以传入到内置函数的回调函数this如何确定呢?

  • 某些内置的函数,我们很难确定它内部是如何调用传入的回调函数;
  • 一方面可以通过分析源码来确定,另一方面我们可以通过经验(见多识广)来确定;
  • 但是无论如何,通常都是我们之前讲过的规则来确定的;

2.4. new绑定

JavaScript中的函数可以当做一个类的构造函数来使用,也就是使用new关键字。

使用new关键字来调用函数时,会执行如下的操作:

  • 1.创建一个全新的对象;
  • 2.这个新对象会被执行Prototype连接;
  • 3.这个新对象会绑定到函数调用的this上(this的绑定在这个步骤完成);
  • 4.如果函数没有返回其他对象,表达式会返回这个新对象;
// 创建Person
function Person(name) {
  console.log(this); // Person {}
  this.name = name; // Person {name: "why"}
}

var p = new Person("why");
console.log(p);

2.5. 规则优先级

学习了四条规则,接下来开发中我们只需要去查找函数的调用应用了哪条规则即可,但是如果一个函数调用位置应用了多条规则,优先级谁更高呢?

1.默认规则的优先级最低

毫无疑问,默认规则的优先级是最低的,因为存在其他规则时,就会通过其他规则的方式来绑定this

2.显示绑定优先级高于隐式绑定

显示绑定和隐式绑定哪一个优先级更高呢?这个我们可以测试一下:

  • 结果是obj2,说明是显示绑定生效了
function foo() {
  console.log(this);
}

var obj1 = {
  name: "obj1",
  foo: foo
}

var obj2 = {
  name: "obj2",
  foo: foo
}

// 隐式绑定
obj1.foo(); // obj1
obj2.foo(); // obj2

// 隐式绑定和显示绑定同时存在
obj1.foo.call(obj2); // obj2, 说明显式绑定优先级更高

3.new绑定优先级高于隐式绑定

  • 结果是foo,说明是new绑定生效了
function foo() {
  console.log(this);
}

var obj = {
  name: "why",
  foo: foo
}

new obj.foo(); // foo对象, 说明new绑定优先级更高

4.new绑定优先级高于bind

new绑定和call、apply是不允许同时使用的,所以不存在谁的优先级更高

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

var obj = {
  name: "obj"
}

var foo = new foo.call(obj);

new和call同时使用

但是new绑定是否可以和bind后的函数同时使用呢?可以

  • 结果显示为foo,那么说明是new绑定生效了
function foo() {
  console.log(this);
}

var obj = {
  name: "obj"
}

// var foo = new foo.call(obj);
var bar = foo.bind(obj);
var foo = new bar(); // 打印foo, 说明使用的是new绑定

优先级总结:

  • new绑定 > 显示绑定(bind)> 隐式绑定 > 默认绑定

三. this规则之外

我们讲到的规则已经足以应付平时的开发,但是总有一些语法,超出了我们的规则之外。(神话故事和动漫中总是有类似这样的人物)

3.1. 忽略显示绑定

如果在显示绑定中,我们传入一个null或者undefined,那么这个显示绑定会被忽略,使用默认规则:

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

var obj = {
  name: "why"
}

foo.call(obj); // obj对象
foo.call(null); // window
foo.call(undefined); // window

var bar = foo.bind(null);
bar(); // window

3.2. 间接函数引用

另外一种情况,创建一个函数的 间接引用,这种情况使用默认绑定规则。

我们先来看下面的案例结果是什么?

  • (num2 = num1)的结果是num1的值;
var num1 = 100;
var num2 = 0;
var result = (num2 = num1);
console.log(result); // 100

我们来下面的函数赋值结果:

  • 赋值(obj2.foo = obj1.foo)的结果是foo函数;
  • foo函数被直接调用,那么是默认绑定;
function foo() {
  console.log(this);
}

var obj1 = {
  name: "obj1",
  foo: foo
}; 

var obj2 = {
  name: "obj2"
}

obj1.foo(); // obj1对象
(obj2.foo = obj1.foo)();  // window

3.3. ES6箭头函数

在ES6中新增一个非常好用的函数类型:箭头函数

  • 这里不再具体介绍箭头函数的用法,可以自行学习。

箭头函数不使用this的四种标准规则(也就是不绑定this),而是根据外层作用域来决定this。

我们来看一个模拟网络请求的案例:

  • 这里我使用setTimeout来模拟网络请求,请求到数据后如何可以存放到data中呢?
  • 我们需要拿到obj对象,设置data;
  • 但是直接拿到的this是window,我们需要在外层定义:var _this = this
  • 在setTimeout的回调函数中使用_this就代表了obj对象
var obj = {
  data: [],
  getData: function() {
    var _this = this;
    setTimeout(function() {
      // 模拟获取到的数据
      var res = ["abc", "cba", "nba"];
      _this.data.push(...res);
    }, 1000);
  }
}

obj.getData();

上面的代码在ES6之前是我们最常用的方式,从ES6开始,我们会使用箭头函数:

  • 为什么在setTimeout的回调函数中可以直接使用this呢?
  • 因为箭头函数并不绑定this对象,那么this引用就会从上层作用域中找到对应的this
var obj = {
  data: [],
  getData: function() {
    setTimeout(() => {
      // 模拟获取到的数据
      var res = ["abc", "cba", "nba"];
      this.data.push(...res);
    }, 1000);
  }
}

obj.getData();

思考:如果getData也是一个箭头函数,那么setTimeout中的回调函数中的this指向谁呢?

  • 答案是window;
  • 依然是不断的从上层作用域找,那么找到了全局作用域;
  • 在全局作用域内,this代表的就是window
var obj = {
  data: [],
  getData: () => {
    setTimeout(() => {
      console.log(this); // window
    }, 1000);
  }
}

obj.getData();

四. this面试题

4.1. 面试题一:

var name = "window";
var person = {
  name: "person",
  sayName: function () {
    console.log(this.name);
  }
};
function sayName() {
  var sss = person.sayName;
  sss(); 
  person.sayName(); 
  (person.sayName)(); 
  (b = person.sayName)(); 
}
sayName();

这道面试题非常简单,无非就是绕一下,希望把面试者绕晕:

function sayName() {
  var sss = person.sayName;
  // 独立函数调用,没有和任何对象关联
  sss(); // window
  // 关联
  person.sayName(); // person
  (person.sayName)(); // person
  (b = person.sayName)(); // window
}

4.2. 面试题二:

var name = 'window'
var person1 = {
  name: 'person1',
  foo1: function () {
    console.log(this.name)
  },
  foo2: () => console.log(this.name),
  foo3: function () {
    return function () {
      console.log(this.name)
    }
  },
  foo4: function () {
    return () => {
      console.log(this.name)
    }
  }
}

var person2 = { name: 'person2' }

person1.foo1(); 
person1.foo1.call(person2); 

person1.foo2();
person1.foo2.call(person2);

person1.foo3()();
person1.foo3.call(person2)();
person1.foo3().call(person2);

person1.foo4()();
person1.foo4.call(person2)();
person1.foo4().call(person2);

下面是代码解析:

随着现代网页应用的复杂性日益增加,传统的窗口管理工具已无法满足开发者和用户的需求。WinBox,一款前沿的 HTML5 网络窗口管理器,以其轻量级、高性能和完全可定制的特性,正引领着界面管理的革新。

一、轻量级与高性能

在追求速度和效率的时代,WinBox 以极简的核心打造,体积轻巧却功能强大。它通过优化的代码和算法,确保在各种设备和平台上都能提供流畅的体验。无论是桌面环境还是移动设备,WinBox 均能无缝集成,实现快速响应。

二、无依赖项

WinBox 设计之初就致力于独立性,无需额外插件或库的支持即可运行。这意味着更少的兼容性问题,以及更易于集成到任何现有的网页项目中。开发团队可以专注于功能的实现,而不必担心底层的支撑问题。

三、完全可定制

定制化是 WinBox 的一大亮点。它提供了丰富的 API 和样式选项,允许开发者根据自己的品牌和用户体验需求,调整窗口管理器的外观和行为。从颜色主题到动画效果,从布局结构到交互逻辑,WinBox 让每一个细节都尽在掌握。

四、开源优势

作为一款开源工具,WinBox 拥有活跃的社区支持和不断的功能迭代。开源意味着透明度和参与度,任何人都可以为项目贡献代码,报告 bug,或是提出新的想法。这样的协作精神确保了 WinBox 能够不断进化,适应未来的挑战。

五、应用场景

无论是管理系统界面、开发复杂的单页应用(SPA),还是创建交互式的数据可视化平台,WinBox 都能够提供坚实的基础。它的灵活性和易用性使得从初创公司到大型企业,各种规模的开发团队都可以从中受益。

使用示例

<!DOCTYPE html>
<html>
<head>
    <title>WinBox.js 示例</title>
    <link rel="stylesheet" type="text/css" href="winbox.min.css">
    <script src="winbox.min.js"></script>
</head>
<body>
    <button onclick="openWindow()">打开窗口</button>

    <script>
        function openWindow() {
            new WinBox({
                title: '示例窗口',
                width: 300,
                height: 200,
                content: '这是一个使用 WinBox.js 创建的窗口。'
            });
        }
    </script>
</body>
</html>


在上面的代码中,我们首先引入了 WinBox.js 的样式表和脚本文件。然后,在页面上添加了一个按钮,点击该按钮将调用 openWindow 函数来创建一个新的窗口。

在 openWindow 函数中,我们使用 new WinBox() 构造函数创建了一个新的窗口对象。通过传递一个配置对象作为参数,我们可以设置窗口的标题、宽度、高度和内容等属性。在这个例子中,我们设置了窗口的标题为 "示例窗口",宽度为 300 像素,高度为 200 像素,并指定了窗口的内容为一段文本。

当用户点击按钮时,将会弹出一个新窗口,显示指定的标题和内容。你可以根据需要进一步自定义窗口的外观和行为,例如添加按钮、表单或其他交互元素。建议查看官网更过示例:

WinBox.js  Modern HTML5 Window Manager

结语

WinBox 是一个强大的HTML5窗口管理器。随着 HTML5 技术的不断成熟,WinBox 也将继续发展,为开发者提供一个更加强大、灵活和高效的开发工具。现在就加入 WinBox 社区,共同塑造网络界面管理的未来。