跟着pink老师课程写的笔记
var e = 10;
var f = e++ + ++e;
// 1. e++ = 10 e = 11
// 2. e = 12 ++e = 12
console.log(f); // 22
有假必假,两边都是true才返回true
对于数值和表达式:
语法: 表达式1 && 表达式2
如果第一个表达式的值为真,则返回表达式2
如果第一个表达式的值为假,则返回表达式1
console.log( 123 && 456 ); // 456
console.log( 0 && 456 ); // 0
console.log( 123 && 456&& 789 ); // 789
有真必真,两边都是false才返回false
对于数值和表达式:
l语法: 表达式1 || 表达式2
l 如果第一个表达式的值为真,则返回表达式1
l 如果第一个表达式的值为假,则返回表达式2
console.log( 123 || 456 ); // 123
console.log( 0 || 456 ); // 456
console.log( 123 || 456 || 789 ); // 123
优先级从上到下依次递减,最上面具有最高的优先级,逗号操作符具有最低的优先级。
①布尔类型相加减
true转化为数值1,false转化为数值0,进行数值运算
②字符型相加减
相加首尾连接
相减是根据Number()方法将字符型转化为数值型,再进行减法。如果转化后值为NaN,则最终结果为NaN
与字符串相加都会变成字符串
数值型和数值字符串相加减,数值字符串会转化为数值,从而结果是数值
数值型和非数值字符串相减,根据Number()方法将字符型转化为数值型,结果是NaN(一般的非数值)
布尔类型和数值相加减,true转化为数值1,false转化为数值0,进行数值运算,结果是数值
undefined和数值相加减结果是NaN
var result = 0.1 + 0.2; // 结果不是 0.3,而是:0.30000000000000004
console.log(0.07 * 100); // 结果不是 7, 而是:7.000000000000001
所以:不要直接判断两个浮点数是否相等 !
函数都是有返回值的
break:结束当前的循环体(如?for、while)
continue:跳出本次循环,继续执行下次循环(如?for、while)
return:不仅可以退出循环,还能够返回?return?语句中的值,同时还可以结束当前的函数体内的代码
当我们不确定有多少个参数传递的时候,可以用arguments来获取(原来形参的位置留空)。
在JavaScript中,arguments实际上它是当前函数的一个内置对象。所有函数都内置了一个arguments对象,arguments对象中存储了传递的所有实参。
伪数组:
length
属性function maxValue() {
var max = arguments[0];
for (var i = 0; i < arguments.length; i++) {
if (max < arguments[i]) {
max = arguments[i];
}
}
return max;
}
console.log(maxValue(2, 4, 5, 9));
console.log(maxValue(12, 4, 9));
// 声明定义方式
function fn() {
...}
// 调用
fn()
因为有名字,所以也被称为命名函数
调用函数的代码既可以放到声明函数的前面,也可以放在声明函数的后面
// 这是函数表达式写法,匿名函数后面跟分号结束
var fn = function(){
...};
// 调用的方式,函数调用必须写到函数体下面
fn();
其中fn是变量名不是函数名
因为函数没有名字,所以也被称为匿名函数
这个fn里面存储的是一个函数
函数表达式方式原理跟声明变量方式是一致的
函数调用的代码必须写到函数体后面
作用于所有代码执行的环境(整个script标签内部)或者一个独立的js文件。
在全局作用域下声明的变量叫做全局变量(在函数外部定义的变量)。
作用于函数内的代码环境,就是局部作用域。?因为跟函数有关系,所以也称为函数作用域。
在局部作用域下声明的变量叫做局部变量(在函数内部定义的变量)
var
声明的变量是局部变量块作用域由{}
包括。
Js中没有块级作用域(在ES6之前)
l 只要是代码,就至少有一个作用域
l 写在函数内部的局部作用域
l 如果函数中还有函数,那么在这个作用域中就又可以诞生一个作用域
l 根据在内部函数可以访问外部函数变量的这种机制,用链式查找决定哪些数据能被内部函数访问,就称作作用域链
function f1() {
var num = 123;
function f2() {
console.log( num );
}
f2();
}
var num = 456;
f1();
//输出123,就近原则(一层一层往外查找)
作用域链:采取就近原则的方式来查找变量最终的值。
console.log(num); // 1.报错,num is not defined
console.log(num);
var num = 2; //2.输出 undefined
fn();
function fn() {
console.log('打印');
} //3.输出 打印
fn();
var fn2 = function() {
console.log('想不到吧');
} //4.报错 fn2 is not a function
JS代码是由浏览器中的JS解析器来执行的。
JS解析器在运行JS代码的时候分为两步:预解析
和代码执行
。
fn();
var fn2 = function() {
console.log('想不到吧');
}
//相当于
var fn2;
fn();
fn2 = function(){
......}
预解析有 变量预解析 和 函数预解析
// 案例4
f1();
console.log(c);
console.log(b);
console.log(a);
function f1() {
var a = b = c = 9;
console.log(a);
console.log(b);
console.log(c);
}
// 相当于以下代码
function f1() {
var a;//局部变量
a = b = c = 9;
// 相当于 var a = 9; b = 9; c = 9;
// b 和 c 直接赋值 没有var 声明 当 全局变量看
// 集体声明 var a = 9, b = 9, c = 9;
console.log(a);
console.log(b);
console.log(c);
}
f1();
console.log(c);
console.log(b);
console.log(a);
//结果:9 9 9 9 9 报错
在JavaScript中,对象是一组无序的相关属性和方法的集合,所有的事物都是对象,例如字符串、数值、数组、函数等。
对象是由属性和方法组成的。
l 属性:事物的特征,在对象中用属性来表示(常用名词)
l 方法:事物的行为,在对象中用方法来表示(常用动词)
例如保存一个人的完整信息,可以用数组
var arr = [‘张三疯’, ‘男', 128,154];
用 对象 :
person.name = ‘张三疯';
person.sex = ‘男';
person.age = 128;
person.height = 154;
对象字面量:就是花括号{}里面包含了表达这个具体事物(对象)的属性和方法。
{}里面采取键值对的形式表示
l 键:相当于属性名
l 值:相当于属性值,可以是任意类型的值(数字类型、字符串类型、布尔类型,函数类型等)
var star = {
name : 'pink',
age : 18,
sex : '男',
sayHi : function(){
alert('大家好啊~');
}
};
var andy = new Obect();
andy.name = 'pink';
andy.age = 18;
andy.sex = '男';
andy.sayHi = function(){
alert('大家好啊~');
}
构造函数:是一种特殊的函数,主要用来初始化对象,即为对象成员变量赋初始值,它总与new运算符一起使用。我们可以把对象中一些公共的属性和方法抽取出来,然后封装到这个函数里面。
在js中,使用构造函数要时要注意以下两点:
function Star(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
var ldh = new Star('刘德华', 18, '男');
console.log(ldh);
变量:单独声明赋值,单独存在
属性:对象里面的变量称为属性,不需要声明,用来描述该对象的特征
函数:单独存在的,通过“函数名()”的方式就可以调用
方法:对象里面的函数称为方法,方法不需要声明,使用“对象.方法名()”的方式就可以调用,方法用来描述该对象的行为和功能。
for…in语句用于对数组或者对象的属性进行循环操作。
for (变量 in 对象名字) {
//在此执行代码
}
语法中的变量是自定义的,它需要符合命名规范,通常我们会将这个变量写为k或者key。
for (var k in obj) {
console.log(k); // 这里的 k 是属性名
console.log(obj[k]); // 这里的 obj[k] 是属性值
}
Math.PI // 圆周率
Math.floor() // 向下取整,往小取值
Math.floor(1.1) //1
Math.floor(-1.1)//-2
Math.ceil() // 向上取整,往大取值
Math.round() // 四舍五入版 就近取整 注意 -3.5 结果是 -3(往大取)
Math.abs() // 绝对值
Math.max()/Math.min() // 求最大和最小值
random()//随机返回一个小数,其取值范围是[0,1),左闭右开0<=x<1
随机数方法
得到一个两数之间的随机数
function getRandomArbitrary(min, max) {
return Math.random() * (max - min) + min;
}
//随机数乘以两数之差,随机取得两数之差之间的数,加上最小可取的数
得到一个两数之间的随机整数
这个例子返回了一个在指定值之间的随机整数。这个值不小于 min
(如果 min
不是整数,则不小于 min
的向上取整数),且小于(不等于)max
。
function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min)) + min;
//不含最大值,含最小值
}
得到一个两数之间的随机整数,包括两个数在内
function getRandomIntInclusive(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
//含最大值,含最小值
}
日期对象是构造函数,所以我们需要实例化后才能使用(要new)
var date = new Date();
var year = date.getFullYear();
var month = date.getMonth() + 1;
var dates = date.getDate();
var days = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'];
var day = date.getDay();
var h = date.getHours();
h = h < 10 ? '0' + h : h;
var m = date.getMinutes();
m = m < 10 ? '0' + m : m;
var s = date.getSeconds();
s = s < 10 ? '0' + s : s;
console.log('今天是' + year + '年' + month + '月' + dates + '日' + days[day] + ' ' + h + ':' + m + ':' + s);
instanceof运算符,可以判断一个对象是否属于某种类型
Array.isArray()用于判断一个对象是否为数组,isArray()是HTML5中提供的方法
var arr = [1, 23];
var obj = {
};
console.log(arr instanceof Array); // true
console.log(obj instanceof Array); // false
console.log(Array.isArray(arr)); // true
console.log(Array.isArray(obj)); // false
var arr = [1500, 1200, 2000, 2100, 1800];
var newArr = [];
for (var i = 0; i < arr.length; i++) {
if (arr[i] < 2000) {
newArr.push(arr[i]);
}
}
console.log(newArr);
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0FliWGdO-1618047172807)(https://i.loli.net/2021/03/23/B2VurRYyx3D7wtn.png)]
var arr = [1, 64, 9, 6];
arr.sort(function(a, b) {
return b - a; // 降序
// return a - b; // 升序
});
console.log(arr)
lastIndexOf()从后面开始查找
arr.join(';')
arr.toString()
为了方便操作基本数据类型,JavaScript?还提供了三个特殊的引用类型:String、Number和Boolean。
基本包装类型就是把简单数据类型包装成为复杂数据类型,这样基本数据类型就有了属性和方法。
//?下面代码有什么问题?
var str = 'andy';
console.log(str.length);
按道理基本数据类型是没有属性和方法的,而对象才有属性和方法,但上面代码却可以执行,这是因为js会把基本数据类型包装为复杂数据类型,其执行过程如下:
//1.生成临时变量,把简单类型包装为复杂数据类型
var temp = new String('andy');
//2.赋值给我们声明的字符变量
str = temp;
//3.销毁临时变量
temp = null;
指的是里面的值不可变,虽然看上去可以改变内容,但其实是地址变了,内存中新开辟了一个内存空间。
var str = 'abc';
str = 'hello';
// 当重新给 str 赋值的时候,常量'abc'不会被修改,依然在内存中
// 重新给字符串赋值,会重新在内存中开辟空间,这个特点就是字符串的不可变
// 由于字符串的不可变,在大量拼接字符串的时候会有效率问题
var str = '';
for (var i = 0; i < 100000; i++) {
str += i;
}
console.log(str); // 这个结果需要花费大量时间来显示,因为需要不断的开辟新的空间
var str = '改革春风吹满地,中国人民真争气!'
console.log(str.indexOf('中'));
console.log(str.indexOf('中', 5));
//输出结果是一样的,因为他只是不查找了满字之前(不包括满字)的字符
replace()方法用于在字符串中用一些字符替换另一些字符。
replace(被替换的字符串,要替换为的字符串);
只替换第一个字符
var str = 'a,b,c,d';
console.log(str.split(',')); // 返回的是一个数组 [a, b, c, d]
toUpperCase() //转换大写
toLowerCase()//转换小写
简单类型又叫做基本数据类型或者值类型,复杂类型又叫做引用类型。
1、栈(操作系统):由操作系统自动分配释放存放函数的参数值、局部变量的值等。其操作方式类似于数据结构中的栈;
简单数据类型存放到栈里面
2、堆(操作系统):存储复杂类型(对象),一般由程序员分配释放,若程序员不释放,由垃圾回收机制回收。
复杂数据类型存放到堆里面
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3lTujijq-1618047249923)(https://i.loli.net/2021/03/25/RlZ9jrpVnGUhTcJ.png)]
注意:JavaScript中没有堆栈的概念,通过堆栈的方式,可以让大家更容易理解代码的一些执行方式,便于将来学习其他语言。
值类型(简单数据类型):string,number,boolean,undefined,null
值类型变量的数据直接存放在变量(栈空间)中
引用类型变量(栈空间)里存放的是地址,真正的对象实例存放在堆空间
函数的形参也可以看做是一个变量,当我们把一个值类型变量作为参数传给函数的形参时,其实是把变量在栈空间里的值复制了一份给形参,那么在方法内部对形参做任何修改,都不会影响到的外部变量。
function fn(a) {
a++;
console.log(a);//这里输出11
}
var x = 10;
fn(x);
console.log(x);//这里输出10
函数的形参也可以看做是一个变量,当我们把引用类型变量传给形参时,其实是把变量在栈空间里保存的堆地址复制给了形参,形参和实参其实保存的是同一个堆地址,所以操作的是同一个对象。
function Person(name) {
this.name = name;
}
function f1(x) {
// x = p
console.log(x.name); // 2. 输出刘德华
x.name = "张学友";
console.log(x.name); // 3. 输出张学友
}
var p = new Person("刘德华");
console.log(p.name); // 1. 输出刘德华
f1(p);
console.log(p.name); // 4. 输出张学友
文档对象模型(Document Object Model,简称 DOM),是 W3C 组织推荐的处理可扩展标记语言(HTML或者XML)的标准编程接口。
W3C 已经定义了一系列的 DOM 接口,通过这些 DOM 接口可以改变网页的内容、结构和样式。
DOM树 又称为文档树模型,把文档映射成树形结构,通过节点对象对其处理,处理的结果可以加入到当前的页面。
DOM 把以上内容都看做是对象
语法:document.getElementById(id)
作用:根据ID获取元素对象
参数:id值,区分大小写的字符串
返回值:元素对象 或 null
语法:document.getElementsByTagName('标签名') 或者 element.getElementsByTagName('标签名')
作用:根据标签名获取元素对象
参数:标签名
返回值:元素对象集合(伪数组,数组元素是元素对象)
注意:
因为得到的是一个对象的集合,所以我们想要操作里面的元素就需要遍历。
得到元素对象是动态的
只获取到一个元素时,返回的依旧时伪数组,只是数组对象只有一个
若没有获取到元素,返回的是空的伪数组
案例代码
<body>
<div id="time">2019-9-9div>
<ul>
<li>hey,manli>
<li>hey,manli>
ul>
<ol id="ol">
<li>哦豁li>
ol>
<script>
// 用getElementById获取
var timer = document.getElementById('time');
console.log(timer);
console.log(typeof(timer));
console.dir(timer);
// 根据标签名获取元素
var lis = document.getElementsByTagName('li');
console.log(lis);
console.log(lis[0]);
for (var i = 0; i < lis.length; i++) {
console.log(lis[i]);
}
// 选取某个父元素的子元素
var ol = document.getElementById('ol');
console.log(ol.getElementsByTagName('li'));
script>
body>
// 1 根据类名返回元素对象集合
document.getElementsByClassName('类名');
// 2 根据指定选择器返回第一个元素对象
document.querySelector('选择器');
// 3 根据指定选择器返回
document.querySelectorAll('选择器');
案例
<div class="box">盒子1div>
<div class="box">盒子2div>
<div class="nav">
<ul>
<li>首页li>
<li>产品li>
ul>
div>
<script>
console.log(document.querySelector('.box'));
console.log(document.querySelector('.nav'));
console.log('---------------');
console.log(document.querySelectorAll('.box'));
script>
doucumnet.body // 返回body元素对象
document.documentElement // 返回html元素对象
<body>
<button id="btn">唐伯虎button>
<script>
var btn = document.getElementById('btn')
btn.onclick = function() {
alert('点秋香')
}
script>
body>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZzIFhXLh-1618047322309)(https://i.loli.net/2021/03/23/1B9bY4JXTiq7aoC.png)]
element.innerText
从起始位置到终止位置的内容, 但它去除 html 标签, 同时空格和换行也会去掉
element.innerHTML
起始位置到终止位置的全部内容,包括 html 标签,同时保留空格和换行
<body>
<button>显示当前系统时间button>
<div>某个时间div>
<p>1123p>
<script>
// 当我们点击了按钮, div里面的文字会发生变化
// 1. 获取元素
var btn = document.querySelector('button');
var div = document.querySelector('div');
// 2.注册事件
btn.onclick = function() {
// div.innerText = '2019-6-6';
div.innerHTML = getDate();
}
function getDate() {
var date = new Date();
// 我们写一个 2019年 5月 1日 星期三
var year = date.getFullYear();
var month = date.getMonth() + 1;
var dates = date.getDate();
var arr = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'];
var day = date.getDay();
return '今天是:' + year + '年' + month + '月' + dates + '日 ' + arr[day];
}
script>
body>
1. innerText、innerHTML 改变元素内容
2. src、href
3. id、alt、title
<body>
<button id="ldh">刘德华button>
<button id="zxy">张学友button> <br>
<img src="images/ldh.jpg" alt="" title="刘德华">
<script>
// 修改元素属性 src
// 1. 获取元素
var ldh = document.getElementById('ldh');
var zxy = document.getElementById('zxy');
var img = document.querySelector('img');
// 2. 注册事件 处理程序
zxy.onclick = function() {
img.src = 'images/zxy.jpg';
img.title = '张学友思密达';
}
ldh.onclick = function() {
img.src = 'images/ldh.jpg';
img.title = '刘德华';
}
script>
body>
利用 DOM 可以操作如下表单元素的属性:
type、value、checked、selected、disabled
<body>
<button>按钮button>
<input type="text" value="输入内容">
<script>
// 1. 获取元素
var btn = document.querySelector('button');
var input = document.querySelector('input');
// 2. 注册事件 处理程序
btn.onclick = function() {
// 表单里面的值 文字内容是通过 value 来修改的
input.value = '被点击了';
// 如果想要某个表单被禁用 不能再点击 disabled 我们想要这个按钮 button禁用
// btn.disabled = true;
this.disabled = true;
// this 指向的是事件函数的调用者 btn
}
script>
body>
1. element.style 行内样式操作
2. element.className 类名样式操作
注意:
1.JS 里面的样式采取驼峰命名法 比如 fontSize、 backgroundColor
2.JS 修改 style 样式操作,产生的是行内样式,CSS 权重比较高
元素对象的style属性也是一个对象!
元素对象.style.样式属性 = 值;
div.style.backgroundColor = 'purple';
元素对象.className = 值;
因为class是关键字,所有使用className。
className 会直接更改元素的类名,会覆盖原先的类名。
div.className = 'first change';
如果有同一组元素,我们想要某一个元素实现某种样式, 需要用到循环的排他思想算法:
element.属性
element.getAttribute('属性');
区别:
element.属性 获取内置属性值(元素本身自带的属性)
element.getAttribute(‘属性’); 主要获得自定义的属性 (标准) 我们程序员自定义的属性
element.属性 = ‘值’ 设置内置属性值。
element.setAttribute('属性', '值');
区别:
element.属性 设置内置属性值
element.setAttribute(‘属性’); 主要设置自定义的属性 (标准)
element.removeAttribute('属性');
自定义属性目的:是为了保存并使用数据。有些数据可以保存到页面中而不用保存到数据库中。
自定义属性获取是通过getAttribute(‘属性’) 获取。
但是有些自定义属性很容易引起歧义,不容易判断是元素的内置属性还是自定义属性。
H5规定自定义属性data-开头做为属性名并且赋值。
或者使用 JS 设置
element.setAttribute('data-index', 2)
获取属性
兼容性获取 element.getAttribute(‘data-index’);
H5新增 element.dataset.index
或者 element.dataset['index']
dateset只能获取data-开头的自定义属性
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-31Uh43UB-1618047359427)(https://i.loli.net/2021/03/23/P6WksBAQZnrNlvo.png)]
节点至少拥有nodeType(节点类型)、nodeName(节点名称)和nodeValue(节点值)这三个
基本属性。
节点层级
node.parentNode
parentNode 属性可返回某节点最近的一个父节点
没有父节点则返回 null
1 parentNode.childNodes (标准)
返回包含指定节点的子节点的集合,该集合为即时更新的集合。
注意:返回值里面包含了所有的子节点,包括元素节点,文本节点等。
如果只想要获得里面的元素节点,则需要专门处理。 所以我们一般不提倡使用childNodes
var ul = document. querySelector(‘ul’);
for(var i = 0; i < ul.childNodes.length;i++) {
if (ul.childNodes[i].nodeType == 1) {
// ul.childNodes[i] 是元素节点
console.log(ul.childNodes[i]);
}
}
2 parentNode.children (非标准)
parentNode.children 是一个只读属性,返回所有的子元素节点。它只返回子元素节点,其余节点不返
回 (这个是我们重点掌握的)
3 parentNode.firstChild
firstChild返回第一个子节点,找不到则返回null。同样,也是包含所有的节点。
4 parentNode.lastChild
lastChild 返回最后一个子节点,找不到则返回null。同样,也是包含所有的节点。
5 parentNode.firstElementChild
返回第一个子元素节点,找不到则返回null。
6 parentNode.lastElementChild
返回最后一个子元素节点,找不到则返回null。
firstElementChild和lastElementChild有兼容性问题,ie9以上才支持
解决方案:
1.node.nextSibling
返回当前元素的下一个兄弟元素节点,找不到则返回null。同样,也是包含所有的节点。
2. node.previousSibling
previousSibling 返回当前元素上一个兄弟元素节点,找不到则返回null。同样,也是包含所有的节点。
3. node.nextElementSibling
nextElementSibling 返回当前元素下一个兄弟元素节点,找不到则返回null。
4. node.previousElementSibling
previousElementSibling 返回当前元素上一个兄弟节点,找不到则返回null。
3和4方法有兼容性问题, IE9 以上才支持,可如下自行封装兼容性函数兼容性问题
function getNextElementSibling(element) {
var el = element;
while (el = el.nextSibling) {
if (el.nodeType === 1) {
return el;
}
}
return null;
}
document.createElement('tagName')
//例如
document.createElement('li')
创建由 tagName 指定的 HTML 元素。因为这些元素原先不存在,
是根据我们的需求动态生成的,所以我们也称为动态创建元素节点。
创建后需要添加节点才有作用
1. node.appendChild(child)
node.appendChild() 方法将一个节点添加到指定父节点的子节点列表末尾。类似于 CSS 里面的after 伪元素
2. node.insertBefore(child, 指定元素)
node.insertBefore() 方法将一个节点添加到父节点的指定子节点前面。类似于 CSS 里面的 before伪元素。
node.removeChild(child)
node.removeChild() 方法从 DOM 中删除一个子节点,返回删除的节点。
node.cloneNode()
node.cloneNode() 方法返回调用该方法的节点的一个副本,且是动态的。
注意:
document.write()
innerHTML
document.createElement()
区别
document.write 是直接将内容写入页面的内容流,会导致页面全部重绘
innerHTML 是将内容写入某个 DOM 节点,不会导致页面全部重绘
innerHTML 复制节点的时候,不会复制原先节点的事件,会存在内存泄露问题
如果页面创建元素很多,建议使用 innerHTML 因其效率更高(不要拼接字符串,采取数组形式拼接)
如果页面创建元素较少,建议使用 createElement()
总结:不同浏览器下,innerHTML 效率要比 creatElement 高
给元素添加事件,称为注册事件或者绑定事件。
注册事件有两种方式:传统方式和方法监听注册方式
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZCEgDe6V-1618047392677)(https://i.loli.net/2021/03/23/iL5EknpAVeSRaJc.png)]
eventTarget.addEventListener(type, listener[, useCapture])
该方法接收三个参数:
type:事件类型字符串,比如 click 、mouseover ,注意这里不要带 on
listener:事件处理函数,事件发生时,会调用该监听函数
useCapture:可选参数,是一个布尔值,默认是 false。学完 DOM 事件流后,我们再进一步学习
eventTarget.attachEvent(eventNameWithOn, callback)
eventTarget.attachEvent()方法将指定的监听器注册到 eventTarget(目标对象) 上,当该对象触
发指定的事件时,指定的回调函数就会被执行。
该方法接收两个参数:
eventNameWithOn:事件类型字符串,比如 onclick 、onmouseover ,这里要带 on
callback: 事件处理函数,当目标触发事件时回调函数被调用
注意:IE8 及早期版本支持
function addEventListener(element, eventName, fn) {
// 判断当前浏览器是否支持 addEventListener 方法
if (element.addEventListener) {
element.addEventListener(eventName, fn); // 第三个参数 默认是false
} else if (element.attachEvent) {
element.attachEvent('on' + eventName, fn);
} else {
// 相当于 element.onclick = fn;
element['on' + eventName] = fn;
}
// 传统注册事件
divs[0].onclick = function() {
alert('111');
// 解绑事件,因为在事件里面,所有触发事件之后才会解绑
divs[0].onclick = null;
}
// 监听事件,function没有名字,是匿名函数,不能直接remove
// divs[1].addEventListener('click', function() {
// alert('2');
// })
// 应该这样写
function fn() {
alert('222');
divs[1].removeEventListener('click', fn);
}
divs[1].addEventListener('click', fn);//里面的fn不需要写调用的()
function removeEventListener(element, eventName, fn) {
// 判断当前浏览器是否支持 removeEventListener 方法
if (element.removeEventListener) {
element.removeEventListener(eventName, fn); // 第三个参数 默认是false
} else if (element.detachEvent) {
element.detachEvent('on' + eventName, fn);
} else {
element['on' + eventName] = null;
}
html中的标签都是相互嵌套的,我们可以将元素想象成一个盒子装一个盒子,document是最外面的大盒子。
当你单击一个div时,同时你也单击了div的父元素,甚至整个页面。
那么是先执行父元素的单击事件,还是先执行div的单击事件 ???
事件流描述的是从页面中接收事件的顺序。
事件发生时会在元素节点之间按照特定的顺序传播,这个传播过程即 DOM 事件流。
比如:我们给页面中的一个div注册了单击事件,当你单击了div时,也就单击了body,单击了html,单击了document。
事件冒泡: IE 最早提出,事件开始时由最具体的元素接收,然后逐级向上传播到到 DOM 最顶层节点的过程。
事件捕获: 网景最早提出,由 DOM 最顶层节点开始,然后逐级向下传播到到最具体的元素接收的过程。
注意
- JS 代码中只能执行捕获或者冒泡其中的一个阶段。
- onclick 和 attachEvent 只能得到冒泡阶段。
- addEventListener(type, listener[, useCapture])第三个参数如果是 true,表示在事件捕
获阶段调用事件处理程序;如果是 false(不写默认就是false),表示在事件冒泡阶段调用事件处理
程序。- 实际开发中我们很少使用事件捕获,我们更关注事件冒泡。
- 有些事件是没有冒泡的,比如 onblur、onfocus、onmouseenter、onmouseleave
- 事件冒泡有时候会带来麻烦,有时候又会帮助很巧妙的做某些事件,我们后面讲解。
// 3. 捕获阶段 如果addEventListener 第三个参数是 true 那么则处于捕获阶段 document -> html -> body -> father -> son
var son = document.querySelector('.son');
son.addEventListener('click', function() {
alert('son');
}, true);
var father = document.querySelector('.father');
father.addEventListener('click', function() {
alert('father');
}, true);
// 4. 冒泡阶段 如果addEventListener 第三个参数是 false 或者 省略 那么则处于冒泡阶段 son -> father ->body -> html -> document
var son = document.querySelector('.son');
son.addEventListener('click', function() {
alert('son');
}, false);
var father = document.querySelector('.father');
father.addEventListener('click', function() {
alert('father');
}, false);
document.addEventListener('click', function() {
alert('document');
})
eventTarget.onclick = function(event) {
}
eventTarget.addEventListener('click', function(event) {
})
// 这个 event 就是事件对象,我们还喜欢的写成 e 或者 evt
事件发生后,跟事件相关的一系列信息数据的集合都放到这个对象里面,这个对象就是事件对象。是自己生成的,不需要我们去写。
比如:
事件触发发生时就会产生事件对象,并且系统会以实参的形式传给事件处理函数。
事件对象本身的获取存在兼容问题:
标准浏览器中是浏览器给方法传递的参数,只需要定义形参 e 就可以获取到。
在 IE6~8 中,浏览器不会给方法传递参数,如果需要的话,需要到 window.event 中获取查找。
解决:
e = e || window.event;
//只要“||”前面为false, 不管“||”后面是true 还是 false,都返回 “||” 后面的值。
//只要“||”前面为true, 不管“||”后面是true 还是 false,都返回 “||” 前面的值。
// 1.e.target 返回触发事件的对象(在这里是鼠标点击的对象)
// this返回的是绑定事件的对象
var div = document.querySelector('div');
div.addEventListener('click', function(e) {
console.log(e.target);
console.log(this);
})
var ul = document.querySelector('ul');
ul.addEventListener('click', function(e) {
// 我们给ul绑定了事件,那么this就指向ul
console.log(this);
// 我们点击的是li,所以e.target指向li
console.log(e.target);
})
var a = document.querySelector('a');
a.addEventListener('click', function(e) {
// DOM标准写法
e.preventDefault();
})
// 传统注册方式
a.onclick = function(e) {
// 普通浏览器
e.preventDefault();
// 低版本历览器
// e.returnValue;
// 所有都能用,无兼容性问题
// return false;
}
标准写法:利用事件对象里面的 stopPropagation()方法
e.stopPropagation()
非标准写法:IE 6-8 利用事件对象 cancelBubble 属性
e.cancelBubble = true;
var son = document.querySelector('.son');
son.addEventListener('click', function(e) {
alert('son');
// 阻止冒泡
e.stopPropagation();
})
//father没有阻止冒泡,依旧会触发document的点击事件
var father = document.querySelector('.father');
father.addEventListener('click', function(e) {
alert('father');
})
document.addEventListener('click', function(e) {
alert('document')
})
兼容性处理
if(e && e.stopPropagation){
e.stopPropagation();
}else{
window.event.cancelBubble = true;
}
把事情委托给别人,代为处理。
事件委托也称为事件代理,在 jQuery 里面称为事件委派。
说白了就是,不给子元素注册事件,给父元素注册事件,把处理代码在父元素的事件中执行。
不是每个子节点单独设置事件监听器,而是事件监听器设置在其父节点上,然后利用冒泡原理影响设置每个子节点。
以上案例:给 ul 注册点击事件,然后利用事件对象的 target 来找到当前点击的 li,因为点击 li,事件会冒泡到 ul 上,
ul 有注册事件,就会触发事件监听器。
只操作了一次 DOM ,提高了程序的性能
案例
var ul = document.querySelector('ul');
ul.addEventListener('mouseover', function(e) {
e.target.style.color = 'pink';
})
ul.addEventListener('mouseout', function(e) {
e.target.style.color = 'black';
})
// 1.阻止右键菜单
document.addEventListener('contextmenu', function(e) {
e.preventDefault();
})
// 2.禁止鼠标选中
document.addEventListener('selectstart', function(e) {
e.preventDefault();
})
event对象代表事件的状态,跟事件相关的一系列信息的集合。现阶段我们主要是用鼠标事件对象MouseEvent 和键盘事件对象 KeyboardEvent。
// 鼠标事件对象 MouseEvent
document.addEventListener('click', function(e) {
// 1. client 鼠标在可视区的x和y坐标
console.log(e.clientX);
console.log(e.clientY);
console.log('---------------------');
// 2. page 鼠标在页面文档的x和y坐标
console.log(e.pageX);
console.log(e.pageY);
console.log('---------------------');
// 3. screen 鼠标在电脑屏幕的x和y坐标
console.log(e.screenX);
console.log(e.screenY);
})
注意: onkeypress 和前面2个的区别是,它不识别功能键,比如左右箭头,shift 等。
执行顺序:down -> press -> up
键盘事件对象 | 说明 |
---|---|
keycode | 返回该键的ASCII值 |
注意: onkeydown 和 onkeyup 不区分字母大小写,onkeypress 区分字母大小写。
案例:按下s键,光标就定位到搜索框
var input = document.querySelector('input');
document.addEventListener('keyup', function(e) {
// keyup和keydown不区分字母大小写
if (e.keyCode == 83) {
input.focus();
}
})
案例:京东快递单号查询
<div class="search">
<div class="con">div>
<input type="text" placeholder="请输入您的快递单号" class="jd">
div>
<script>
var con = document.querySelector('.con');
var jd_input = document.querySelector('.jd');
jd_input.addEventListener('keyup', function(e) {
if (this.value == '') {
con.style.display = 'none'
} else {
con.style.display = 'block'
con.innerText = this.value;
}
})
// 获得焦点且内容不为空时,显示con盒子
jd_input.addEventListener('focus', function() {
if (this.value != '') {
con.style.display = 'block'
}
})
// 失去焦点隐藏con盒子
jd_input.addEventListener('blur', function() {
con.style.display = 'none';
})
script>
BOM(Browser Object Model)即浏览器对象模型,它提供了独立于内容而与浏览器窗口进行交互的对象,其核心对象是 window。
BOM 由一系列相关的对象构成,并且每个对象都提供了很多方法与属性。
BOM 缺乏标准,JavaScript 语法的标准化组织是 ECMA,DOM 的标准化组织是 W3C,BOM 最初是Netscape 浏览器标准的一部分。
BOM比DOM更大,它包含DOM
window 对象是浏览器的顶级对象,它具有双重角色。
注意:window下的有一个特殊属性 window.name
①onload
window.onload = function(){}
或者
window.addEventListener("load",function(){});
window.onload 是窗口 (页面)加载事件,当文档内容完全加载完成会触发该事件(包括图像、脚本文件、CSS文件等), 就调用的处理函数。
注意:
window.onload = function() {
var btn = document.querySelector('button');
btn.addEventListener('click', function() {
alert('哦豁~');
})
}
window.addEventListener('load', function() {
var btn = document.querySelector('button');
btn.addEventListener('click', function() {
alert('哦豁~');
})
})
②DOMContentLoaded
document.addEventListener('DOMContentLoaded',function(){
})
DOMContentLoaded 事件触发时,仅当DOM加载完成,不包括样式表,图片,flash等等。(Ie9以上才支持)
如果页面的图片很多的话, 从用户访问到onload触发可能需要较长的时间, 交互效果就不能实现,必然影响用户的体验,此时用DOMContentLoaded 事件比较合适。
window.onresize = function(){
}
window.addEventListener("resize",function(){
});
window.onresize 是调整窗口大小加载事件, 当触发时就调用的处理函数。
注意:
只要窗口大小发生像素变化,就会触发这个事件。
我们经常利用这个事件完成响应式布局。
window.innerWidth 当前屏幕的宽度
//宽度超过800显示div,小于800隐藏div
<script>
window.addEventListener('load', function() {
var div = document.querySelector('div');
window.addEventListener('resize', function() {
if (window.innerWidth <= 800) {
div.style.display = 'none'
} else {
div.style.display = 'block'
}
})
})
</script>
window.setTimeout(调用函数, [延迟的毫秒数]);
setTimeout() 方法用于设置一个定时器,该定时器在定时器到期后执行调用函数。
注意:
function callback() {
console.log('2');
}
// 直接写函数,timer1是标识符
var timer1 = window.setTimeout(function() {
console.log('1');
}, 5000)
// 写函数名
var timer2 = window.setTimeout(callback, 5000)
//字符串‘函数名()',不提倡
var timer3 = window.setTimeout('callback()', 5000)
window.clearTimeout(timeoutID)
clearTimeout()方法取消了先前通过调用 setTimeout() 建立的定时器。
注意:
window.setInterval(回调函数, [间隔的毫秒数]);
setInterval() 方法重复调用一个函数,每隔这个时间,就去调用一次回调函数。
注意:
案例:倒计时
<div class="hour"></div>
<div class="minute"></div>
<div class="second"></div>
<script>
var hour = document.querySelector('.hour');
var minute = document.querySelector('.minute');
var second = document.querySelector('.second');
// 返回的是用户输入时间总的毫秒数
var inputTime = +new Date('2021-3-21 18:00:00')
// 开启定时器,因为定时器第一次执行也是一秒后,所以需要先执行一次
countDown();
setInterval(countDown, 1000)
function countDown() {
// 返回的是当前时间总的毫秒数
var nowTime = +new Date();
// times是剩余时间总的秒数
var times = (inputTime - nowTime) / 1000;
var h = parseInt(times / 60 / 60 % 24); //时
h = h < 10 ? '0' + h : h;
// 把剩余的小时给 小时黑色盒子
hour.innerHTML = h;
// 分
var m = parseInt(times / 60 % 60);
m = m < 10 ? '0' + m : m;
minute.innerHTML = m;
// 当前的秒
var s = parseInt(times % 60);
s = s < 10 ? '0' + s : s;
second.innerHTML = s;
}
window.clearInterval(intervalID);
clearInterval()方法取消了先前通过调用 setInterval()建立的定时器。
案例:短信发送冷却
手机号码: <input type="text"> <button>发送</button>
<!-- ① 按钮点击之后,会禁用 disabled 为true
② 同时按钮里面的内容会变化, 注意 button 里面的内容通过 innerHTML修改
③ 里面秒数是有变化的,因此需要用到定时器
④ 定义一个变量,在定时器里面,不断递减
⑤ 如果变量为0 说明到了时间,我们需要停止定时器,并且复原按钮初始状态。 -->
<script>
var btn = document.querySelector('button');
btn.addEventListener('click', function() {
btn.disabled = 'true';
var s = 3;
btn.innerHTML = s + 's'
var timer = setInterval(function() {
if (s == 0) {
clearInterval(timer);
btn.disabled = false;
btn.innerHTML = '发送';
} else {
s--;
btn.innerHTML = s + 's';
}
}, 1000)
})
</script>
this的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定this到底指向谁,一般情况下this的最终指向的是那个调用它的对象。
现阶段,我们先了解一下几个this指向
全局作用域或者普通函数中this指向全局对象window(注意定时器里面的this指向window)
方法调用中谁调用this指向谁
构造函数中this指向构造函数的实例
JavaScript 语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。这是因为 Javascript 这门脚本语言诞生的使命所致——JavaScript 是为处理页面中用户的交互,以及操作 DOM 而诞生的。比如我们对某个 DOM 元素进行添加和删除操作,不能同时进行。 应该先进行添加,之后再删除。
单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。这样所导致的问题是: 如果JS 执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞的感觉。
//以下代码执行的结果是什么?
console.log(1);
setTimeout(function () {
console.log(3);
}, 0);
console.log(2);
打印1,后打印2,最后打印3
为了解决这个问题,利用多核 CPU 的计算能力,HTML5 提出 Web Worker 标准,允许 JavaScript 脚本创建多个线程。于是,JS 中出现了同步和异步。
同步
前一个任务结束后再执行后一个任务,程序的执行顺序与任务的排列顺序是一致的、同步的。比如做饭的同步做
法:我们要烧水煮饭,等水开了(10分钟之后),再去切菜,炒菜。
异步
你在做一件事情时,因为这件事情会花费很长时间,在做这件事的同时,你还可以去处理其他事情。比如做
饭的异步做法,我们在烧水的同时,利用这10分钟,去切菜,炒菜。
他们的本质区别: 这条流水线上各个流程的执行顺序不同。
先执行执行栈中的同步任务。
异步任务(回调函数)放入任务队列中。
一旦执行栈中的所有同步任务执行完毕,系统就会按次序读取任务队列中的异步任务,于是被读取的异步任
务结束等待状态,进入执行栈,开始执行。
由于主线程不断的重复获得任务、执行任务、再获取任务、再执行,所以这种机制被称为事件循环( event loop)。
window 对象给我们提供了一个 location 属性用于获取或设置窗体的 URL,并且可以用于解析 URL 。 因为这个属性返回的是一个对象,所以我们将这个属性也称为 location 对象。
统一资源定位符 (Uniform Resource Locator, URL) 是互联网上标准资源的地址。互联网上的每个文件都有一个唯一的 URL,它包含的信息指出文件的位置以及浏览器应该怎么处理它。
URL 的一般语法格式为:
protocol://host[:port]/path/[?query]#fragment
http://www.itcast.cn/index.html?name=andy&age=18#link
重点记住: href 和 search
案例:url传参
//login.html
<form action="11-index.html">
<!-- name属性一定不能漏掉,不然search为空 -->
<input type="text" name='uname' class="uname">
<input type="submit" value="登录" class="login">
</form>
//index.html
var div = document.querySelector('div');
console.log(location.search);
var params = location.search.substr(1);
console.log(params);
var strs = params.split('=');
console.log(strs);
div.innerHTML = '欢迎回来,' + strs[1] + '!';
var btn = document.querySelector('button');
btn.addEventListener('click', function() {
// 记录浏览历史,所以可以实现后退功能
location.assign('http://www.itcast.cn');
// 不记录浏览历史,所以不可以实现后退功能
location.replace('http://www.itcast.cn');
location.reload(true);
})
navigator 对象包含有关浏览器的信息,它有很多属性,我们最常用的是 userAgent,该属性可以返回由客户机发送服务器的 user-agent 头部的值。
下面前端代码可以判断用户那个终端打开页面,实现跳转
if((navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i))) {
window.location.href = ""; //手机
} else {
window.location.href = ""; //电脑
}
window对象给我们提供了一个 history对象,与浏览器历史记录进行交互。该对象包含用户(在浏览器窗口中)访问过的URL。
history对象一般在实际开发中比较少用,但是会在一些 OA 办公系统中见到。
offset 翻译过来就是偏移量, 我们使用 offset系列相关属性可以动态的得到该元素的位置(偏移)、大小等。
offset 可以得到任意样式表中的样式值
offset 系列获得的数值是没有单位的
offsetWidth 包含padding+border+width
offsetWidth 等属性是只读属性,只能获取不能赋值
所以,我们想要获取元素大小位置,用offset更合适
style 只能得到行内样式表中的样式值
style.width 获得的是带有单位的字符串
style.width 获得不包含padding和border 的值
style.width 是可读写属性,可以获取也可以赋值
所以,我们想要给元素更改值,则需要用style改变
因为平时我们都是给元素注册触摸事件,所以重点记住 targetTocuhes
案例:获取鼠标在盒子内的坐标
var div = document.querySelector('div');
div.addEventListener('mousemove', function(e) {
var x = e.pageX - this.offsetLeft;
var y = e.pageY - div.offsetTop;
div.innerHTML = x + ',' + y;
})
案例 :模态框拖拽
<body>
<div class="login-header"><a id="link" href="javascript:;">点击,弹出登录框a>div>
<div class="login">
<div id="title" class="login-title">会员登录
<span><a id="closeBtn" href="javascript:void(0);" class="close-login">关闭a>span>
div>
<div class="login-input-content">
<div class="login-input">
<label for="">用户名:label>
<input type="text" placeholder="请输入用户名" name='info[username]' id="username" class="list-input">
div>
<div class="login-input">
<label for="">登录密码:label>
<input type="password" placeholder="请输入密码" name="info[password]" id="password" class="list-input">
div>
div>
<button id="logingBtn" class="login-button">登录button>
div>
<div id="bg" class="login-bg">div>
<script>
var login = document.querySelector('.login');
var mask = document.querySelector('.login-bg');
var link = document.querySelector('#link');
var closeBtn = document.querySelector('.close-login');
// 显示登录框
link.addEventListener('click', function() {
mask.style.display = 'block';
login.style.display = 'block';
})
// 关闭按钮
closeBtn.addEventListener('click', function() {
mask.style.display = 'none';
login.style.display = 'none';
})
// 拖拽模态框
// 鼠标按下
var titile = document.querySelector('#title');
titile.addEventListener('mousedown', function(e) {
// 鼠标在盒子内的坐标=鼠标在页面坐标-盒子距页面坐标
var x = e.pageX - login.offsetLeft;
var y = e.pageY - login.offsetTop;
// console.log(x + ',' + y);
// 移动事件注意要在按下事件内
var move = function(e) {
// 注意单位,赋值要用style不能用offset
login.style.left = e.pageX - x + 'px';
login.style.top = e.pageY - y + 'px';
}
this.addEventListener('mousemove', move);
this.addEventListener('mouseup', function() {
titile.removeEventListener('mousemove', move)
})
})
script>
body>
案例:仿京东放大镜
window.addEventListener('load', function() {
var preview_img = document.querySelector('.preview_img')
var mask = document.querySelector('.mask')
var big = document.querySelector('.big');
// 鼠标经过显示透明黄色区域和放大区域
preview_img.addEventListener('mouseover', function() {
mask.style.display = 'block';
big.style.display = 'block';
})
// 鼠标经过隐藏透明黄色区域和放大区域
preview_img.addEventListener('mouseout', function() {
mask.style.display = 'none';
big.style.display = 'none';
})
preview_img.addEventListener('mousemove', function(e) {
// 鼠标在盒子内的距离。注意看父亲是否有定位,如果有定位需要把父亲的加进去
var x = e.pageX - this.offsetLeft;
var y = e.pageY - this.offsetTop;
// 把鼠标坐标 - 盒子宽高一半 给mask,先不直接赋值
maskX = x - mask.offsetWidth / 2;
maskY = y - mask.offsetHeight / 2;
// 如果盒子左侧距离和顶部距离小于0或超过大盒子宽度-小盒子宽度
// 遮挡层的最大移动距离
var maskMax = preview_img.offsetWidth - mask.offsetWidth
if (maskX <= 0) {
maskX = 0
}
// 超过大盒子-小盒子
else if (maskX >= maskMax) {
maskX = maskMax;
}
if (maskY <= 0) {
maskY = 0;
} else if (maskY >= maskMax) {
maskY = maskMax;
}
// 赋值
mask.style.left = maskX + 'px';
mask.style.top = maskY + 'px';
// 放大模块
// 大图片的移动距离 = 遮挡层移动距离 * 大图片最大移动距离 / 遮挡层的最大移动距离
var bigImg = document.querySelector('.bigImg');
// 大图片最大移动距离
var bigMax = bigImg.offsetWidth - big.offsetWidth;
var bigX = maskX * bigMax / maskMax;
var bigY = maskY * bigMax / maskMax;
// console.log(bigX);
bigImg.style.left = -bigX + 'px';
bigImg.style.top = -bigY + 'px';
})
})
client 翻译过来就是客户端,我们使用 client 系列的相关属性来获取元素可视区的相关信息。通过 client系列的相关属性可以动态的得到该元素的边框大小、元素大小等。
clintWidth和offsetWidth的区别就是client不包含边框等
立即执行函数,不需要调用,立马就能自己执行的函数,也可以传递参数
(function(){})()
或者( function(){} () )
主要作用: 创建一个独立的作用域,里面所有的变量都是局部变量, 避免了命名冲突问题
下面三种情况都会刷新页面都会触发 load 事件。
1.a标签的超链接
2.F5或者刷新按钮(强制刷新)
3.前进后退按钮
但是 火狐中,有个特点,有个“往返缓存”,这个缓存中不仅保存着页面数据,还保存了DOM和JavaScript的状态;实际上是将整个页面都保存在了内存里。
所以此时后退按钮不能刷新页面。
此时可以使用 pageshow事件来触发,这个事件在页面显示时触发,无论页面是否来自缓存。在重新加载页面中,pageshow会在load事件触发后触发;根据事件对象中的persisted来判断是否是缓存中的页面触发的pageshow事件
注意这个事件给window添加。
scroll 翻译过来就是滚动的,我们使用 scroll 系列的相关属性可以动态的得到该元素的大小、滚动距离等。
scrollWidth不包含边框,包含padding和内容区
如果浏览器的高(或宽)度不足以显示整个页面时,会自动出现滚动条。当滚动条向下滚动时,页面上面被隐藏掉的高度,我们就称为页面被卷去的头部。滚动条在滚动时会触发 onscroll事件。
页面被卷去的头部:可以通过window.pageYOffset 获得
如果是被卷去的左侧 window.pageXOffset
注意,元素被卷去的头部是 element.scrollTop , 如果是页面被卷去的头部 则是 window.pageYOffset
案例:仿淘宝固定右侧侧边栏
<script>
var sliderBar = document.querySelector('.slider-bar')
var goTop = document.querySelector('.goTop')
var banner = document.querySelector('.banner');
var main = document.querySelector('.main');
// 顶部距页面边距的距离,写在函数外面更加安全
var bannerTop = banner.offsetTop;
var mainTop = main.offsetTop;
// 侧边栏在函数触发时的位置
var sliderBarTop = sliderBar.offsetTop - bannerTop;
// 页面滚动时触发函数
document.addEventListener('scroll', function() {
// 如果页面被卷曲上部值大于banner顶部距离
if (window.pageYOffset >= bannerTop) {
sliderBar.style.position = 'fixed';
sliderBar.style.top = sliderBarTop + 'px';
} else {
sliderBar.style.position = 'absolute';
sliderBar.style.top = '250px';
}
if (window.pageYOffset >= mainTop) {
goTop.style.display = 'block';
} else {
goTop.style.display = 'none';
}
})
</script>
需要注意的是,页面被卷去的头部,有兼容性问题,因此被卷去的头部通常有如下几种写法:
function getScroll() {
return {
left: window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft||0,
top: window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0
};
}
使用的时候 getScroll().left
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QbxW8Afa-1618047757305)(https://i.loli.net/2021/03/24/lS9b4EKAGtnUjwT.png)]
他们主要用法:
1.offset系列 经常用于获得元素位置 offsetLeft offsetTop
2.client经常用于获取元素大小 clientWidth clientHeight
3.scroll 经常用于获取滚动距离 scrollTop scrollLeft
4.注意页面滚动的距离通过 window.pageXOffset 获得
当鼠标移动到元素上时就会触发mouseenter 事件
类似 mouseover,它们两者之间的差别是:
mouseover 鼠标经过自身盒子会触发,经过子盒子还会触发。mouseenter 只会经过自身盒子触发
之所以这样,就是因为mouseenter不会冒泡
跟mouseenter搭配鼠标离开 mouseleave 同样不会冒泡
核心原理:通过定时器 setInterval() 不断移动盒子位置。
实现步骤:
如果多个元素都使用这个动画函数,每次都要var 声明定时器。我们可以给不同的元素使用不同的定时器(自己专门用自己的定时器)。
//需要传递 obj(动画对象)和target(移动的距离)
function animate(obj, target) {
// 先清除原先的定时器,防止多个定时器同时执行
clearInterval(obj.timer);
//每次都需要声明timer,可以给不同的动画对象添加不同的timer属性
//var timer = setInterval(function() {
obj.timer = setInterval(function() {
if (obj.offsetLeft >= target) {
// 停止动画
clearInterval(obj.timer)
} else {
obj.style.left = obj.offsetLeft + 5 + 'px';
}
}, 50)
}
缓动动画就是让元素运动速度有所变化,最常见的是让速度慢慢停下来
思路:
//仍有bug,被移动对象最终停止位置可能不是我们规定的位置,因为除法可能除不尽
var step = (target - obj.offsetLeft) / 10;
//我们需要把步长改为整数,向上取整
step = Math.ceil(step);
obj.style.left = obj.offsetLeft + step + 'px';
可以让动画函数从 800 移动到 500。
当我们点击按钮时候,判断步长是正值还是负值
1.如果是正值,则步长往大了取整
2.如果是负值,则步长 向小了取整
//通过三元函数对步长取整进行调整,因为步长有正负,所以不需要特地判断最终位置和现在位置,直接+step就可以
step = step > 0 ? Math.ceil(step) : Math.floor(step);
**回调函数原理:**函数可以作为一个参数。将这个函数作为参数传到另一个函数里面,当那个函数执行完之后,再执行传进去的这个函数,这个过程就叫做回调。
**回调函数写的位置:**定时器结束的位置。
function animate(obj, target, callback) {
// console.log(callback); callback = function() {} 调用的时候 callback()
// 先清除以前的定时器,只保留当前的一个定时器执行
clearInterval(obj.timer);
obj.timer = setInterval(function() {
// 步长值写到定时器的里面
// 把我们步长值改为整数 不要出现小数的问题
// var step = Math.ceil((target - obj.offsetLeft) / 10);
var step = (target - obj.offsetLeft) / 10;
step = step > 0 ? Math.ceil(step) : Math.floor(step);
if (obj.offsetLeft == target) {
// 停止动画 本质是停止定时器
clearInterval(obj.timer);
// 回调函数写到定时器结束里面
// if (callback) {
// // 调用函数
// callback();
// }
callback && callback();
}
// 把每次加1 这个步长值改为一个慢慢变小的值 步长公式:(目标值 - 现在的位置) / 10
obj.style.left = obj.offsetLeft + step + 'px';
}, 15);
}
防止轮播图按钮连续点击造成播放过快。
节流阀目的:当上一个函数动画内容执行完毕,再去执行下一个函数动画,让事件无法连续触发。
核心实现思路:利用回调函数,添加一个变量来控制,锁住函数和解锁函数。
开始设置一个变量var flag= true;
If(flag){flag = false; do something}
关闭水龙头
利用回调函数动画执行完毕, flag = true
打开水龙头
window.addEventListener('load', function() {
// 1. 获取元素
var arrow_l = document.querySelector('.arrow-l');
var arrow_r = document.querySelector('.arrow-r');
var focus = document.querySelector('.focus');
var focusWidth = focus.offsetWidth;
// 2. 鼠标经过focus 就显示隐藏左右按钮
focus.addEventListener('mouseenter', function() {
arrow_l.style.display = 'block';
arrow_r.style.display = 'block';
clearInterval(timer);
timer = null; // 清除定时器变量
});
focus.addEventListener('mouseleave', function() {
arrow_l.style.display = 'none';
arrow_r.style.display = 'none';
timer = setInterval(function() {
//手动调用点击事件
arrow_r.click();
}, 2000);
});
// 3. 动态生成小圆圈 有几张图片,我就生成几个小圆圈
var ul = focus.querySelector('ul');
var ol = focus.querySelector('.circle');
// console.log(ul.children.length);
for (var i = 0; i < ul.children.length; i++) {
// 创建一个小li
var li = document.createElement('li');
// 记录当前小圆圈的索引号 通过自定义属性来做
li.setAttribute('index', i);
// 把小li插入到ol 里面
ol.appendChild(li);
// 4. 小圆圈的排他思想 我们可以直接在生成小圆圈的同时直接绑定点击事件
li.addEventListener('click', function() {
// 干掉所有人 把所有的小li 清除 current 类名
for (var i = 0; i < ol.children.length; i++) {
ol.children[i].className = '';
}
// 留下我自己 当前的小li 设置current 类名
this.className = 'current';
// 5. 点击小圆圈,移动图片 当然移动的是 ul
// ul 的移动距离 小圆圈的索引号 乘以 图片的宽度 注意是负值
// 当我们点击了某个小li 就拿到当前小li 的索引号
var index = this.getAttribute('index');
// 当我们点击了某个小li 就要把这个li 的索引号给 num
num = index;
// 当我们点击了某个小li 就要把这个li 的索引号给 circle
circle = index;
// num = circle = index;
console.log(focusWidth);
console.log(index);
animate(ul, -index * focusWidth);
})
}
// 把ol里面的第一个小li设置类名为 current
ol.children[0].className = 'current';
// 6. 克隆第一张图片(li)放到ul 最后面
var first = ul.children[0].cloneNode(true);
ul.appendChild(first);
// 7. 点击右侧按钮, 图片滚动一张
var num = 0;
// circle 控制小圆圈的播放
var circle = 0;
// flag 节流阀
var flag = true;
arrow_r.addEventListener('click', function() {
if (flag) {
flag = false; // 关闭节流阀
// 如果走到了最后复制的一张图片,此时 我们的ul 要快速复原 left 改为 0
if (num == ul.children.length - 1) {
ul.style.left = 0;
num = 0;
}
num++;
animate(ul, -num * focusWidth, function() {
flag = true; // 打开节流阀
});
// 8. 点击右侧按钮,小圆圈跟随一起变化 可以再声明一个变量控制小圆圈的播放
circle++;
// 如果circle == 4 说明走到最后我们克隆的这张图片了 我们就复原
if (circle == ol.children.length) {
circle = 0;
}
// 调用函数
circleChange();
}
});
// 9. 左侧按钮做法
arrow_l.addEventListener('click', function() {
if (flag) {
flag = false;
if (num == 0) {
num = ul.children.length - 1;
ul.style.left = -num * focusWidth + 'px';
}
num--;
animate(ul, -num * focusWidth, function() {
flag = true;
});
// 点击左侧按钮,小圆圈跟随一起变化 可以再声明一个变量控制小圆圈的播放
circle--;
// 如果circle < 0 说明第一张图片,则小圆圈要改为第4个小圆圈(3)
// if (circle < 0) {
// circle = ol.children.length - 1;
// }
circle = circle < 0 ? ol.children.length - 1 : circle;
// 调用函数
circleChange();
}
});
function circleChange() {
// 先清除其余小圆圈的current类名
for (var i = 0; i < ol.children.length; i++) {
ol.children[i].className = '';
}
// 留下当前的小圆圈的current类名
ol.children[circle].className = 'current';
}
// 10. 自动播放轮播图
var timer = setInterval(function() {
//手动调用点击事件
arrow_r.click();
}, 2000);
})
window.pageYOffset
得到window.scroll(x,y)
//1. 获取元素
var sliderbar = document.querySelector('.slider-bar');
var banner = document.querySelector('.banner');
// banner.offestTop 就是被卷去头部的大小 一定要写到滚动的外面
var bannerTop = banner.offsetTop
// 当我们侧边栏固定定位之后应该变化的数值
var sliderbarTop = sliderbar.offsetTop - bannerTop;
// 获取main 主体元素
var main = document.querySelector('.main');
var goBack = document.querySelector('.goBack');
var mainTop = main.offsetTop;
// 2. 页面滚动事件 scroll
document.addEventListener('scroll', function() {
// console.log(11);
// window.pageYOffset 页面被卷去的头部
// console.log(window.pageYOffset);
// 3 .当我们页面被卷去的头部大于等于了 172 此时 侧边栏就要改为固定定位
if (window.pageYOffset >= bannerTop) {
sliderbar.style.position = 'fixed';
sliderbar.style.top = sliderbarTop + 'px';
} else {
sliderbar.style.position = 'absolute';
sliderbar.style.top = '300px';
}
// 4. 当我们页面滚动到main盒子,就显示 goback模块
if (window.pageYOffset >= mainTop) {
goBack.style.display = 'block';
} else {
goBack.style.display = 'none';
}
})
// 3. 当我们点击了返回顶部模块,就让窗口滚动的页面的最上方
goBack.addEventListener('click', function() {
// 里面的x和y 不跟单位的 直接写数字即可
// window.scroll(0, 0);
// 因为是窗口滚动 所以对象是window
animate(window, 0);
});
window.addEventListener('load', function() {
// 1. 获取元素
var cloud = document.querySelector('.cloud');
var c_nav = document.querySelector('.c-nav');
var lis = c_nav.querySelectorAll('li');
// 2. 给所有的小li绑定事件
// 这个current 做为筋斗云的起始位置
var current = 0;
for (var i = 0; i < lis.length; i++) {
// (1) 鼠标经过把当前小li 的位置做为目标值
lis[i].addEventListener('mouseenter', function() {
animate(cloud, this.offsetLeft);
});
// (2) 鼠标离开就回到起始的位置
lis[i].addEventListener('mouseleave', function() {
animate(cloud, current);
});
// (3) 当我们鼠标点击,就把当前位置做为目标值
lis[i].addEventListener('click', function() {
current = this.offsetLeft;
});
}
})
移动端浏览器兼容性较好,我们不需要考虑以前 JS 的兼容性问题,可以放心的使用原生 JS 书写效果,但是移动端也有自己独特的地方。比如触屏事件 touch(也称触摸事件),Android和 IOS 都有。
touch 对象代表一个触摸点。触摸点可能是一根手指,也可能是一根触摸笔。触屏事件可响应用户手指(或触控笔)对屏幕或者触控板操作。
常见的触屏事件如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h7uKE5MD-1618047817045)(https://i.loli.net/2021/03/24/MCBtVTRE9HNaou1.png)]
TouchEvent 是一类描述手指在触摸平面(触摸屏、触摸板等)的状态变化的事件。这类事件用于描述一个或多个触点,使开发者可以检测触点的移动,触点的增加和减少,等等
touchstart、touchmove、touchend 三个事件都会各自有事件对象。
触摸事件对象重点我们看三个常见对象列表:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eTeognrt-1618047822076)(https://i.loli.net/2021/03/24/eLbCiDv9tspE6rc.png)]
因为平时我们都是给元素注册触摸事件,所以重点记住
targetTocuhes
// 触摸事件对象
// 1. 获取元素
// 2. 手指触摸DOM元素事件
var div = document.querySelector('div');
div.addEventListener('touchstart', function(e) {
// console.log(e);
// touches 正在触摸屏幕的所有手指的列表
// targetTouches 正在触摸当前DOM元素的手指列表
// 如果侦听的是一个DOM元素,他们两个是一样的
// changedTouches 手指状态发生了改变的列表 从无到有 或者 从有到无
// 因为我们一般都是触摸元素 所以最经常使用的是 targetTouches
console.log(e.targetTouches[0]);
// targetTouches[0] 就可以得到正在触摸dom元素的第一个手指的相关信息比如 手指的坐标等等
});
// 3. 手指在DOM元素身上移动事件
div.addEventListener('touchmove', function() {
});
// 4. 手指离开DOM元素事件
div.addEventListener('touchend', function(e) {
// console.log(e);
// 当我们手指离开屏幕的时候,就没有了 touches 和 targetTouches 列表
// 但是会有 changedTouches
});
touchstart
、touchmove
、touchend
可以实现拖动元素
但是拖动元素需要当前手指的坐标值 我们可以使用 targetTouches[0]
里面的pageX
和 pageY
移动端拖动的原理: 手指移动中,计算出手指移动的距离。然后用盒子原来的位置 + 手指移动的距离
手指移动的距离: 手指滑动中的位置 减去 手指刚开始触摸的位置
拖动元素三步曲:
(1) 触摸元素
touchstart
: 获取手指初始坐标,同时获得盒子原来的位置
(2) 移动手指touchmove
: 计算手指的滑动距离,并且移动盒子
(3) 离开手指touchend
:
注意: 手指移动也会触发滚动屏幕所以这里要阻止默认的屏幕滚动 e.preventDefault();
移动端轮播图功能和基本PC端一致。
自动播放功能-无缝滚动
注意,我们判断条件是要等到图片滚动完毕再去判断,就是过渡完成后判断
此时需要添加检测过渡完成事件 transitionend
判断条件:如果索引号等于 3 说明走到最后一张图片,此时 索引号要复原为 0
此时图片,去掉过渡效果,然后移动
如果索引号小于0, 说明是倒着走, 索引号等于2
此时图片,去掉过渡效果,然后移动
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KpojBiQu-1618047851368)(https://i.loli.net/2021/03/25/QMLC6dIohzgRDOi.png)]
classList属性是HTML5新增的一个属性,返回元素的类名。但是ie10以上版本支持。
该属性用于在元素中添加,移除及切换 CSS 类。有以下方法
添加类:
element.classList.add(’类名’);
focus.classList.add('current');
移除类:
element.classList.remove(’类名’);
focus.classList.remove('current');
切换类:
element.classList.toggle(’类名’);
focus.classList.toggle('current');
注意:以上方法里面,所有类名都不带点
移动端 click 事件会有 300ms 的延时,原因是移动端屏幕双击会缩放(double tap to zoom) 页面。
解决方案:
<meta name="viewport" content="user-scalable=no">
2.利用touch事件自己封装这个事件解决300ms 延迟
/*原理就是:
1. 当我们手指触摸屏幕,记录当前触摸时间
2. 当我们手指离开屏幕, 用离开的时间减去触摸的时间
3. 如果时间小于150ms,并且没有滑动过屏幕, 那么我们就定义为点击*/
//封装tap,解决click 300ms 延时
function tap (obj, callback) {
var isMove = false;
var startTime = 0; // 记录触摸时候的时间变量
obj.addEventListener('touchstart', function (e) {
startTime = Date.now(); // 记录触摸时间
});
obj.addEventListener('touchmove', function (e) {
isMove = true; // 看看是否有滑动,有滑动算拖拽,不算点击
});
obj.addEventListener('touchend', function (e) {
if (!isMove && (Date.now() - startTime) < 150) {
// 如果手指触摸和离开时间小于150ms 算点击
callback && callback(); // 执行回调函数
}
isMove = false; // 取反 重置
startTime = 0;
});
}
//调用
tap(div, function(){
// 执行代码 });
3.使用插件。fastclick 插件解决300ms 延迟。
动端要求的是快速开发,所以我们经常会借助于一些插件来帮我完成操作,那么什么是插件呢?
JS 插件是 js 文件,它遵循一定规范编写,方便程序展示效果,拥有特定功能且方便调用。如轮播图和瀑布流插件。
特点:它一般是为了解决某个问题而专门存在,其功能单一,并且比较小。
我们以前写的animate.js 也算一个最简单的插件
fastclick 插件解决 300ms 延迟。 使用延时
GitHub官网地址: https://github.com/ftlabs/fastclick
if ('addEventListener' in document) {
document.addEventListener('DOMContentLoaded', function() {
FastClick.attach(document.body);
}, false);
}
中文官网地址: https://www.swiper.com.cn/
superslide: http://www.superslide2.com/
iscroll: https://github.com/cubiq/iscroll
H5 给我们提供了 video 标签,但是浏览器的支持情况不同。
不同的视频格式文件,我们可以通过source解决。
但是外观样式,还有暂停,播放,全屏等功能我们只能自己写代码解决。
这个时候我们可以使用插件方式来制作。
我们可以通过 JS 修改元素的大小、颜色、位置等样式。
框架,顾名思义就是一套架构,它会基于自身的特点向用户提供一套较为完整的解决方案。框架的控制权在框架本身,使用者要按照框架所规定的某种规范进行开发。
插件一般是为了解决某个问题而专门存在,其功能单一,并且比较小。
前端常用的框架有 Bootstrap、Vue、Angular、React 等。既能开发PC端,也能开发移动端
前端常用的移动端插件有 swiper、superslide、iscroll等。
框架: 大而全,一整套解决方案
插件: 小而专一,某个功能的解决方案
Bootstrap 是一个简洁、直观、强悍的前端开发框架,它让 web 开发更迅速、简单。
它能开发PC端,也能开发移动端
Bootstrap JS插件使用步骤:
1.引入相关js 文件
2.复制HTML 结构
3.修改对应样式
4.修改相应JS 参数
随着互联网的快速发展,基于网页的应用越来越普遍,同时也变的越来越复杂,为了满足各种各样的需求,会经常性在本地存储大量的数据,HTML5规范提出了相关解决方案。
1、数据存储在用户浏览器中
2、设置、读取方便、甚至页面刷新不丢失数据
3、容量较大,sessionStorage约5M、localStorage约20M
4、只能存储字符串,可以将对象JSON.stringify() 编码后存储
1、生命周期为关闭浏览器窗口
2、在同一个窗口(页面)下数据可以共享
3、以键值对的形式存储使用
存储数据:
sessionStorage.setItem(key, value)
获取数据:
sessionStorage.getItem(key)
删除数据:
sessionStorage.removeItem(key)
清空数据:(所有都清除掉)
sessionStorage.clear()
1、声明周期永久生效,除非手动删除,否则关闭页面也会存在
2、可以多窗口(页面)共享(同一浏览器可以共享)
3、以键值对的形式存储使用
存储数据:
localStorage.setItem(key, value)
获取数据:
localStorage.getItem(key)
删除数据:
localStorage.removeItem(key)
清空数据:(所有都清除掉)
localStorage.clear()