参考自后盾人 链接
结论:变量声明升,函数整体提升
console.log(a);
var a= 10;
这里并不会报错,由于变量提升,它相当于
var a;
console.log(a);
a=10;
再举个例子
test();
function test() {
console.log(a);
if (false) {
var a = '123123';
}
}
输出结果为undefined 就算不执行if语句但是也会有变量提升,函数也不会报错。
为了避免变量提升带来的问题,我们可以使用es6的新特性 const let。
用const let 声明的变量不会出现这样的问题。
这里可以正常输出a的值
let a = 1
function test(){
console.log(a);
}
test()
下面报错,这是TDC临时死区的特点。必须先声明再使用
let a = 1;
function test(){
console.log(a);
let a=2;
}
test()
引入了别的js,会造成变量的污染。为了解决这个问题以往都采用下面的方法。
1.js的内容
利用函数作用域来避免变量的污染。
1.js还可以采用块作用域的方法
结果相同,但是注意使用块作用域时。要使用let或者const声明变量。
使用var变量声明时,会添加到window下面
例如
var a = 1;
console.log(window.a); //打印结果为 1
var screenLeft = 88;
console.log(window.screenLeft); //本来screenLeft会输出窗口距离屏幕左侧的距离,但是这样不管怎么拖拽窗口改变,它都会输出8。因为它被var给覆盖了。
这是var的缺点 但是let 和 const不会出现这种问题
const a = 1;
console.log(window.a) // undefined
再来讨论重复声明的问题
在同一作用域 var 可被多次声明,let 和const 则会报错
var a=1;
var a=2;//浏览器不会报错
let b=1;
let b=2;//浏览器会报错
typeof是判断类型的,instanceof是判断是否在原型链上
const array =[1,2,3];
console.log(typeof array); //object
const obj = {
name: 'libai'};
console.log(typeof obj); //object
console.log(array instanceof Array); //true
console.log(array instanceof Object); //true
console.log(obj instanceof Array); //false
const str ='我是无敌的';
for( let value of str){
console.log(value);
}
for(let key in str){
console.log(str[key]);
}
for in 遍历出的可以为索引,for of遍历出的值为内容。字符串,数组,对象同理。
字符串的截取
const str = 'abcdefghijklmn'
console.log(str.slice(0,2)); //从第0位开始截取到第2位(不包括第二位)
console.log(str.substring(0,2));//同上
console.log(str.substr(1,2)); //从第一位开始 截取2个
字符串的搜索
const str = 'abcdefghijklmbn'
console.log(str.indexOf('b')); //1 从前面开始找 第几位 找不到返回-1
console.log(str.lastIndexOf('b'));//13 从后开始找 找不到返回-1
console.log(str.includes('abc')); //true 判断是否包含'abc'返回布尔值
字符串的替换
把 我喜欢在b站学习js和css 中的js和css替换成链接
const word = ['js','css'];
const str = '我喜欢在b站学习js和css';
//pre为前一个值(这里是指 '我喜欢在b站学习js和css'),word为遍历的值(这里为'js','css')
const replaceString= word.reduce((pre,word)=>{
console.log(pre,word);
return pre.replace(word,`${
word}`)
},str) //这里的str是初始值
document.body.innerHTML = replaceString
</script>
下面的截图为输出的结果 可以清楚的看出pre和word在循环中的变化
字符串转换为数组,数组转换为字符串
const str = "abcd,efg";
console.log(str.split(",")); //从哪里切割
const array = ['abc','zdsf'];
console.log(array.join('|'));//用什么隔开
空字符串和空数组转换为布尔值都为真
const a =[];
const b ={
};
Boolean(a); //true
Boolean(b); //true
const c = 99;
if(c){
console.log("进入了循环");} //可以进入
console.log(c==true) //false 这里会把true转换为数值型为1 99==1 所以false
const d = [1];
console.log(d==true); //true 这里同样会都转换为数值型 Number(d)的值为1 true为1 所以相同
const e= [2]; //Number的值为2
console.log(e==true) //false
if(e){
}//会进入循环
const f = [1,2] //Number(d)为NaN
由此可以得知,判断和转换的值不一定相同
const number = 99;
console.log(Number.isInteger(number)); //true 判断是否为整数
const number1 = 99.65;
console.log(number1.toFixed(1)); //99.7 保留几位小数四舍五入 得到的值为字符串类型
console.log(Math.round(number1)); // 100 四舍五入取整
console.log(Math.ceil(number1)); //向上取整 100
console.log(Math.floor(number1));//向下取整99
Math.random(); //随机数取值为 0<= ... <1
//随机取值 1<= x <=10 min+Math.floor(Math.random()*(max-min+1))
console.log(1+Math.floor(Math.random()*(10-1+1)))
const date1 = Date();//object
const date = new Date(); //string
console.log(date.getMonth()+1); //得到的月份从0开始
console.log(date.getFullYear()); //得到年份
console.log(date.getDay()); //得到日
console.log(date.getHours()+" "+date.getMinutes()+ " " +date.getSeconds())
console.log(date*1) //得到的是时间戳
console.log(date.valueOf()) //得到的是时间戳
console.log(date.getTime()) //得到的是时间戳
console.log(Number(date)) //得到的是时间戳
const timeStamp = date.getTime();
console.log(new Date(timeStamp));//时间戳转换为标准时间
向数组中添加元素、删除元素、截取。。。
push方法
const array = [1,2,3];
array.push(1,3,4)//向数组的末尾追加,可以追加多个
console.log(array); //[ 1, 2, 3, 1, 3, 4 ]
unshift方法
const array = [1,2,3];
array.unshift(7,8,9)//向数组前面追加,同样可以追加多个
console.log(array); //[ 7, 8, 9, 1, 2, 3 ]
pop方法 删除最后一个元素
const array = [1,2,3];
array.pop();
console.log(array);//[1,2]
slice方法 截取数组元素 不改变原数组
const array = [1,2,3];
const array2 = array.slice(1,2) //从第几个截取到第几个
console.log(array2); //[2]
console.log(array); //[1,2,3]
splice方法 同样是截取数组元素 但是它会改变原数组。因此可以利用它来实现数组的增删改。非常的强大。
const array = [1,2,3];
const array2 = array.slice(1,2) //从第几个开始 截取几个
console.log(array2); //[2]
console.log(array); //[1,2,3]
上面的例子就等同于删除操作,从下标为一个元素开始删除两个。下面的例子演示增加和修改
const array = [1,2,3];
array.splice(1,0,1000)//增加操作 在下标为1的地方增加一个元素1000
console.log(array); //[1,1000,2,3];
//接下来把1000改成 2000
array.splice(1,1,2000)
console.log(array); //[ 1, 2000, 2, 3 ]
//最后删除2000
array.splice(1,1);
console.log(array); //[ 1, 2, 3 ]
es6的新特性
结构赋值简化了赋值操作
const array = ['张三','李四','王五'];
const [name1,name2,name3] = array;
console.log(name1,name2,name3); //张三 李四 王五
结构赋值与…结合使用
const array = ['张三','李四','王五'];
const [name1,...names] = array;
console.log(name1,names); //张三 Array [ "李四", "王五" ]
//把数组的第一个元素为name1,由于...的存在 它会将剩余的所有元素都塞给names
…展开符使用
const array = ['张三','李四','王五'];
const array2 = ['赵柳'];
array3 = [...array,...array2]//相当于 array3 = ['张三','李四','王五','赵柳'] 就是将数组或对象展开
console.table(array3);
array3 = [...array,array2]
console.log(array3);//Array(4) [ "张三", "李四", "王五", (1) […] ]
数组的查找 和字符串类似。indexOf lastIndexOf includes 等
find方法查找 传入一个函数 返回值为true则返回 该元素 只查找第一个符合条件的元素
const array = [{
id:1,name:'张三'},{
id:2,name:'李四'},{
id:3,name:'王五'}];
const obj = array.find(item=> item.id===3); //find 如果函数值为true则返回
console.log(obj);
如果要查找符合条件的元素的索引 则可使用 findIndex使用方法与find类似只是返回结果不同。
数组的排序
sort方法
升序
const array = [
{
price:1000, product: '手机'},
{
price:500, product: '电风扇'},
{
price:25000, product: '电脑'},
{
price:300, product: '鼠标'},
{
price:400, product: '键盘'},
];
const newArray = array.sort(function(a,b) {
return a.price-b.price
})
console.log(newArray);
数组的迭代器
keys() 返回的是 索引和当前是否迭代完成
values() 返回的是 当前的值和是否迭代完成
entries() 返回的是 [索引 ,值] 和是否迭代完成
const array = [
{
price:1000, product: '手机'},
{
price:500, product: '电风扇'},
{
price:25000, product: '电脑'},
{
price:300, product: '鼠标'},
{
price:400, product: '键盘'},
];
console.log(array.keys().next()); // {value: 0, done: false}
console.log(array.values().next()); // {value: {price:1000, product:'手机'}, done: false}
console.log(array.entries().next());// {value: [0,{price:1000 , product: '手机'}], done: false}
可以利用迭代器进行循环搭配数组的结构赋值
const array = [
{
price:1000, product: '手机'},
{
price:500, product: '电风扇'},
{
price:25000, product: '电脑'},
{
price:300, product: '鼠标'},
{
price:400, product: '键盘'},
];
for(let [key,value] of array.entries()){
let {
price, product} = value
console.log(key,value);
console.log(price, product);
}
foreach,map就省略了。
filter 过滤 只返回为true的内容
reduce方法
//找出大于200块钱的商品
const array = [
{
price:1000, product: '手机'},
{
price:500, product: '电风扇'},
{
price:25000, product: '电脑'},
{
price:300, product: '鼠标'},
{
price:400, product: '键盘'},
];
const mun = array.reduce((pre, cur)=>{
if(cur.price>200){
return [...pre,cur]
}else{
return pre
}
},[])
console.log(mun);
some 和 every
every和some比较类似 像是 &&和||
every是只要函数返回值有一个false就返回false 只有全部为true才返回true
some是只要有一个true就返回true
every
const array = [
{
name:'张三', score: 88},
{
name:'李四', score: 98},
{
name:'王五', score: 55},
{
name:'赵柳', score: 96},
{
name:'宋琪', score: 67},
{
name:'张久', score: 74},
];
const flag= array.every((item)=> item.score>=60);
flag? console.log("全都及格了") : console.log("有人没及格");
//结果是有人没及格 ,因为王五的成绩是55 判断到王五时返回false 不继续判断后面的成绩
every是&& some是|| some不写例子了
Symbol 本质上是一种唯一标识符,可用作对象的唯一属性名,这样其他人就不会改写或覆盖你设置的属性值。
声明方法:
let id = Symbol("id“);
Symbol 数据类型的特点是唯一性,即使是用同一个变量生成的值也不相等。
let id1 = Symbol(‘id’);
let id2 = Symbol(‘id’);
console.log(id1 == id2); //false
let id1 = Symbol('id');
let id2 = Symbol('id');
console.log(id1 == id2); //false
Symbol 数据类型的另一特点是隐藏性,for···in,object.keys() 不能访问
let id = Symbol(“id”);
let obj = {
[id]:‘symbol’
};
for(let option in obj){
console.log(obj[option]); //空
}
let id = Symbol("id");
let obj = {
[id]:'symbol'
};
for(let option in obj){
console.log(obj[option]); //空
}
但是也有能够访问的方法:Object.getOwnPropertySymbols
Object.getOwnPropertySymbols 方法会返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值。
let id = Symbol(“id”);
let obj = {
[id]:‘symbol’
};
let array = Object.getOwnPropertySymbols(obj);
console.log(array); //[Symbol(id)]
console.log(obj[array[0]]); //‘symbol’
let id = Symbol("id");
let obj = {
[id]:'symbol'
};
let array = Object.getOwnPropertySymbols(obj);
console.log(array); //[Symbol(id)]
console.log(obj[array[0]]); //'symbol'
虽然这样保证了Symbol的唯一性,但我们不排除希望能够多次使用同一个symbol值的情况。
为此,官方提供了全局注册并登记的方法:Symbol.for()
let name1 = Symbol.for(‘name’); //检测到未创建后新建
let name2 = Symbol.for(‘name’); //检测到已创建后返回
console.log(name1 === name2); // true
let name1 = Symbol.for('name'); //检测到未创建后新建
let name2 = Symbol.for('name'); //检测到已创建后返回
console.log(name1 === name2); // true
通过这种方法就可以通过参数值获取到全局的symbol对象了,反之,能不能通过symbol对象获取到参数值呢?
是可以的 ,通过Symbol.keyFor()
let name1 = Symbol.for(‘name’);
let name2 = Symbol.for(‘name’);
console.log(Symbol.keyFor(name1)); // ‘name’
console.log(Symbol.keyFor(name2)); // ‘name’
let name1 = Symbol.for('name');
let name2 = Symbol.for('name');
console.log(Symbol.keyFor(name1)); // 'name'
console.log(Symbol.keyFor(name2)); // 'name'
最后,提醒大家一点,在创建symbol类型数据 时的参数只是作为标识使用,所以 Symbol() 也是可以的。
set与数组很相似,但是set里面不能放入相同的数据
//set的两种声明方式
const set = new Set();
const set2 = new Set([1,2,3,'1']);
set.add("张三"); //向set中添加元素
set.add("李四");
set2.delete(2) //返回值为 boolean
set2.clear(); //清空set
set2.has(1); //检查是否存在某元素 返回值为boolean
set2.size //长度
console.log(set);
console.log(set2);
有些情况数组能处理,而set处理起来很麻烦。有时候set能处理的,数组有不太容易。这种情况我们就需要让数组和set进行互相帮助,完成需求再转换回来。完成任务。
比如说要把set中工资大于10000的人给筛选出来
let set2 = new Set([
{
name: '张三', money: 100000},
{
name: '李四', money: 10500},
{
name: '王五', money: 7190},
{
name: '赵柳', money: 5010},
]);
let setArray = Array.from(set2).filter(set=> set.money>10000);
set2 = new Set(setArray);
console.log(set2);
在比如说,要把数组中的重复元素去除。这里就可以运用set的元素不能重复的特性
let array = [1,2,415,1,2,3,58,9];
const set = new Set(array);
array = [...set];
console.log(array);
//Array(6) [ 1, 2, 415, 3, 58, 9 ]
遍历可以用forEach 和 for of
weakSet
添加add 删除delete 是否存在 has 与Set类型类似
1、WeakSet的成员只能是对象,不能是其他数据类型,否则会报错;
2、WeakSet的成员都是弱引用,只要对象的其他引用被删除,垃圾回收机制就会释放该对象占用的内存,从而避免内存泄漏。
3、由于WeakSet的成员随时可能被垃圾回收机制回收,成员的数量不稳定,所以没有size属性。
4、没有clear()方法
5、不能遍历
Map
map的键可以采用任何类型,和对象不同。对象的键只能是字符串。
//第一种声明方式
const map = new Map();
const obj = {
id: 1,
name:'杜甫'
}
map.set('姓名','李白'); //键为字符串
map.set(1,'13214'); //键为数值型
map.set(obj, '苏轼'); //键为对象
//也可以采用map的构造函数
const map2 = new Map([['name','张飞'],['name2','刘备']]);
//通过get方法取值
console.log(map2.get('name')); //张飞
与set也颇为类似 has delete clear它都有
全局环境下this就是window对象的引用
使用严格模式时在全局函数内this为undefined
bind apply call都用于改变this的指向。其中apply call会自动调用。
function show (car){
console.log(this.name,car);
}
let wangwu ={
name: '王五'
}
let lisi ={
name : '李四'
}
show.apply(wangwu,['奔驰'])//前面是this的指向,后面是参数apply要用数组传
show.call(lisi, '宝马')//call与apply 第二个参数不用数组
用call改变函数this的指向
<button message='诗仙'>李白</button>
<button message='诗圣'>杜甫</button>
<script>
function show (){
console.log(this.getAttribute('message'));//此时的this指向window
}
const btns = document.querySelectorAll('button');
btns.forEach(function(btn){
btn.addEventListener('click',function(){
show.call(btn);
})
})
</script>
//这样就改变了this的指向,点击李白打印诗仙。点击杜甫,打印诗圣
bind
bind()是将函数绑定到某个对象,比如 a.bind(hd) 可以理解为将a函数绑定到hd对象上即 hd.a()。
与 call/apply 不同bind不会立即执行
bind 是复制函数形为会返回新函数
const obj = {
name: '李明'
}
function test(score){
this.score = score;
console.log(this.name);
}
func = test.bind(obj);
func(99);
console.log(obj.score);
在回调函数中使用
let phone = {
name: 'apple',
price: 8000
}
setInterval(function(){
console.log(this.name);
}.bind(phone),500)
全局作用域只有一个,每个函数又都有作用域(环境)。
编译器运行时会将变量定义在所在作用域
使用变量时会从当前作用域开始向上查找变量
作用域就像攀亲亲一样,晚辈总是可以向上辈要些东西
作用域链只向上查找,找到全局window即终止,应该尽量不要在全局作用域中添加变量。
函数每次调用都会创建一个新作用域
let site = '后盾人';
function a() {
let hd = 'houdunren.com';
function b() {
let cms = 'hdcms.com';
console.log(hd);
console.log(site);
}
b();
}
a();
如果子函数被使用时父级环境将被保留
function hd() {
let n = 1;
return function() {
let b = 1;
return function() {
console.log(++n);
console.log(++b);
};
};
}
let a = hd()();
a(); //2,2
a(); //3,3
构造函数也是很好的环境例子,子函数被外部使用父级环境将被保留
function User() {
let a = 1;
this.show = function() {
console.log(a++);
};
}
let a = new User();
a.show(); //1
a.show(); //2
let b = new User();
b.show(); //1
函数能访问到其他作用域的函数就称为闭包
下面这个例子是给数组排序,可以通过向term中传送排序的条件。
function term(attr){
//这个函数就是闭包 子函数使用了父函数中的attr
return (a,b)=>{
return a[attr]>b[attr]?1:-1; //因为参数为字符串所以只能用[]来获取,不能用a.attr的方式
}
}
const products = [
{
name: '苹果',
price: 5,
count: 560
},{
name: '西瓜',
price: 3.5,
count:98
},{
name: '香蕉',
price: 4.5,
count: 500
},{
name: '橘子',
price: 6,
count: 1000
}
];
console.log( products.sort(term('count')) )
const phone ={
name: '三星',
price: 2000
};
const flag=phone.hasOwnProperty("name"); //true 判断对象中是否存在该属性
console.log(phone.hasOwnProperty("toString")); //false 此方法是检查自身有没有该属性
console.log('toString' in phone); //true 这样是写包括它的原型
Object.assign()
从一个或多个对象复制属性
const phone ={
name: '三星',
price: 2000
};
const newPhone = Object.assign(phone,{
count: '600'});
console.log(newPhone);
Object.key() 获取对象中所有的键
Object.values() 获取对象中所有的值
浅拷贝
使用浅拷贝时如果被拷贝的对象中含有引用类型时,则不能完全复制引用类型到新对象中,只能传址。当修改其中一个对象中的引用类型时,另一个对象中的该值也同样会变化。
const phone ={
name: '三星',
price: 2000,
style:{
color: 'red',
size: 'small'
}
};
const newPhone = {
};
// const newPhone= Object.assign({},phone);
// const newPhone = {...phone}
for(let key of Object.keys(phone)){
newPhone[key] = phone[key]
}
console.log(newPhone);
const phone ={
name: '三星',
price: 2000,
style:{
color: 'red',
size: 'small'
}
};
const newPhone = {
};
// const newPhone= Object.assign({},phone);
// const newPhone = {...phone}
for(let key of Object.keys(phone)){
newPhone[key] = phone[key]
}
newPhone.style.color = 'blue'; //这里改变newPhone的引用类型的值
console.log(phone); //phone中的值也变了
深拷贝
深拷贝则不会出现浅拷贝的问题,它会把对象中的引用类型也完完全全的拷贝一份。修改一个对象的引用类型时,另一个对象不受影响。
function copy(object) {
let obj = object instanceof Array ? []:{
};
for(let key of Object.keys(object)){
if( typeof object[key] =='object'){
obj[key] = copy(object[key]);
}else{
obj[key] = object[key];
}
}
return obj;
}
const phone = {
name: '三星',
price: 2000,
style: {
color: 'red',
size: 'small'
}
};
const newPhone = copy(phone)
newPhone.style.color = 'blue'
console.log(phone);//改变newPhone的引用类型 phone的内容不变化
为什么定义一个字符或者数值都能使用方法或者属性?
因为每当你const num = 1 或者 const str ='abc’时,都是通过构造器定义的。
都相当于
const num = new Number(99);
console.log(num.valueOf());
const string = new String("后盾人");
console.log(string.valueOf());
const boolean = new Boolean(true);
console.log(boolean.valueOf());
const date = new Date();
console.log(date.valueOf() * 1);
const regexp = new RegExp("\\d+");
console.log(regexp.test(99));
let hd = new Object();
hd.name = "后盾人";
console.log(hd);
字面量创建的对象,内部也是调用了Object构造函数
const hd = {
name: "后盾人"
};
console.log(hd.constructor); //ƒ Object() { [native code] }
//下面是使用构造函数创建对象
const hdcms = new Object();
hdcms.title = "开源内容管理系统";
console.log(hdcms);
使用 Object.getOwnPropertyDescriptor查看对象属性的描述。
const phone = {
name: '三星',
price: 2000,
style: {
color: 'red',
size: 'small'
}
};
console.log(Object.getOwnPropertyDescriptor(phone,'name'));
使用 Object.getOwnPropertyDescriptors查看对象所有属性的描述
const phone = {
name: '三星',
price: 2000,
style: {
color: 'red',
size: 'small'
}
};
console.log(Object.getOwnPropertyDescriptors(phone));
设置特性
使用Object.defineProperty 方法修改属性特性,通过下面的设置属性name将不能被遍历、删除、修改。
使用 Object.defineProperties 可以一次设置多个属性,具体参数和上面介绍的一样。
Object.preventExtensions 禁止向对象添加属性
Object.isExtensible 判断是否能向对象中添加属性
Object.seal()方法封闭一个对象,阻止添加新属性并将所有现有属性标记为 configurable: false
Object.isSealed 如果对象是密封的则返回 true,属性都具有 configurable: false。
Object.freeze 冻结对象后不允许添加、删除、修改属性,writable、configurable都标记为false
Object.isFrozen()方法判断一个对象是否被冻结
详细可见后盾人
https://houdunren.gitee.io/note/js/10%20%E5%AF%B9%E8%B1%A1.html#%E8%AE%BE%E7%BD%AE%E7%89%B9%E5%BE%81
getter方法用于获得属性值,setter方法用于设置属性,这是JS提供的存取器特性即使用函数来管理属性。
const data = Symbol();
const phone = {
[data]:{
name: '华为nova3',
price: 2000
},
set name(value){
this[data].name = value
},
get name(){
return this[data].name
}
};
phone.name= 'ipone';
console.log(phone.name); //iphone
console.log(phone.data); //undefined
代理(拦截器)是对象的访问控制,setter/getter 是对单个对象属性的控制,而代理是对整个对象的控制。
对象的代理
"use strict";
const hd = {
name: "后盾人" };
const proxy = new Proxy(hd, {
get(obj, property) {
return obj[property];
},
set(obj, property, value) {
obj[property] = value;
return true;
}
});
proxy.age = 10;
console.log(hd);
函数的代理
如果代理以函数方式执行时,会执行代理中定义 apply 方法。
参数说明:函数,上下文对象,参数
下面使用 apply 计算函数执行时间
function factorial(num) {
return num == 1 ? 1 : num * factorial(num - 1);
}
let proxy = new Proxy(factorial, {
apply(func, obj, args) {
console.time("run");
func.apply(obj, args);
console.timeEnd("run");
}
});
proxy.apply(this, [1, 2, 3]);
vue的双向绑定
"use strict";
const obj = {
};
const proxy = new Proxy(obj, {
get(obj, property) {
},
set(obj, property, value) {
obj[property] = value;
document.querySelectorAll(`[v-model=${
property}]`).forEach(el=>{
el.value = value
})
document.querySelectorAll(`[v-bind=${
property}]`).forEach(el=>{
el.innerHTML = value
})
return true
}
});
const els = document.querySelectorAll('[v-model]');
els.forEach(el => {
el.addEventListener('keyup', function () {
proxy[el.getAttribute('v-model')] = el.value;
})
})
js转json JSON.stringify()
<script>
const obj = {
name: '张三',
score: 99,
hobby:[
'足球','篮球','打游戏'
]
}
const json = JSON.stringify(obj,null,2);
console.log(json);
可以为数据添加 toJSON 方法来自定义返回格式
例子来自后盾人
let hd = {
"title": "后盾人",
"url": "houdunren.com",
"teacher": {
"name": "向军大叔",
},
"toJSON": function () {
return {
"title": this.url,
"name": this.teacher.name
};
}
}
console.log(JSON.stringify(hd)); //{"title":"houdunren.com","name":"向军大叔"}
使用 JSON.parse 将字符串 json 解析成对象
const obj = {
name: '张三',
score: 99,
hobby:[
'足球','篮球','打游戏'
]
}
const json = JSON.stringify(obj,null,2);//转换为json是字符串类型
console.log(json);
const data= JSON.parse(json);//转回js对象类型
console.log(data);
可使用第二个参数函数来对返回的数据二次处理
let hd = {
title: "后盾人",
url: "houdunren.com",
teacher: {
name: "向军大叔"
}
};
let jsonStr = JSON.stringify(hd);
console.log(
JSON.parse(jsonStr, (key, value) => {
if (key == "title") {
return `[推荐] ${
value}`;
}
return value;
})
);
let array= ["a"];
console.log(array.concat("b"));
console.log(array);
这就是使用了数组的原型方法。
默认情况下创建对象都会有原型。
创建一个默认原型为空的对象
const car = {
name: '奔驰'
}
//创建的对象默认原型为Object 原型的方法hasOwnProperty判断对象对是否含有某属性
console.log(car.hasOwnProperty('name'));//true
//接下来创建一个新对象,默认原型为空 第一个参数为默认原型,第二个为对象的特征描述
const car2 = Object.create(null, {
name: {
value: "宝马",
enumerable: true //默认不可遍历
}
});
console.log(car2.hasOwnProperty('name'));//报错 car2.hasOwnProperty is not a function
//Object.keys是静态方法,不是原型方法所以是可以使用的
console.log(Object.keys(car2));
函数拥有多个原型,prototype 用于实例对象使用,__proto__用于函数对象使用
我们随便创建一个构造函数,再用它实例化一个对象。输出一下它的结构
function Car(name,price){
this.name=name
this.price = price
}
const benchi = new Car();
console.dir(Car);
console.dir(benchi);
我们可以看到Car函数有prototype和__proto__,其中__proto__是供Car本身来使用的。比如说bind(),call()等方法,都是在__proto__原型中的。而prototype则在它创建对象时,把prototype的内容作为__proto__中的内容传送给实例化出来的对象。
也就是说 console.log(benchi.proto===Car.prototype);
function Car(name,price){
this.name=name
this.price = price
}
Car.__proto__.run=function(){
console.log('Car跑起来了');
}
Car.run(); //本Car是没有run的方法的,但我们可以在它的原型上添加。添加之后Car就可以用run方法了
const benchi = new Car();
benchi.run()//报错 由于run方法是添加到__proto__上的,所以实例化出来的benchi不具备run方法
Car.prototype.view=function(){
//但是方法添加到Car的prototype
console.log('车可供人欣赏');
}
benchi.view();// 可调用,benchi实例化之后__propto__会获得Car的prototype中所有的方法,因为方法添加到了原型上,所以和示例化的顺序无关。
下面使用 setPrototypeOf 与 getPrototypeOf 获取与设置原型,操作的是__proto__而不是prototype
const obj = {
};
const parent = {
name: '父亲'};
Object.setPrototypeOf(obj,parent);
// obj.__proto__= parent //这个写法与方面相同
console.dir(obj);
constructor存在于prototype原型中,用于指向构建函数的引用。
function Car(){
this.run= function(){
console.log("跑起来了");
}
}
const benchi = new Car();
console.dir(benchi); //发现__proto__中包含constructor
//那么我们是不是可以用 benchi对象来构建另一个对象呢
const constructor = benchi.__proto__.constructor;//这个__proto__可以省略。因为它会自动去原型中查找方法
const baoma = new constructor();
baoma.run(); //运行成功
通过引用类型的原型,继承另一个引用类型的属性与方法,这就是实现继承的步骤。
对象的检查 hasOwnProperty 和 in
对象设置属性,只是修改对象属性并不会修改原型属性,使用hasOwnProperty 判断对象本身是否含有属性并不会检测原型。
使用 in 会检测原型与对象,而 hasOwnProperty 只检测对象,所以结合后可判断属性是否在原型中
function Parent(){
}
const zhangsan = new Parent();
const lisi = new Parent();
zhangsan.name= '张三';
zhangsan.__proto__.name = '中国人';//在zhangsan的原型上添加属性
console.log(zhangsan.hasOwnProperty('name'));//检查zhangsan是否有name属性
console.log(lisi.name); //因为lisi里面没有name属性,所以它去原型中查找
console.log(lisi.__proto__===zhangsan.__proto__);//true
delete zhangsan.name;
console.log(zhangsan.hasOwnProperty('name'));//false
console.log('name' in zhangsan) //true
console.log(zhangsan.name); //中国人
方法定义在函数中,每次实例化都会产生一个相同的方法。浪费空间。
function User(name) {
this.name = name;
this.get = function () {
return this.name;
};
}
let lisi = new User("小明");
let wangwu = new User("王五");
console.log(lisi.get == wangwu.get); //false
为了解决这个问题,我们可以把方法添加到原型上面。
function User(name) {
this.name = name;
}
User.prototype.get = function() {
return this.name;
};
let lisi = new User("小明");
let wangwu = new User("王五");
console.log(lisi.get == wangwu.get); //true
//通过修改原型方法会影响所有对象调用,因为方法是共用的
lisi.__proto__.get = function() {
return "啦啦啦" + this.name;
};
console.log(lisi.get()); //啦啦啦小明
console.log(wangwu.get()); //啦啦啦王五
我们希望调用父类构造函数完成对象的属性初始化,但像下面这样使用是不会成功的。因为此时 this 指向了window,无法为当前对象声明属性。
function User(name) {
this.name = name;
console.log(this);// Window
}
User.prototype.getUserName = function() {
return this.name;
};
function Admin(name) {
User(name);
}
Admin.prototype = Object.create(User.prototype);
Admin.prototype.role = function() {
};
let zhangsan = new Admin("张三");
console.log(zhangsan.getUserName()); //undefined
解决上面的问题是使用 call/apply 为每个生成的对象设置属性
function User(name) {
this.name = name;
console.log(this); // Admin
}
User.prototype.getUserName = function() {
return this.name;
};
function Admin(name) {
User.call(this, name);
}
Admin.prototype = Object.create(User.prototype);
let zhangsan = new Admin("zhangsan ");
console.log(zhangsan.getUserName()); //zhangsan
类的本质就是在操作原型
静态属性 与 静态属性可以直接用类型调用,它是定义到对象本身上面的。普通方法和普通属性是定义到实例化之后的对象上面
用构造函数的方法声明静态方法和静态属性
function Car(name){
this.name = name;
this.run= function(){
console.log("普通方法----跑起来了");
}
}
Car.name='车'
Car.run = function(){
console.log("静态方法----跑起来了");
}
console.dir(Car);
现在有了类 能更方便的定义类了
class Car{
constructor(name){
this.name=name
}
color = 'red'
run=()=>{
console.log('普通方法 跑起来了');
}
static run=function(){
console.log('静态方法 跑起来了');
}
static name= '静态方法 名字'
}
console.dir(Car);
Car.run();
console.log(Car.name);
const benchi = new Car('奔驰');
console.log(benchi);
属性名和方法名之前加#就可把它变成私有属性,外部不能随意访问和修改,子类也不行。
class Car{
#name= '张三'
}
const benchi = new Car();
console.log(benchi.#name='李四');
class Car{
#name= '奔驰'
#run = () =>{
console.log('跑起来');
}
get name(){
return this.#name;
}
set name(name){
this.#name = name;
return true
}
}
const benchi = new Car();
// benchi.#run() //报错
console.log(benchi.name='宝马');
function Parent(name,age){
this.name =name
this.age =age
}
function Son(...args){
Parent.apply(this,args)
}
Son.prototype= Object.create(Parent.prototype);
const zhangsan = new Son('张三',18)
console.log(zhangsan);
用class继承构造函数
class Parent{
constructor(name,age){
this.name=name;
this.age = age;
}
height = 180 //这里直接添加到prototype上了
}
class Son extends Parent{
constructor(...args){
//这个可以不写,系统默认写
super(...args);
}
}
const zhangsan = new Son('张三',18);
console.log(zhangsan);
原始的方法继承
function Parent(){
}
Parent.prototype.show= function(){
console.log('show');
}
function Son(){
}
Son.prototype= Object.create(Parent.prototype);
Son.prototype.constructior = Son;
const zhangsan = new Son();
zhangsan.show(); //成功打印show
class的方法继承
class Parent{
show = ()=>{
//添加直接添加到prototype属性上面
console.log('show');
}
}
class Son extends Parent{
}
const zhangsan = new Son();
zhangsan.show()
静态方法的继承
原理
function Parent (){
}
Parent.name = '张三';
Parent.show= function(){
console.log('我叫张三');
}
function Son(){
}
Son.__proto__ = Parent;
Son.show();
console.log(Son.name);
class类 同理
class Parent{
static name = '张三'
static show= function(){
console.log('wo shi zhang san');
}
}
class Son extends Parent{
}
console.log(Son.name);
Son.show();
class Parent{
}
class Son extends Parent{
}
const zhangsan = new Son();
console.log(zhangsan instanceof Son);
console.log(zhangsan instanceof Parent);
console.log(Son.prototype.isPrototypeOf(zhangsan));
console.log(Parent.prototype.isPrototypeOf(zhangsan));
同步方法先执行 > promise完成后放入微观任务队列中 > 定时器之类的异步方法执行完毕之后放入宏观任务队列最后执行
promise 创建时即立即执行即同步任务,then 会放在异步微任务中执行,需要等同步任务执行后才执行。
promise 的 then、catch、finally的方法都是异步任务
程序需要将主任务执行完成才会执行异步队列任务
setTimeout(()=>{
console.log('定时器'); //宏观任务
},0)
new Promise(resolve=>{
resolve(); //发送通知,执行成功方法 微观任务
console.log('任务执行中'); //promise中的任务为同步
}).then(values=>console.log('成功'));
console.log('同步');
Promise
状态被改变后就不能再修改了,下面先通过resolve 改变为成功状态,表示promise 状态已经完成,就不能使用 reject 更改状态了
const p1 = new Promise((resolve, reject) => {
resolve("fulfilled");
reject("rejected");
});
const p2 = new Promise(resolve => {
resolve(p1);
}).then(
value => {
console.log(value);
},
reason => {
console.log(reason);
}
);
当promise做为参数传递时,需要等待promise执行完才可以继承,下面的p2需要等待p1执行完成。
因为p2 的resolve 返回了 p1 的promise,所以此时p2 的then 方法已经是p1 的了
正因为以上原因 then 的第一个函数输出了 p1 的 resolve 的参数
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("操作成功");
}, 2000);
});
const p2 = new Promise((resolve, reject) => {
resolve(p1);
}).then(
msg => {
console.log(msg);
},
error => {
console.log(error);
}
);
then返回的也是一个promise,所以可以链式操作。
每次的 then 都是一个全新的 promise,默认 then 返回的 promise 状态是 fulfilled
const p = new Promise((resolve,reject)=>{
// resolve("成功");
resolve("失败");
})
p.then(success=>{
console.log(success);
},(msg)=>{
console.log(msg);
}).then(success=>{
//默认返回成功
console.log('success');
},(msg)=>{
console.log('msg');
})
当把script写在外部时,浏览器会自动把script移入body中
<!DOCTYPE html>
<html lang="en">
<head>
</head>
<body>
</body>
<script>
console.log('张三');
</script>
</html>
id没有加引号,浏览器也会自动补齐
<div id=div></div>
<body>
<table>
zhangsan
<tr>
<td>lisi</td>
</tr>
</table>
</body>
获取元素时,元素必须已经渲染到页面上。否则就向下例一样获取不到
<body>
<script>
const div = document.querySelector('#div')
console.log(div);
</script>
<div id="div"></div>
</body>
解决方法,把script移到div下面 或者 用onload等待页面渲染完之后执行onload里面的代码。也可以采用定时器异步等方法。
<body>
<script>
window.onload = ()=>{
const div = document.querySelector('#div')
console.log(div);
}
</script>
<div id="div"></div>
</body>
JS中的内容称为DOM节点对象(node),即然是对象就包括操作NODE的属性和方法
<body id="hdcms">
<!-- 后盾人 -->
</body>
<script>
// document节点 noteType为9
console.log(document.nodeType)
// 第一个子节点为,且nodetype为10
console.log(document.childNodes.item(0).nodeType)
// body 是标签节点 nodeType为1
console.log(document.body.nodeType)
// body的属性节点nodeType 为2
console.log(document.body.attributes[0].nodeType)
// body的第一个节点为文本节点,nodeType为3
console.log(document.body.childNodes.item(0).nodeType)
// body的第二个节点为注释,nodeType类型为8
console.log(document.body.childNodes[1].nodeType)
</script>
在浏览器渲染过程中会将文档内容生成为不同的对象
遍历出h1的原型链
<body>
<h1 id="title">大家好</h1>
<script>
function prototype(el) {
const parent = Object.getPrototypeOf(el)
console.log(parent);
parent ? prototype(parent) : null
}
const title = document.querySelector('#title');
prototype(title);
console.log(title.__proto__ === HTMLHeadingElement.prototype);
console.log(HTMLHeadingElement.prototype.__proto__ === HTMLElement.prototype);
</script>
</body>
原型 | 说明 |
---|---|
Object | 根对象 |
EventTarget | 提供事件支持 |
Node | 提供parentNode等节点操作方法 |
Element | 提供getElementsByTagName、querySelector等方法 |
HTMLElement | 所有元素的基础类,提供className、nodeName等方法 |
HTMLHeadingElement | Head标题元素类 |
利用原型,添加一个截取字符串方法
<body>
<input type="text" value="Hello everyone" />
<div id="div">Hello World</div>
<script>
// console.log(document.querySelector('[type=text]').__proto__);//HTMLInputElement
class Util {
static isForm = function (node) {
return node instanceof HTMLInputElement
}
}
Node.prototype.substr = function (start, num) {
if (Util.isForm(this)) {
return this.value.substr(start, num)
} else {
return this.textContent.substr(start, num)
}
}
//div
console.log(document.querySelector('#div').substr(1, 2))
//input
console.log(document.querySelector('[type=text]').substr(3, 5))
</script>
</body>
既然DOM与我们其他js创建的对象特征相仿,所以也可以为DOM对象添加属性或方法。
对于系统应用的属性,应明确含义不应该随意使用,比如ID是用于标识元素唯一属性,不能用于其他目的
下面的直接修改ID属性是不建议的
<div id="div">Hello World</div>
<script>
const el = document.querySelector('#div');
el.id= '张三'
const el2 = document.querySelector('#div');//空
</script>
<body>
<div id="div">Hello World</div>
<script>
const el = document.querySelector('#div');
console.dir(el);
Object.assign(el,{
onclick(){
this.style.color = 'red'
},
innerHTML: '你好世界'
})
</script>
</body>
使用对象特性更改样式属性
<body>
<div id="div">Hello World</div>
<script>
const el = document.querySelector('#div');
console.dir(el);
Object.assign(el.style,{
color: 'red',
fontSize: '30px'
})
</script>
</body>
document是window对象的属性,是由HTMLDocument类实现的示例。
原型链中也包含Node,所以可以使用有关节点操作的方法如nodeType/NodeName等
<body>
<div id="div">Hello World</div>
<script>
console.dir(document.nodeName)
console.dir(document.nodeType)
function prototype(el) {
const parent = Object.getPrototypeOf(el)
console.log(parent);
parent ? prototype(parent) : null
}
prototype(document)
</script>
</body>
Html
下面通过节点的nodeType来获取元素,因为标签的nodeType为1
<body>
<div id="div">Hello World</div>
<script>
window.onload = function () {
let html = [...document.childNodes].filter((node) => {
if (node.nodeType === 1) {
return node
}
})[0]
console.log(html)
}
</script>
</body>
系统提供了简单的方式来获取html元素,效果与上面相同
console.log(document.documentElement)
//设置文档标签
document.title = 'hello world'
//获取文档标题
console.log(document.title);
//获取当前URL
console.log(document.URL)
//获取域名
document.domain
//获取来源地址
console.log(document.referrer)
不同类型的节点拥有不同属性,下面是节点属性的说明与示例
nodeType指以数值返回节点的类型
nodeType | 说明 |
---|---|
1 | 元素节点 |
2 | 属性节点 |
3 | 文本节点 |
8 | 注释节点 |
9 | document对象 |
下面是节点nodeType的示例
<body>
<div id="app">
<div class="houdunren" data="hd">houdunren.com</div>
<div class="hdcms">hdcms.com</div>
<div class="xiangjun"><!-- 向军大叔 --></div>
</div>
<script>
const node = document.querySelector(`#app`)
console.log(node.nodeType) //1
console.log(node.firstChild.nodeType) //3 空格文本
const xj = document.querySelector('.xiangjun')
console.log(xj.childNodes[0].nodeType) //8 注释
</script>
</body>
递归获取所有标签元素
<body>
<div id="div">
<ul>
<li>
<h2><strong>hello world</strong></h2>
</li>
</ul>
<div id='div2'>
小飞象
</div>
</div>
<script>
const array = [];
function getAll (el){
[...el.childNodes].map((e)=>{
if(e.nodeType==1){
array.push(e)
getAll(e)
}
})
}
getAll(document.body)
console.log(array);
</script>
</body>
nodeName
nodeName指定节点的名称
获取值为大写形式
nodeType | nodeName |
---|---|
1 | 元素名称如DIV |
2 | 属性名称 |
3 | #text |
8 | #comment |
下面来操作 nodeName
<body>
<div id="app">
<div class="houdunren" data="hd">houdunren.com</div>
<div class="hdcms">hdcms.com</div>
<div class="xiangjun"><!-- 向军大叔 --></div>
</div>
<script>
const node = document.querySelector(`#app`)
console.log(node.nodeName) //DIV
console.log(node.firstChild.nodeName) //#text
const xj = document.querySelector('.xiangjun')
console.log(xj.childNodes[0].nodeName) //#comment
</script>
</body>
** tagName**
nodeName可以获取不限于元素的节点名,tagName仅能用于获取标签元素节点名称。
用tagName遍历元素,因为它只能获取标签,可以利用这一特性。
<body>
<div id="div">
<ul>
<li>
<h2><strong>hello world</strong></h2>
</li>
</ul>
<div id='div2'>
小飞象
</div>
</div>
<script>
const array = [];
function getAll(el){
[...el.childNodes].forEach(e=>{
if(e.tagName){
array.push(e.tagName)
getAll(e)
}
})
}
getAll(document.querySelector('#div'))
console.log(array);
</script>
</body>
nodeValue
使用nodeValue或data函数获取节点值,也可以使用节点的data属性获取节点内容
nodeType | nodeValue |
---|---|
1 | null |
2 | 属性值 |
3 | 文本内容 |
8 | 注释内容 |
下面来看nodeValue的示例,直接采用后盾人的例子
<div id="app">
<div class="houdunren" data="hd">houdunren.com</div>
<div class="hdcms">hdcms.com</div>
<div class="xiangjun"><!-- 向军大叔 --></div>
</div>
<script>
const node = document.querySelector(`#app`)
console.log(node.nodeValue) //null
const hdcms = document.querySelector('.hdcms')
console.log(hdcms.firstChild.nodeValue) //hdcms.com
const xj = document.querySelector('.xiangjun')
console.log(xj.childNodes[0].nodeValue) // 向军大叔
</script>
使用data属性获取节点内容
<div id="app">
houdunren.com
</div>
<script>
const app = document.querySelector('#app')
console.log(app.childNodes[0].data) //houdunren.com
</script>
** 树状节点**
下面获取标签树状结构即多级标签结构,来加深一下nodeType/nodeName等知识
document.documentElement 是获取全部的标签
<!DOCTYPE html>
<html lang="en">
<head >
</head>
<body>
<div id="app">
hello world
</div>
<script>
function getAll(el){
if(el.nodeType!=1) return //如果传入的不是标签直接退出
return [...el.childNodes].filter(e=> e.nodeType==1) //判断子节点是否为标签节点。如果是就进入遍历
.map(e=>{
return {
name: e.tagName, //e.nodeName
children: getAll(e) //遍历子标签
}
})
}
const nodes=getAll(document.documentElement);
console.log(nodes);
</script>
</body>
</html>
Nodelist与HTMLCollection都是包含多个节点标签的集合,大部分功能也是相同的。
length
Nodelist与HTMLCollection包含length属性,记录了节点元素的数量
<body>
<div name="app">
<div id="div">小飞象</div>
<div name="feixiang">xiaofeixiang</div>
</div>
<script>
const nodes = document.getElementsByTagName('div')
const nodes2 = document.querySelectorAll('div')
for (let i = 0; i < nodes.length; i++) {
console.log(nodes[i])
}
console.log(nodes);
console.log(nodes2);
</script>
</body>
有时候使用数组方法来操作节点集合,这就需要将集合节点转化为数组类型,有以下几种方法可以实现。
使用call调用原型方法 array.form …展开语法
<body>
<div name="app">
<div id="div">小飞象</div>
<div name="feixiang">xiaofeixiang</div>
</div>
<script>
const nodes = document.getElementsByTagName('div')
console.log([...nodes]);
console.log(Array.from(nodes));
console.log(Array.prototype.slice.call(nodes,0));
</script>
</body>
item
Nodelist与HTMLCollection提供了item()方法来根据索引获取元素
<body>
<div name="app">
<div id="div">小飞象</div>
<div name="feixiang">xiaofeixiang</div>
</div>
<script>
const nodes = document.getElementsByTagName('div')
console.log(nodes.item(1));
console.log(nodes[2]); //使用数组索引获取更方便
</script>
</body>
namedItem
HTMLCollection具有namedItem方法可以按name或id属性来获取元素
<body>
<div name="app">
<div id="div">小飞象</div>
<div name="feixiang">xiaofeixiang</div>
</div>
<script>
const nodes = document.getElementsByTagName('div')
console.log(nodes.namedItem('app')); //按name属性查找
console.log(nodes.namedItem('div')); //按div查找
console.log(nodes['feixiang']); //也可以使用数组或属性方式获取
console.log(nodes.feixiang);
</script>
</body>
数组检索时使用item方法,字符串索引时使用namedItem方法
<body>
<div name="app">
<div id="div">小飞象</div>
<div name="feixiang">xiaofeixiang</div>
</div>
<script>
const nodes = document.getElementsByTagName('div')
console.log(nodes[0]); //按照索引
console.log(nodes['feixiang']); //按照name或id
</script>
</body>
常用元素
系统针对特定标签提供了快速选择的方式
比如获取所有的超链接
<body>
<div name="app">
<a href="#">小飞象</a>
<a href="#">xiaofeixiang</a>
</div>
<script>
console.log(document.links);
</script>
</body>
方法 | 说明 |
---|---|
document.documentElement | 文档节点即html标签节点 |
document.body | body标签节点 |
document.head | head标签节点 |
document.links | 超链接集合 |
document.anchors | 所有锚点集合 |
document.forms | form表单集合 |
document.images | 图片集合 |
节点是父子级嵌套与前后兄弟关系,使用DOM提供的API可以获取这种关系的元素。
基础知识
节点是根据HTML内容产生的,所以也存在父子、兄弟、祖先、后代等节点关系
节点属性 | 说明 |
---|---|
childNodes | 获取所有子节点 |
parentNode | 获取父节点 |
firstChild | 子节点中第一个 |
lastChild | 子节点中最后一个 |
nextSibling | 下一个兄弟节点 |
previousSibling | 上一个兄弟节点 |
子节点集合与首、尾节点获取
<div id="app">
<div class="houdunren" data="hd">houdunren.com</div>
<div class="hdcms">hdcms.com</div>
<div class="xiangjun">向军大叔</div>
</div>
<script>
const node = document.querySelector(`#app`)
console.log(node.childNodes) //所有子节点
console.log(node.firstChild) //第一个子节点是文本节点
console.log(node.lastChild) //最后一个子节点也是文本节点
</script>
<body>
<div id="app">
<div class="houdunren" data="hd">houdunren.com</div>
<div class="hdcms">hdcms.com</div>
<div class="xiangjun">向军大叔</div>
</div>
<script>
const node = app.querySelector(`.hdcms`)
console.log(node.parentNode) //div#app
console.log(node.childNodes) //文本节点
console.log(node.nextSibling) //下一个兄弟节点是文本节点
console.log(node.previousSibling) //上一个节点也是文本节点
</script>
</body>
document是顶级节点html标签的父节点是document
<script>
console.log(document.documentElement.parentNode === document)
</script>
元素关系
使用childNodes等获取的节点包括文本与注释,但这不是我们常用的,系统也提供了只操作元素的关系方法。
节点属性 | 说明 |
---|---|
parentElement | 获取父元素 |
children | 获取所有子元素 |
childElementCount | 子标签元素的数量 |
firstElementChild | 第一个子标签 |
lastElementChild | 最后一个子标签 |
previousElementSibling | 上一个兄弟标签 |
nextElementSibling | 下一个兄弟标签 |
contains | 返回布尔值,判断传入的节点是否为该节点的后代节点 |
以下实例展示怎样通过元素关系获取元素
<div id="app">
<div class="houdunren" data="hd">houdunren.com</div>
<div class="hdcms">hdcms.com</div>
<div class="xiangjun"><!-- 向军大叔 --></div>
</div>
<script>
const app = document.querySelector(`#app`)
console.log(app.children) //所有子元素
console.log(app.firstElementChild) //第一个子元素 div.houdunren
console.log(app.lastElementChild) //最后一个子元素 div.xiangjun
const hdcms = document.querySelector('.hdcms')
console.log(hdcms.parentElement) //父元素 div#app
console.log(hdcms.previousElementSibling) //上一个兄弟元素 div.houdunren
console.log(hdcms.nextElementSibling) //下一个兄弟元素 div.xiangjun
</script>
html标签的父节点是document,但父标签节点不存在
<script>
console.log(document.documentElement.parentNode === document) //true
console.log(document.documentElement.parentElement) //null
</script>
系统提供了丰富的选择节点(NODE)的操作方法
使用ID选择是非常方便的选择具有ID值的节点元素,但注意ID应该是唯一的
getElementById只能通过document访问,不能通过元素读取拥有ID的子元素
拥有ID的元素可做为WINDOW的属性进行访问
如果声明了变量这种访问方式将无效,所以并不建议使用这种方式访问对象
<body>
<div id="app">
123
</div>
<script>
app.innerText= '456'
console.log(app.innerText); //456
</script>
</body>
使用getElementByName获取去设置了name属性的元素,虽然在DIV等元素上同样有效,但一般用来对表单元素进行操作时使用。
<body>
<div name="houdunren">houdunren.com</div>
<input type="text" name="username" />
<script>
const div = document.getElementsByName('houdunren')
console.dir(div)
const input = document.getElementsByName('username')
console.dir(input)
</script>
</body>
使用getElementsByTagName用于按标签名获取元素
<div name="houdunren">houdunren.com</div>
<div id="app"></div>
<script>
const divs = document.getElementsByTagName('div')
console.dir(divs)
</script>
通配符
可以使用通配符 ***** 获取所有元素
<div name="houdunren">houdunren.com</div>
<div id="app"></div>
<script>
const nodes = document.getElementsByTagName('*')
console.dir(nodes)
</script>
某个元素也可以使用通配置符 ***** 获取后代元素,下面获取 id为houdunren的所有后代元素
<div id="houdunren">
<span>houdunren.com</span>
<span>hdcms.com</span>
</div>
<script>
const nodes = document.getElementsByTagName('*').namedItem('houdunren').getElementsByTagName('*')
console.dir(nodes) //打印的是两个span标签
</script>
getElementsByClassName用于按class样式属性值获取元素集合
<body>
<div class="houdunren hdcms xiangjun">houdunren.com</div>
<div class="hdcms">hdcms.com</div>
<script>
const nodes = document.getElementsByClassName('hdcms')
console.log(nodes.length) //2
//查找包含class属性包括 hdcms 与 houdunren 的元素
const tags = document.getElementsByClassName('hdcms houdunren')
console.log(tags.length) //1
</script>
</body>
<body>
<div id="app">
<div>小飞象</div>
<div>
<ul>
<li>js</li>
<li>css</li>
<li>react</li>
<li>实战</li>
<li>找工作</li>
</ul>
</div>
</div>
<script>
const nodes = document.querySelector('#app');
const els = []
function getAll(el){
[...el.children].map(e=>{
if(e.children){
els.push(e)
getAll(e);
}
})
}
getAll(nodes)
console.log(els);
for(let i=0 ; i<els.length ; i++){
console.log(els[i]);
}
</script>
</body>
根据节点列表的length属性,使用for遍历
Nodelist节点列表也可以使用forEach来进行遍历,但HTMLCollection则不可以
节点集合对象原型中不存在map方法,但可以借用Array的原型map方法实现遍历
<div id="app">
<div>小飞象</div>
<div>
<ul>
<li>js</li>
<li>css</li>
<li>react</li>
<li>实战</li>
<li>找工作</li>
</ul>
</div>
</div>
<script>
const nodes = document.querySelectorAll('div')
Array.prototype.map.call(nodes, (node, index) => {
console.log(node, index)
})
</script>
也可以转为数组进行操作
<body>
<div id="app">
<div>小飞象</div>
<div>
<ul>
<li>js</li>
<li>css</li>
<li>react</li>
<li>实战</li>
<li>找工作</li>
</ul>
</div>
</div>
<script>
const nodes = document.querySelectorAll('div');
[...nodes].map( (node, index) => {
console.log(node, index)
})
</script>
</body>
节点集合是类数组的可迭代对象所以可以使用for…of进行遍历
<body>
<div id="app">
<div>小飞象</div>
<div>
<ul>
<li>js</li>
<li>css</li>
<li>react</li>
<li>实战</li>
<li>找工作</li>
</ul>
</div>
</div>
<script>
const nodes = document.querySelectorAll('div');
for(node of nodes){
console.log(node);
}
</script>
</body>
在css中可以通过样式选择器修饰元素样式,在DOM操作中也可以使用这种方式查找元素。
使用getElementsByTagName等方式选择元素不够灵活,使用样式选择器,更加灵活方便。
使用querySelectorAll根据css选择器获取Nodelist节点列表
获取的Nodelist节点列表是静态的,添加或删除元素后不变
querySelector使用CSS选择器获取一个元素,下面是根据属性获取单个元素
用于检测元素是否是指定的样式选择器匹配,下面过滤掉所有name属性的li元素
<body>
<div id="app">
<li>houdunren</li>
<li>向军大叔</li>
<li name="hdcms">hdcms.com</li>
</div>
<script>
const app = document.getElementById('app')
const nodes = [...app.querySelectorAll('li')].filter((node) => {
return !node.matches(`[name]`); //返回的是布尔值
})
console.log(nodes)
</script>
</body>
查找最近的符合选择器的祖先元素(包括自身),下例查找父级拥有 .comment类的元素
<div class="comment">
<ul class="comment">
<li>houdunren.com</li>
</ul>
</div>
<script>
const li = document.getElementsByTagName('li')[0]
const node = li.closest(`.comment`)
console.log(node)
</script>
通过getElementsByTagname等getElementsBy… 函数获取的Nodelist与HTMLCollection集合是动态的,即有元素添加或移动操作将实时反映最新状态。
下例中通过按钮动态添加元素后,获取的元素集合是动态的
<h1>houdunren.com</h1>
<h1>hdcms.com</h1>
<button id="add">添加元素</button>
<script>
let elements = document.getElementsByTagName('h1')
console.log(elements)
let button = document.querySelector('#add')
button.addEventListener('click', () => {
document.querySelector('body').insertAdjacentHTML('beforeend', '向军大叔
')
console.log(elements) //每添加一个标题,elements也跟着变化
})
</script>
document.querySelectorAll获取的集合是静态的
<h1>houdunren.com</h1>
<h1>hdcms.com</h1>
<button id="add">添加元素</button>
<script>
let elements = document.querySelectorAll('h1')
console.log(elements.length)
let button = document.querySelector('#add')
button.addEventListener('click', () => {
document.querySelector('body').insertAdjacentHTML('beforeend', '向军大叔
')
console.log(elements.length) //不管添加多少个标题,长度始终是2
})
</script>
如果需要保存静态集合,则需要对集合进行复制
<body>
<div id="houdunren">houdunren.com</div>
<div name="hdcms">hdcms.com</div>
<script>
const array1 = document.getElementsByTagName('div');
const clone = [...array1];
console.log('array1.length ' + array1.length);
console.log('clone.length ' + clone.length);
document.body.appendChild(document.createElement('div'))
console.log('array1.length ' + array1.length);
console.log('clone.length ' + clone.length);
</script>
</body>
元素的标准属性具有相对应的DOM对象属性
有些属性名与JS关键词冲突,系统已经起了别名
属性 | 别名 |
---|---|
class | className |
for | htmlFor |
使用属性设置图片的路径
使用hidden隐藏元素
<body>
<img src="" alt="">
<script>
const img = document.querySelector('img');
img.src= 'https://img-blog.csdnimg.cn/20210403173200136.png';
img.alt= '小飞象'
img.addEventListener('click',function(){
this.hidden = true
})
</script>
</body>
大部分属性值都是字符串,但并不是全部。比如说表单的checked就是布尔型。
<label for="hot"> <input id="hot" type="checkbox" name="hot" />热门 </label>
<script>
const node = document.querySelector(`[name='hot']`)
node.addEventListener('change', function () {
console.log(this.checked)
})
</script>
属性值并都与HTML定义的值一样
对于标准的属性可以使用DOM属性的方式进行操作,但对于标签的非标准的定制属性则不可以。但JS提供了方法来控制标准或非标准属性
可以理解为元素的属性分两个地方保存,DOM属性中记录标准属性,特征中记录标准和定制属性。
方法 | 说明 |
---|---|
getAttribute | 获取属性 |
setAttribute | 设置属性 |
removeAttribute | 删除属性 |
hasAttribute | 属性检测 |
特征是可迭代对象
<body>
<div id="app" content="小飞象" color="red">hdcms.com</div>
<script>
const node = document.querySelector(`#app`)
for (const a of node.attributes) {
console.log(a);
}
</script>
</body>
特征值与HTML定义是一致的,这和属性值是不同的
<a href="#houdunren" id="home">后盾人</a>
<script>
const node = document.querySelector(`#home`)
// http://127.0.0.1:5500/test.html#houdunren
console.log(node.href)
// #houdunren
console.log(node.getAttribute('href'))
</script>
元素提供了attributes属性可以只读的获取元素的属性
<div class="hdcms" data-content="后盾人">hdcms.com</div>
<script>
let hdcms = document.querySelector('.hdcms')
console.dir(hdcms.attributes['class'].nodeValue) //hdcms
console.dir(hdcms.attributes['data-content'].nodeValue) //后盾人
</script>
虽然可以随意定义特征并使用getAttribute等方法管理。但很容易造成与标签的现在或未来属性重名。建议使用以data-为前缀的自定义特征处理,针对这种定义方式JS也提供了接口方便操作。
<body>
<div id="app" data-content="小飞象" data-el-color="red">hdcms.com</div>
<script>
const node = document.querySelector(`#app`);
let color=node.dataset.elColor; //采用驼峰的方式
console.log(color);
color = node.dataset.elColor = 'blue';
console.log(color);
</script>
</body>
特征和属性是记录元素属性的两大不同场所。大部分更改会进行同步操作。
下面使用属性更改了className,会自动同步到了特征集中,反之亦然。
<body>
<div id="app" class="blue" data-content="小飞象" data-el-color="red">hdcms.com</div>
<script>
const node = document.querySelector(`#app`);
node.className = 'red';
console.log(node.getAttribute('class'));
node.setAttribute('class','green');
console.log(node.className);
</script>
</body>
但是input的value是单项的,修改特征会同步到属性。修改属性,不会影响特征
创建节点就是构建出DOM对象,然后根据需要添加到其他节点中
创建文本对象并添加到元素中
<body>
<div id="app">你的名字是:</div>
<script>
const node = document.querySelector(`#app`);
const text =document.createTextNode('张三');
console.log(typeof text); //object
node.append(text)
</script>
</body>
使用该方法可以创建标签节点对象,创建span标签新节点并添加到div中
<body>
<div id="app">你是谁:</div>
<script>
const node = document.querySelector(`#app`);
let span =document.createElement('span');
span.innerHTML='我是新创建的标签';
node.append(span)
</script>
</body>
使用PROMISE结合节点操作来加载外部JAVASCRIPT文件
function js(file) {
return new Promise((resolve, reject) => {
let js = document.createElement('script')
js.type = 'text/javascript'
js.src = file
js.onload = resolve
js.onerror = reject
document.head.appendChild(js)
})
}
js('1.js')
.then(() => console.log('加载成功'))
.catch((error) => console.log(`${
error.target.src} 加载失败`))
使用cloneNode和document.importNode用于复制节点对象操作
<body>
<div id="app"><h1>我是个标题 <span> 深拷贝能复制我 </span></h1></div>
<script>
const node = document.querySelector(`#app`);
const title = document.querySelector(`h1`);
const cloneTitle = title.cloneNode(true);
node.append(cloneTitle)
</script>
</body>
document.importNode方法是部分IE浏览器不支持的,也是复制节点对象的方法
第一个参数为节点对象
第二个参数为true时递归复制
<body>
<div id="app"><h1>我是个标题 <span> 深拷贝能复制我 </span></h1></div>
<script>
const node = document.querySelector(`#app`);
const title = document.querySelector(`h1`);
const cloneTitle = document.importNode(title,true);
node.append(cloneTitle)
</script>
</body>
innerHTML用于向标签中添加html内容,同时触发浏览器的解析器重绘DOM
下例使用innerHTML获取和设置div内容
innerHTML中只解析HTML标签语法,所以其中的script不会作为js处理
<div id="app">
<div class="houdunren" data="hd">houdunren.com</div>
<div class="hdcms">hdcms.com</div>
</div>
<script>
let app = document.querySelector('#app')
console.log(app.innerHTML)
app.innerHTML = '后盾人
'
</script>
重绘节点
使用innerHTML操作会重绘元素,下面在点击第二次就没有效果了
<body>
<div id="app">
<button>houdunren.com</button>
<img src="https://img0.baidu.com/it/u=3225163326,3627210682&fm=26&fmt=auto&gp=0.jpg" alt="" />
</div>
<script>
const app = document.querySelector('#app')
app.querySelector('button').addEventListener('click', function () {
alert(this.innerHTML)
this.parentElement.innerHTML += '
向军大叔'
})
</script>
</body>
outerHTML与innerHTML的区别是包含父标签
<body>
<div id="app">
<div class="houdunren" data="hd">houdunren.com</div>
<div class="hdcms">hdcms.com</div>
</div>
<script>
let app = document.querySelector('#app')
app.outerHTML = '后盾人
'
console.log(app.outerHTML)
</script>
</body>
<body>
<div id="app">
<div class="houdunren" data="hd">houdunren.com</div>
<div class="hdcms">hdcms.com</div>
</div>
<script>
let app = document.querySelector('#app')
app.innerHTML = '后盾人
'
console.log(app.innerHTML)
</script>
</body>
textContent与innerText是访问或添加文本内容到元素中
<body>
<div id="app">
<h1>houdunren.com</h1>
</div>
<script>
let app = document.querySelector('#app')
console.log(app.textContent)
</script>
</body>
<body>
<div id="app">
<div class="houdunren" data="hd">houdunren.com</div>
<div class="hdcms">hdcms.com</div>
</div>
<script>
let app = document.querySelector('#app')
app.textContent="后盾人
"
</script>
</body>
将文本插入到元素指定位置,不会对文本中的标签进行解析,包括以下位置
选择 | 说明 |
---|---|
beforebegin | 元素本身前面 |
afterend | 元素本身后面 |
afterbegin | 元素内部前面 |
beforeend | 元素内部后面 |
<body>
<div id="app" style="display:inline-block;line-height: 50px;text-align: center;background-color: pink; width: 300px; height: 50px;">
我是div
</div>
<script>
let app = document.querySelector('#app')
app.insertAdjacentText('beforebegin','元素本身前面')
</script>
</body>
方法 | 说明 |
---|---|
append | 节点尾部添加新节点或字符串 |
prepend | 节点开始添加新节点或字符串 |
before | 节点前面添加新节点或字符串 |
after | 节点后面添加新节点或字符串 |
replaceWith | 将节点替换为新节点或字符串 |
使用remove方法可以删除节点
将html文本插入到元素指定位置,浏览器会对文本进行标签解析,包括以下位置
在div#app前添加HTML文本
<div id="app">
<div class="houdunren" data="hd">houdunren.com</div>
<div class="hdcms">hdcms.com</div>
</div>
<script>
let app = document.querySelector('#app')
let span = document.createElement('span')
app.insertAdjacentHTML('beforebegin', '后盾人
')
</script>
insertAdjacentElement() 方法将指定元素插入到元素的指定位置,包括以下位置
第一个参数是位置
第二个参数为新元素节点
在div#app 前插入span标签
<div id="app">
<div class="houdunren" data="hd">houdunren.com</div>
<div class="hdcms">hdcms.com</div>
</div>
<script>
let app = document.querySelector('#app')
let span = document.createElement('span')
span.innerHTML = '后盾人'
app.insertAdjacentElement('beforebegin', span)
</script>
JS为表单的操作提供了单独的集合控制
<body>
<form action="" name="hd">
<input type="text" name="title" />
</form>
<script>
const form = document.forms.hd
console.log(form.title) // console.log(form.elements.title)
</script>
</body>
通过表单项可以反向查找FORM
<form action="" name="hd">
<input type="text" name="title" />
</form>
<script>
const form = document.forms.hd
console.log(form.title.form === form) //true
</script>
通过DOM修改样式可以通过修改元素的class属性或通过style对象设置行样式来完成
建议使用class控制样式,将任务交给css处理。
使用JS的className可以批量设置样式
<head>
<style>
.pink{
background-color: pink;
}
.fontSize{
font-size: 30px;
}
</style>
</head>
<body>
<div id="text" class="pink">
白日依山尽,黄河入海流
</div>
<script>
const el = document.querySelector('#text');
el.className += ' fontSize' //前面有空格 要形成class='pink fontSize'的样式
</script>
</body>
也可以通过特征的方式修改
<head>
<style>
.pink{
background-color: pink;
}
.fontSize{
font-size: 30px;
}
</style>
</head>
<body>
<div id="text" class="pink">
白日依山尽,黄河入海流
</div>
<script>
const el = document.querySelector('#text');
el.setAttribute('class','pink fontSize')
</script>
</body>
如果对类单独进行控制使用classList属性操作
方法 | 说明 |
---|---|
node.classList.add | 添加类名 |
node.classList.remove | 删除类名 |
node.classList.toggle | 切换类名 |
node.classList.contains | 类名检测 |
<head>
<style>
.pink{
background-color: pink;
}
.fontSize{
font-size: 30px;
}
</style>
</head>
<body>
<div id="text" class="pink">
白日依山尽,黄河入海流
</div>
<script>
const el = document.querySelector('#text');
el.classList.add('fontSize')
</script>
</body>
使用style对象可以对样式属性单独设置,使用cssText可以批量设置行样式
样式属性设置
使用节点的style对象来设置行样式
多个单词的属性使用驼峰进行命名
<div id="app" class="d-flex container">后盾人</div>
<script>
let app = document.getElementById('app')
app.style.backgroundColor = 'red'
app.style.color = 'yellow'
</script>
批量设置行样式
使用 cssText属性可以批量设置行样式,属性名和写CSS一样不需要考虑驼峰命名
<div id="app" class="d-flex container">后盾人</div>
<script>
let app = document.getElementById('app')
app.style.cssText = `background-color:red;color:yellow`
</script>
也可以通过setAttribute改变style特征来批量设置样式
<div id="app" class="d-flex container">后盾人</div>
<script>
let app = document.getElementById('app')
app.setAttribute('style', `background-color:red;color:yellow;`)
</script>
style
可以通过DOM对象的style属性读取行样式
style对象不能获取行样式外定义的样式
<style>
div {
color: yellow;
}
</style>
<div id="app" style="background-color: red; margin: 20px;">后盾人</div>
<script>
let app = document.getElementById('app')
console.log(app.style.backgroundColor)
console.log(app.style.margin)
console.log(app.style.marginTop)
console.log(app.style.color) //没有值,因为style只能获取行内定义的样式
使用window.getComputedStyle可获取所有应用在元素上的样式属性
函数第一个参数为元素
第二个参数为伪类
这是计算后的样式属性,所以取得的单位和定义时的可能会有不同
<body>
<style>
div {
font-size: 35px;
color: yellow;
}
</style>
<div id="app" style="background-color: red; margin: 20px;">后盾人</div>
<script>
let app = document.getElementById('app')
let fontSize = window.getComputedStyle(app).fontSize
console.log(fontSize.slice(0, -2))//去掉px
console.log(parseInt(fontSize))
</script>
</body>
首先理解视口(窗口)与文档的含义
方法 | 说明 | 注意 |
---|---|---|
window.innerWidth | 视口宽度 | 包括滚动条(不常用) |
window.innerHeight | 视口高度 | 包括滚动条(不常用) |
document.documentElement.clientWidth | 视口宽度 | |
document.documentElement.clientHeight | 视口高度 |
方法 | 说明 | 备注 |
---|---|---|
element.getBoundingClientRect | 返回元素在视口坐标及元素大小,包括外边距,width/height与offsetWidth/offsetHeight匹配 | 窗口坐标 |
element.getClientRects | 行级元素每行尺寸位置组成的数组 | |
element.offsetParent | 拥有定位属性的父级,或body/td/th/table | 对于隐藏元素/body/html值为null |
element.offsetWidth | 元素宽度尺寸,包括内边距与边框和滚动条 | |
element.offsetHeight | 元素高度尺寸,包括内边距与边框和滚动条 | |
element.offsetLeft | 相对于祖先元素的x轴坐标 | |
element.offsetTop | 相对于祖先元素的y轴坐标 | |
element.clientWidth | 元素宽度,不包含边框,只包含内容和内边距,行元素尺寸为0 | |
element.clientHeight | 元素高度,不包含边框,只包含内容和内边距,行元素尺寸为0 | |
element.clientLeft | 内容距离外部的距离,滚动条在左侧时包括滚动条的尺寸 | |
element.clientTop | 内容距离顶部的距离,滚动条在顶部时包括滚动条尺寸 | |
element.scrollWidth | 元素宽度,内容+内边距+内容溢出的尺寸 | 元素可滚动的最大距离 |
element.scrollHeight | 元素高度,内容+内边距+内容溢出的尺寸 | |
element.scrollLeft | 水平滚动条左侧已经滚动的宽度 | |
element.scrollTop | 垂直滚动条顶部已经滚动的高度 |
为什么不要使用getComputedStyle
使用 getBoundingClientRect获取元素相对于文档的几何坐标信息
<style>
* {
padding: 0;
margin: 0;
}
main {
padding: 200px;
position: relative;
}
#app {
width: 200px;
background: #e34334;
margin: 100px;
padding: 50px;
border: 20px solid #efbc0f;
color: white;
text-align: center;
}
</style>
<main>
<div id="app">houdunren.com</div>
</main>
<script>
let app = document.getElementById('app')
let info = app.getBoundingClientRect()
console.table(info)
</script>
getClientRects用于返回多行元素所占的尺寸,下面示例将为每行返回所占的空间尺寸
<style>
span {
width: 200px;
overflow: auto;
}
</style>
<span>
网页很多都是多屏,所以文档尺寸一般大于视口尺寸,当打开控制台后,视口尺寸相应变小。网页很多都是多屏,所以文档尺寸一般大于视口尺寸,当打开控制台后,视口尺寸相应变小。网页很多都是多屏,所以文档尺寸一般大于视口尺寸,当打开控制台后,视口尺寸相应变小。
</span>
<script>
let span = document.querySelector('span')
let info = span.getClientRects()
console.log(info)
</script>
js提供了方法获取指定坐标上的元素,如果指定坐标点在视口外,返回值为null
方法 | 说明 |
---|---|
element.elementsFromPoint | 返回指定坐标点所在的元素集合 |
element.elementFromPoint | 返回指定坐标点最底层的元素 |
返回指定坐标点上的元素集合
<body>
<style>
div {
width: 200px;
height: 200px;
background: pink;
}
</style>
<div>
</div>
<script>
let els = document.elementsFromPoint(100,100)
console.log(els); //[div,body,html]
let el = document.elementFromPoint(100,100)
console.log(el); // 获取到div标签
</script>
</body>
下方掌握文档或元素的滚动操作
方法 | 说明 | 备注 |
---|---|---|
element.scrollLeft | 获取和设置元素x轴滚动位置 | |
element.scrollTop | 获取和设置元素y轴滚动位置 | |
element.scrollBy() | 按偏移量进行滚动内容 | 参数为对象,{top:垂直偏移量,left:水平偏移量,behavior:滚动方式} |
element.scroll()或element.scrollTo() | 滚动到指定的具体位置 | 参数为对象,{top:x轴文档位置,left:y轴文档位置,behavior:滚动方式} |
element.scrollIntroView(boolean) | 定位到顶部或底部 | 参数为true元素定位到底部,为false定位窗口底部 |
下例是查看文档滚动的X/Y坐标示例,请在控制台查看结果
也可以使用window.pageXOffset对象属性获取
<body>
<style>
div {
width: 2000px;
height: 2000px;
background: pink;
}
</style>
<div>
</div>
<script>
// window.onscroll = function () {
// console.log(document.querySelector('html').scrollTop)
// console.log(document.documentElement.scrollLeft)
// }
window.onscroll = function () {
console.log(window.pageXOffset)
console.log(window.pageYOffset)
}
</script>
</body>
<body>
<style>
#parent {
width: 200px;
height: 200px;
background: pink;
overflow: auto;
}
#son{
width: 400px;
height: 400px;
}
</style>
<div id="parent">
<div id="son">
</div>
</div>
<script>
document.getElementById('parent').addEventListener('scroll',function(){
console.log(this.scrollLeft);
console.log(this.scrollTop);
})
</script>
</body>
下面介绍的是控制元素滚动的操作方法
scrollBy
behavior:smooth 为平滑滚动 按照偏移量进行移动
<body>
<script>
setTimeout(()=>{
document.documentElement.scrollBy({
top: 300, behavior: 'smooth'})
},1000)
</script>
</body>
scroll
使用scroll滚动到指定位置 移动到指定位置
<body>
<script>
setTimeout(()=>{
document.documentElement.scrollBy({
top: 300, behavior: 'smooth'})
},1000)
</script>
</body>
scrollIntoView
使用元素scrollIntoView方法实现滚动操作,参数可以是布尔值和对象
<head>
<style>
div {
height: 2000px;
background: red;
border-top: solid 50px #efbc0f;
border-bottom: solid 50px #1bb491;
}
span {
border-radius: 50%;
color: #fff;
background: #000;
width: 50px;
height: 50px;
display: block;
text-align: center;
line-height: 50px;
position: fixed;
top: 50%;
right: 50px;
border: solid 2px #ddd;
}
</style>
<div id="app">hdcms.com</div>
<span>TOP</span>
<script>
document.querySelector('span').addEventListener('click', () => {
let app = document.querySelector('#app')
app.scrollIntoView({
block: 'end', behavior: 'smooth' })
})
</script>
</body>
事件的目的是要执行一段代码,我们称这类代码为事件处理程序。当在对象上触发事件时就会执行定义的事件处理程序
可以在html元素上设置事件处理程序,浏览器解析后会绑定到DOM属性中
<button onclick="alert(`houdunren.com`)">后盾人</button>
往往事件处理程序业务比较复杂,所以绑定方法或函数会常见
<button onclick="show()">后盾人</button>
<script>
function show() {
alert('houdunren.com')
}
</script>
也可以使用方法作为事件处理程序
<input type="text" onkeyup="HD.show()" />
<script>
class HD {
static show() {
console.log('houdunren')
}
}
</script>
可以传递事件源对象与事件对象
<body>
<button onclick="show(this,'houdunren','hdcms','向军大叔',event)">后盾人</button>
<script>
function show(...args) {
console.log(args)
}
</script>
</body>
也可以将事件处理程序绑定到DOM属性中
<div id="app">houdunren.com</div>
<script>
const app = document.querySelector('#app')
app.onclick = function () {
this.style.color = 'red'
}
</script>
无法为事件类型绑定多个事件处理程序,下面绑定了多个事件处理程序,因为属性是相同的所以只有最后一个有效
<div id="app">houdunren.com</div>
<script>
const app = document.querySelector('#app')
app.onclick = function () {
this.style.color = 'red'
}
app.onclick = function () {
this.style.fontSize = '55px'
}
</script>
通过上面的说明我们知道使用HTML与DOM绑定事件都有缺陷,所以我们可以使用事件监听的方法
使用addEventListener添加事件处理程序有以下几个特点
方法 | 说明 |
---|---|
addEventListener | 添加事件处理程序 |
removeEventListener | 移除事件处理程序 |
设置多个事件处理程序,按设置的顺序先后执行
<body>
<div id="app">houdunren.com</div>
<script>
const app = document.querySelector('#app')
app.addEventListener('click', function () {
this.style.color = 'red'
})
app.addEventListener('click', function () {
this.style.fontSize = '55px'
})
</script>
</body>
若果事件处理程序可以使对象,对象的handleEvent方法会作为事件处理程序执行。
<body>
<div id="app">houdunren.com</div>
<script>
const app = document.querySelector('#app')
class HD {
handleEvent(e) {
//e为鼠标事件
console.log(e.type);//事件类型
this[e.type](e) //this.click(e) 相同
}
click(e) {
console.log('单击事件')
}
mouseover(e) {
console.log('鼠标移动事件')
}
}
app.addEventListener('click', new HD())
app.addEventListener('mouseover', new HD())
</script>
</body>
###移除事件
使用removeEventListener删除绑定的事件处理程序
事件处理程序单独定义函数或方法,这可以保证事件处理程序是同一个
<div id="app">houdunren.com</div>
<button id="hd">删除事件</button>
<script>
const app = document.querySelector('#app')
const hd = document.querySelector('#hd')
function show() {
console.log('APP我执行了')
}
app.addEventListener('click', show)
hd.addEventListener('click', function () {
app.removeEventListener('click', show)
})
</script>
addEventListener的第三个参数为定制的选项,可传递object或boolean类型
下面是传递对象时的说明
once | 可选参数 | 说明 |
---|---|---|
once | true/false | 只执行一次事件 |
capture | true/false | 事件是在捕获/冒泡哪个阶段执行,true为捕获阶段 false为冒泡阶段 |
passive | true/false | 声明事件里不会调用 preventDefault(),可减少系统默认行为的等待 |
下面使用once:true 来指定事件只执行一次
<body>
<button id="app">houdunren.com</button>
<script>
const app = document.querySelector('#app')
app.addEventListener(
'click',
function () {
alert('houdunren@向军大叔')
},
{
once: true }
)
</script>
</body>
设置{capture:true} 或直接设置第三个参数为true用来在捕获阶段执行事件
addEventListener的第三个参数传递true/false 和设置 {capture:true/false}是一样
<div id="app" style="background-color: red">
<button id="bt">houdunren.com</button>
</div>
<script>
const app = document.querySelector('#app')
const bt = document.querySelector('#bt')
app.addEventListener(
'click',
function () {
alert('这是div事件 ')
},
{
capture: true }
)
bt.addEventListener(
'click',
function () {
alert('这是按钮事件 ') //点击按钮先出来div事件然后才是按钮事件
},
{
capture: true }
)
</script>
设置 { capture: false } 或直接设置第三个参数为false用来在冒泡阶段执行事件
<div id="app" style="background-color: red">
<button id="bt">houdunren.com</button>
</div>
<script>
const app = document.querySelector('#app')
const bt = document.querySelector('#bt')
app.addEventListener(
'click',
function () {
alert('这是div事件 ')
},
{
capture: false }
)
bt.addEventListener(
'click',
function () {
alert('这是按钮事件 ')//点击按钮时,先触发按钮事件,然后才是div事件
},
{
capture: false }
)
</script>
执行事件处理程序时,会产生当前事件相关信息的对象,即为事件对象。系统会自动做为参数传递给事件处理程序。
属性 | 说明 |
---|---|
type | 事件类型 |
currentTarget | 当前执行事件的对象 |
target | 事件目标对象,冒泡方式时父级对象可以通过该属性找到在哪个子元素上最终执行事件 |
x | 相对窗口的x坐标 |
y | 相对窗口的y坐标 |
clientX | 相对窗口的x坐标 |
clientY | 相对窗口的y坐标 |
screenX | 相对计算机屏幕的x坐标 |
screenY | 相对计算机屏幕的y坐标 |
pageX | 相对于文档的x坐标 |
pageY | 相对于文档的y坐标 |
offsetY | 相对于事件对象的x坐标 |
标签元素是嵌套的,在一个元素上触发的事件,同时也会向上执行父级元素的事件处理程序,一直到HTML标签元素
冒泡过程中的任何事件处理程序中,都可以执行 event.stopPropagation() 方法阻止继续进行冒泡传递
<body>
<style>
#app {
background: #34495e;
width: 300px;
padding: 30px;
}
#app h2 {
background-color: #f1c40f;
}
</style>
<div id="app">
<h2>我是h2子元素</h2>
</div>
<script>
const app = document.querySelector('#app')
const h2 = document.querySelector('h2')
app.addEventListener('click', (event) => {
console.log(`event.currentTarget:${
event.currentTarget.nodeName}`)
console.log(`event.target:${
event.target.nodeName}`)
console.log('app event')
})
h2.addEventListener('click', (event) => {
event.stopPropagation() //阻止冒泡
//event.stopImmediatePropagation(); //下面h2绑定的点击事件和冒泡不会触发
console.log(`event.currentTarget:${
event.currentTarget.nodeName}`)
console.log(`event.target:${
event.target.nodeName}`)
console.log(`h2 event`)
})
h2.addEventListener('click', (event) => {
console.log('h2 的第二个事件处理程序')
})
</script>
</body>
事件执行顺序为 捕获>事件目标>冒泡,在向下传递到目标对象的过程即为事件捕获。
通过设置第三个参数为true或{capture:true}在捕获阶段执行事件处理程序
上面事件选择有例子
借助事件冒泡思路,我们可以不为子元素设置事件,而将事件设置在父级。返回通过父级事件对象的event.target查找子元素,并对他做出处理。
<style>
.hd {
border: solid 2px #ddd;
background-color: red;
color: white;
}
</style>
<ul>
<li>houdunren.com</li>
<li>hdcms.com</li>
</ul>
<script>
'use strict'
const ul = document.querySelector('ul')
ul.addEventListener('click', () => {
if (event.target.tagName === 'LI') event.target.classList.toggle('hd')
})
</script>
事件代理例子2
<body>
<div class="tab">
<dl>
<dt data-action="toggle">在线教程</dt>
<dd data-action="hidden">houdunren.com</dd>
</dl>
<dl>
<dt data-action="toggle">开源软件</dt>
<dd data-action="hidden">hdcms.com</dd>
</dl>
</div>
<script>
class HD {
constructor(el) {
this.el = el
el.addEventListener('click', (event)=>{
const action = event.target.dataset.action
if (action) this[action]()
})
}
hidden() {
event.target.hidden = true
}
toggle() {
this.el.querySelectorAll(`[data-action='hidden']`).forEach((e) => (e.hidden = true))
event.target.nextElementSibling.hidden = false
}
}
new HD(document.querySelector('.tab'))
</script>
</body>
下面使用事件代理来对未来元素进行事件绑定
<body>
<button>
添加一个小li
</button>
<ul id="list">
</ul>
<script>
let i=0;
const ul = document.querySelector('#list');
document.querySelector('button').addEventListener('click',()=>{
const li = document.createElement('li');
li.innerHTML= `我是${
i++}号`;
ul.append(li);
})
ul.addEventListener('click',(e)=>{
console.log(e.target.innerHTML);
})
</script>
</body>
js中有些对象会设置默认事件处理程序,比如a标签点击时会进行跳转
一般默认处理程序会在用户自定义的处理程序后执行,所有我们可以在我们定义的事件处理中取消默认事件处理程序的执行。
<a href="https://www.houdunren.com">后盾人</a>
<script>
document.querySelector('a').addEventListener('click', () => {
event.preventDefault()
alert(event.target.innerText)
})
</script>
事件名 | 说明 |
---|---|
window.onload | 文档解析及外部资源加载后 |
DOMContentLoaded | 文档解析后执行,不需要等待图片/样式文件等外部资源加载,该事件只能通过addEventListener设置 |
window.beforeunload | 文档刷新或关闭时 |
window.unload | 文档卸载时 |
scroll | 页面滚动时 |
window.onload事件在文档解析后及图片、外部样式文件等资源加载完后执行
<script>
window.onload = function () {
alert('houdunren.com')
}
</script>
<div id="app">houdunren.com</div>
DOMContentLoaded事件在文档标签解析后执行,不需要等外部图片、样式文件、JS文件等样式资源加载
<script>
window.addEventListener('DOMContentLoaded', (event) => {
console.log('houdunren.com')
})
</script>
<div id="app">houdunren.com</div>
当浏览器窗口关闭或者刷新时,会触发beforeunload事件,可以取消关闭或刷新页面
window.onbeforeunload = function (e) {
return '真的要离开吗?'
}
window.unload事件在文档资源被卸载时执行
window.addEventListener('unload', function (e) {
localStorage.setItem('name', 'houdunren')
})
<body>
houdunren.com
<script>
document.addEventListener('copy', () => {
event.preventDefault()
alert('禁止复制内容')
})
</script>
</body>