JavaScript知识点,看不懂陪你去爬山

312 阅读23分钟

1. 什么是作用域

​ 常见的作用域主要分为几个类型:全局作用域函数作用域块状作用域动态作用域

对象 类型
global/window 全局作用域
function 函数作用域(局部作用域
{ } 块状作用域
this 动态作用域

变量或者其他表达式不在 “当前的作用域”,那么JavaScript机制会继续沿着作用域链上查找直到全局作用域(global或浏览器中的 window)如果找不到将不可被使用。 作用域也可以根据代码层次分层,以便子作用域可以访问父作用域,通常是指沿着链式的作用域链查找 而不能从父作用域引用子作用域中的变量和引用

1.1全局作用域

	变量在函数或者代码块{}外定义,即为全局作用域。不过,在函数或者代码块{}内未定义的变量也是拥有全局作用域的。
var name = "Sheep";  
// 此处可调用name变量 
function myFunction() {     
    // 函数内可调用 name 变量 
}

	上述代码中变量 name 就是在函数外定义的,它是拥有全局作用域的。这个变量可以在任意地方被读取或者修改,当然如果变量在函数内没有声明(没有使用 var 关键字),该变量依然为全局变量。
// 此处可调用 name 变量
function myFunction() {      	
    name = "Sheep";    
    // 此处可调用 name 变量 
}

	以上实例中 name 在函数内,但是拥有全局作用域,它将作为 global 或者 window 的属性存在。(检验window的属性可以在控制台中delete XXX,如果是window的属性则返回true,否则返回false)。

在函数内部或代码块中没有定义的变量实际上是作为 window/global 的属性存在,而不是全局变量。换句话说没有使用 var 定义的变量虽然拥 有全局作用域,但是它是可以被 delete 的,而全局变量不可以。

1.2函数作用域

	在函数内部定义的变量,就是局部作用域。函数作用域内,对外是封闭的,从外层的作用域无法直接访问函数内部的作用域。
	在函数作用域中,不带var的变量一定是全局的。
	*在函数作用域中,用var定义的变量会将声明提升到函数作用域的顶端,但是不会调用函数。

fn();
function fn() {
    console.log('hi');
}
//结果:控制台打印字符串  hi
//注意:函数声明代表函数整体,所以函数提升后,函数名代表整个函数,但是函数并没有被调用。
//函数表达式创建函数,会执行变量提升,此时接收函数的变量名无法正确的调用
fn();
var  fn = function() {
    console.log('hello');
}

//结果:报错提示 ”fn is not a function"
//解释:该段代码执行之前,会做变量声明提升,fn在提升之后的值是undefined;而fn调用是在fn被赋值为函数体之前,此时fn的值是undefined,所以无法正确调用

2.Let & Const

2.1 let 声明的变量拥有块级作用域

{   
    let a = 1 
} 
console.log(a); //undefined

Tip: a变量是在代码块{}中使用 let 定义的,它的作用域是这个代码块内部,外部无法访问。

2.2 let 声明的全局变量不是全局对象的属性

	不可以通过 window.变量名的方式访问这些变量,而 var 声明的全局变量是 window 的属性,是可以通过 window.变量名 的方式访的。
var a = 1 
console.log(window.a); //1

let a = 1 
console.log(window.a); // undefined

2.3 用let重定义变量会抛出一个语法错误

var 可以重复定义,使用 let 却不可以。
var a = 1 var a = 2 
console.log(a)  //2

let a = 1 
let a = 2 // Uncaught SyntaxError: Identifier 'a' has already been declared

2.4 let声明的变量不会进行变量提升

function test () {   
    console.log(a)   
    var a = 1 
} 
 
test() //undefined

上述代码中,a 的调用在声明之前,所以它的值是 undefined,而不是 Uncaught ReferenceError。实际上因为 var 会导致变量提升,上述代码和 的代码等同:
function test () {   
    var a   
    console.log(a)   
    a = 1 
} 

3、数组

3、1数组的遍历

  • 普通for循环

    支持break和continue

const array=[1,2,,3,4,5]
for(var i =0; i < array.length; i++){       			console.log(array[i]);
}
  • 数组的forEach()

    语法简洁,不需要通过索引去访问数组项,然而它的缺点也是很明显,不支持 break、continue 等。

array.forEach(function(i){  
    console.log(i);
})


//如下代码想遍历数组,遇到数组项 3 之后就结束遍历,不然打印出所遍历过的数值项
[1,2,3,4,5].forEach(function(i){
    if(i===3){
        return;
    }else{    
        console.log(i);
    }
})
//输出: 1,2,4,5

[!DANGER] forEach 的代码块中不能使用 break、continue,它会抛出异常

  • 数组的every()

    使用 every 遍历就可以做到 break 那样的效果,简单的说 return false 等同于 break,return true 等同于 continue。

[1,2,3,4,5].every(function(i){
    if(i===3){
        return false;
    }else{    
        console.log(i);
        return true;
    }
})

[!DANGER] every 的代码块中不能使用 break、continue,它会抛出异常。

  • for...in

    用for in不仅可以对数组,也可以对enumerable对象操作。

    通常用for in来遍历对象的键名。

    支持break和continue。

var enum = {
    a:1,
    b:2,
    c:3,
    d:"sheep"
};  
for(let item in enum) {  
    console.log(item,enum[item]);  
} 

  • for...of

    for of遍历的只是数组内的元素,而不包括数组的原型属性method和索引name

var arr=[1,2,4,5,6,7]
arr.name="sheep";
for (var value of arr) {
  console.log(value);
}

4、类型转换

字符串长度: 变量.length可以获取字符串中字符的个数

空字符串的长度是0

字符串类型的数据,当是数字的时候,在进行乘除减的运算时,会先将字符串类型的数据

隐式转换成数值类型的数据,再进行计算,结果也是数值类型的数据.

字符串类型的数据在做相加运算的时候,是字符串的拼接

字符串类型的数据和任何类型的数据进行相加操作都是拼接

布尔类型中的true可以转换成数值类型的1,false可以转换成数值类型的0

字符串类型的数据和任何类型的数据进行相加操作都是拼接

5、函数

5.1 函数声明的方式

(1)、有名函数:命名函数,有函数名的函数

有名函数定义方式:
1.定义函数名的方式   function 函数名(){}
2.函数表达式的方式   var sum = function(){},调用要在定义之后,否则会报错


//定义函数名的方式
function fn(){
    alert('定义函数名的方式');
}
			

//函数表达式方式
//sum();  //报错
var sum = function (a,b){
    //alert('函数表达式方式');
    return a+b;
}
//sum();

(2)、匿名函数:没有函数名的函数. 不能直接定义

匿名函数定义的方式:
1.作为函数表达式的方式定义
2.作为自调用函数的方式定义,自调用函数在定义的时候就立即调用,而且只能用一次
3.作为参数传递
4.作为事件处理函数
5.作为返回值


//错误的
//	function(){		
//}
(function(){
    alert('函数自调用1');
})();
			
(function(){
    alert('函数自调用2');
})();

5.2函数形参和实参数量不匹配时

注意:在JavaScript中,形参的默认值是undefined。
  • 函数可以带参数也可以不带参数
  • 声明函数的时候,函数名括号里面的是形参,形参的默认值为 undefined
  • 调用函数的时候,函数名括号里面的是实参
  • 多个参数中间用逗号分隔
  • 形参的个数可以和实参个数不匹配,但是结果不可预计,我们尽量要匹配

5.3 break ,continue ,return 的区别

  • break :结束当前的循环体(如 for、while)
  • continue :跳出本次循环,继续执行下次循环(如 for、while)
  • return :不仅可以退出循环,还能够返回 return 语句中的值,同时还可以结束当前的函数体内的代码

5.4 arguments的使用

​ 当不确定有多少个参数传递的时候,可以用 arguments 来获取。JavaScript 中,arguments实际上它是当前函数的一个内置对象。所有函数都内置了一个 arguments 对象,arguments 对象中存储了传递的所有实参。arguments展示形式是一个伪数组,因此可以进行遍历。伪数组具有以下特点:

  • 具有 length 属性

  • 按索引方式储存数据

  • 不具有数组的 push , pop 等方法

    注意:在函数内部使用该对象,用此对象获取函数调用时传的实参。

5.5 构造函数和原形

5.5.1对象的三种创建方式

  1. 字面量方式

    var obj = {};
    
  2. new关键字

    var obj = new Object();
    
  3. 构造函数方式

    function Person(name,age){
      this.name = name;
      this.age = age;
    }
    var obj = new Person('sheep',23);
    

5.5.2构造函数原型prototype

构造函数通过原型分配的函数是所有对象所共享的。

JavaScript 规定,每一个构造函数都有一个prototype 属性,指向另一个对象。注意这个prototype就是一个对象,这个对象的所有属性和方法,都会被构造函数所拥有。

我们可以把那些不变的方法,直接定义在 prototype 对象上,这样所有对象的实例就可以共享这些方法。

function Star(uname, age) {
    this.uname = uname;
    this.age = age;
}
Star.prototype.sing = function() {
	console.log('我会唱歌');
}
var ldh = new Star('刘德华', 18);
var zxy = new Star('张学友', 19);
ldh.sing();//我会唱歌
zxy.sing();//我会唱歌

5.5.3对象原型

对象都会有一个属性 __proto__ 指向构造函数的 prototype 原型对象,之所以我们对象可以使用构造函数 prototype 原型对象的属性和方法,就是因为对象有 __proto__ 原型的存在。
__proto__对象原型和原型对象 prototype 是等价的
__proto__对象原型的意义就在于为对象的查找机制提供一个方向,或者说一条路线,但是它是一个非标准属性,因此实际开发中,不可以使用这个属性,它只是内部指向原型对象 prototype

5.5.4 constructor构造函数

对象原型( __proto__)和构造函数(prototype)原型对象里面都有一个属性 constructor 属性 ,constructor 我们称为构造函数,因为它指回构造函数本身。
constructor 主要用于记录该对象引用于哪个构造函数,它可以让原型对象重新指向原来的构造函数。
一般情况下,对象的方法都在构造函数的原型对象中设置。如果有多个对象的方法,我们可以给原型对象采取对象形式赋值,但是这样就会覆盖构造函数原型对象原来的内容,这样修改后的原型对象 constructor  就不再指向当前构造函数了。此时,我们可以在修改后的原型对象中,添加一个 constructor 指向原来的构造函数。

如果我们修改了原来的原型对象,给原型对象赋值的是一个对象,则必须手动的利用constructor指回原来的构造函数如:

 function Star(uname, age) {
     this.uname = uname;
     this.age = age;
 }
 // 很多情况下,我们需要手动的利用constructor 这个属性指回 原来的构造函数
 Star.prototype = {
 // 如果我们修改了原来的原型对象,给原型对象赋值的是一个对象,则必须手动的利用constructor指回原来的构造函数
   constructor: Star, // 手动设置指回原来的构造函数
   sing: function() {
     console.log('我会唱歌');
   },
   movie: function() {
     console.log('我会演电影');
   }
}
var zxy = new Star('张学友', 19);
console.log(zxy)

以上代码运行结果,设置constructor属性如图:

如果未设置constructor属性,如图:

5.5.5原型链

每一个实例对象有一个__proto__属性,指向的构造函数的原型对象,构造函数的原型对象也是一个对象,也有__proto__属性,这样一层一层往上找就形成了原型链。

5.5.6构造函数实例和原型对象三角关系

1.构造函数的prototype属性指向了构造函数原型对象
2.实例对象是由构造函数创建的,实例对象的__proto__属性指向了构造函数的原型对象
3.构造函数的原型对象的constructor属性指向了构造函数,实例对象的原型的constructor属性也指向了构造函数

5.5.7原型链和成员的查找机制

任何对象都有原型对象,也就是prototype属性,任何原型对象也是一个对象,该对象就有__proto__属性,这样一层一层往上找,就形成了一条链,我们称此为原型链;

当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。
如果没有就查找它的原型(也就是 __proto__指向的 prototype 原型对象)。
如果还没有就查找原型对象的原型(Object的原型对象)。
依此类推一直找到 Object 为止(null)。
__proto__对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线。

5.5.8原型对象中this指向

构造函数中的this和原型对象的this,都指向我们new出来的实例对象

function Star(uname, age) {
    this.uname = uname;
    this.age = age;
}
var that;
Star.prototype.sing = function() {
    console.log('我会唱歌');
    that = this;
}
var ldh = new Star('刘德华', 18);
// 1. 在构造函数中,里面this指向的是对象实例 ldh
console.log(that === ldh);//true
// 2.原型对象函数里面的this 指向的是 实例对象 ldh

5.5.9通过原型为数组扩展内置方法

 Array.prototype.sum = function() {
   var sum = 0;
   for (var i = 0; i < this.length; i++) {
   sum += this[i];
   }
   return sum;
 };
 //此时数组对象中已经存在sum()方法了  可以始终 数组.sum()进行数据的求

5.6继承

5.6.1 call()

  • call()可以调用函数
  • call()可以修改this的指向,使用call()的时候 参数一是修改后的this指向,参数2,参数3..使用逗号隔开连接
 function fn(x, y) {
     console.log(this);
     console.log(x + y);
}
  var o = {
  	name: 'andy'
  };
  fn.call(o, 1, 2);//调用了函数此时的this指向了对象o,

5.6..2子构造函数继承父构造函数中的属性

  1. 先定义一个父构造函数
  2. 再定义一个子构造函数
  3. 子构造函数继承父构造函数的属性(使用call方法)
 // 1. 父构造函数
 function Father(uname, age) {
   // this 指向父构造函数的对象实例
   this.uname = uname;
   this.age = age;
 }
  // 2 .子构造函数 
function Son(uname, age, score) {
  // this 指向子构造函数的对象实例
  3.使用call方式实现子继承父的属性
  Father.call(this, uname, age);
  this.score = score;
}
var son = new Son('刘德华', 18, 100);
console.log(son);

5.6.3借用原型对象继承方法

  1. 先定义一个父构造函数
  2. 再定义一个子构造函数
  3. 子构造函数继承父构造函数的属性(使用call方法)
// 1. 父构造函数
function Father(uname, age) {
  // this 指向父构造函数的对象实例
  this.uname = uname;
  this.age = age;
}
Father.prototype.money = function() {
  console.log(100000);
 };
 // 2 .子构造函数 
  function Son(uname, age, score) {
      // this 指向子构造函数的对象实例
      Father.call(this, uname, age);
      this.score = score;
  }
// Son.prototype = Father.prototype;  这样直接赋值会有问题,如果修改了子原型对象,父原型对象也会跟着一起变化
  Son.prototype = new Father();
  // 如果利用对象的形式修改了原型对象,别忘了利用constructor 指回原来的构造函数
  Son.prototype.constructor = Son;
  // 这个是子构造函数专门的方法
  Son.prototype.exam = function() {
    console.log('孩子要考试');

  }
  var son = new Son('刘德华', 18, 100);
  console.log(son);

如上代码结果如图:

6、浏览器兼容性

6.1不同版本的浏览器

  • 高级浏览器: chrome firefox ie9以上
  • 低版本浏览器: ie 6 7 8

6.2 兼容性封装innerText和textContent

innerText属性是微软给ie浏览器制定的,后来被所有浏览器广泛使用,但是在之前的某个版本的firefox浏览器不能使用此属性。 textContent属性是后来出现的属性,功能和innerText一样,所有高级浏览器可以使用,低版本浏览器不能使用。

// 让所有的高级浏览器使用textContent,让ie低版本使用innerText
/**
* 兼容性封装读取元素内容
* @param {元素对象} obj
*/
function getTextContent(obj){
	if(obj.textContent){
		//alert('高级浏览器');
		return obj.textContent;
	}else{
	//alert('低级浏览器');
	return obj.innerText;
	}
}
			
/**
* 兼容性封装设置元素内容
* @param {元素对象} obj
* @param {文本内容} txt
*/
function setTextContent(obj,txt){
	if(obj.textContent){
        //alert('高级浏览器');
        obj.textContent = txt;
	}else{
		//alert('低级浏览器');
		obj.innerText = txt;
	}
}

6.3 兼容性封装元素的事件监听

元素对象.addEventListener('不带on的事件类型',事件处理函数,布尔值);

  • 布尔值:false事件冒泡,true事件捕获
  • 支持给同一个元素绑定多个相同的事件类型,只支持高级浏览器

元素对象.atachEvent('带on的事件类型',事件处理函数);

  • 只支持ie10及以下浏览器,ie低版本在给同一个元素绑定多个相同的事件类型时,是倒叙执行。
/**
* 兼容性封装给元素添加事件监听
* @param {元素} obj
* @param {事件类型} type
* @param {事件处理函数} fn
 * @param {布尔值:false事件冒泡,true事件捕获} bool
 */
function addEvent(obj, type, fn, bool) {
	if (obj.addEventListener) {
          //高级
          obj.addEventListener(type, fn, bool)
     } else {
		 //低级
          obj.attachEvent('on' + type, fn)
	}
}


/**
* 兼容性封装给元素解除事件监听
* @param {Object} obj
* @param {Object} type
* @param {Object} fn
 @param {Object} bool
*/
function removeEvent(obj,type,fn,bool){
	if(obj.removeEventListener){
		obj.removeEventListener(type,fn,bool);
	}else{
		obj.detachEvent('on'+type,fn);
	}
}

7、事件

7.1事件的三个阶段

1.事件捕获阶段: 由外向内,事件捕获是由网景公司给浏览器制定的规则 2.事件目标阶段: 被触发的那个元素 3.事件冒泡阶段: 由内向外,事件冒泡是由微软公司给ie浏览制定的规则

 先是事件捕获到事件目标,再从事件目标进行事件冒泡 
  • 元素.on事件的方式只能是默认的事件冒泡过程

  • 元素.addEventListener('click',事件处理函数,true);

    第三个参数:是false时,是事件冒泡阶段,ture是事件捕获阶段

7.2 注册事件(2种方式)

7.3 事件监听

addEventListener()事件监听(IE9以后支持)

eventTarget.addEventListener()方法将指定的监听器注册到 eventTarget(目标对象)上,当该对象触发指定的事件时,就会执行事件处理函数。

attacheEvent()事件监听(IE678支持)

​ eventTarget.attachEvent()方法将指定的监听器注册到 eventTarget(目标对象) 上,当该对象触发指定的事件时,指定的回调函数就会被执行。

<button>传统注册事件</button>
<button>方法监听注册事件</button>
<button>ie9 attachEvent</button>
<script>
    var btns = document.querySelectorAll('button');
    // 1. 传统方式注册事件
    btns[0].onclick = function() {
        alert('hi');
    }
    btns[0].onclick = function() {
            alert('hao a u');
        }
   // 2. 事件侦听注册事件 addEventListener 
   // (1) 里面的事件类型是字符串 必定加引号 而且不带on
   // (2) 同一个元素 同一个事件可以添加多个侦听器(事件处理程序)
    btns[1].addEventListener('click', function() {
        alert(22);
    })
    btns[1].addEventListener('click', function() {
            alert(33);
    })
    // 3. attachEvent ie9以前的版本支持
    btns[2].attachEvent('onclick', function() {
        alert(11);
    })
</script>

事件监听兼容性解决方案

封装一个函数,函数中判断浏览器的类型:

7.4 删除事件(解绑事件)

    <div>1</div>
    <div>2</div>
    <div>3</div>
    <script>
        var divs = document.querySelectorAll('div');
        divs[0].onclick = function() {
            alert(11);
            // 1. 传统方式删除事件
            divs[0].onclick = null;
        }
        // 2. removeEventListener 删除事件
        divs[1].addEventListener('click', fn) // 里面的fn 不需要调用加小括号
        function fn() {
            alert(22);
            divs[1].removeEventListener('click', fn);
        }
        // 3. detachEvent
        divs[2].attachEvent('onclick', fn1);

        function fn1() {
            alert(33);
            divs[2].detachEvent('onclick', fn1);
        }
    </script>

**删除事件兼容性解决方案 **

7.5 DOM事件流

html中的标签都是相互嵌套的,我们可以将元素想象成一个盒子装一个盒子,document是最外面的大盒子。
当你单击一个div时,同时你也单击了div的父元素,甚至整个页面。

那么是先执行父元素的单击事件,还是先执行div的单击事件 ???

比如:我们给页面中的一个div注册了单击事件,当你单击了div时,也就单击了body,单击了html,单击了document。


IE 提出从目标元素开始,然后一层一层向外接收事件并响应,也就是冒泡型事件流。
Netscape(网景公司)提出从最外层开始,然后一层一层向内接收事件并响应,也就是捕获型事件流。

最终,w3c 采用折中的方式,平息了战火,制定了统一的标准 —--— 先捕获再冒泡。
现代浏览器都遵循了此标准,所以当事件发生时,会经历3个阶段。

DOM 事件流会经历3个阶段:

  1. 捕获阶段

  2. 当前目标阶段

  3. 冒泡阶段

​ 我们向水里面扔一块石头,首先它会有一个下降的过程,这个过程就可以理解为从最顶层向事件发生的最具体元素(目标点)的捕获过程;之后会产生泡泡,会在最低点( 最具体元素)之后漂浮到水面上,这个过程相当于事件冒泡。

事件冒泡

    <div class="father">
        <div class="son">son盒子</div>
    </div>
    <script>
        // onclick 和 attachEvent(ie) 在冒泡阶段触发
        // 冒泡阶段 如果addEventListener 第三个参数是 false 或者 省略 
        // son -> father ->body -> html -> document
        var son = document.querySelector('.son');
		// 给son注册单击事件
        son.addEventListener('click', function() {
            alert('son');
        }, false);
		// 给father注册单击事件
        var father = document.querySelector('.father');
        father.addEventListener('click', function() {
            alert('father');
        }, false);
		// 给document注册单击事件,省略第3个参数
        document.addEventListener('click', function() {
            alert('document');
        })
    </script>

事件捕获

    <div class="father">
        <div class="son">son盒子</div>
    </div>
    <script>
        // 如果addEventListener() 第三个参数是 true 那么在捕获阶段触发
        // document -> html -> body -> father -> son
         var son = document.querySelector('.son');
		// 给son注册单击事件,第3个参数为true
         son.addEventListener('click', function() {
             alert('son');
         }, true);
         var father = document.querySelector('.father');
		// 给father注册单击事件,第3个参数为true
         father.addEventListener('click', function() {
             alert('father');
         }, true);
		// 给document注册单击事件,第3个参数为true
        document.addEventListener('click', function() {
            alert('document');
        }, true)
    </script>

7.6 事件对象

什么是事件对象

事件发生后,跟事件相关的一系列信息数据的集合都放到这个对象里面,这个对象就是事件对象。

比如:

  1. 谁绑定了这个事件。

  2. 鼠标触发事件的话,会得到鼠标的相关信息,如鼠标位置。

  3. 键盘触发事件的话,会得到键盘的相关信息,如按了哪个键。

事件对象的使用

事件触发发生时就会产生事件对象,并且系统会以实参的形式传给事件处理函数。

所以,在事件处理函数中声明1个形参用来接收事件对象。

事件对象的兼容性处理

事件对象本身的获取存在兼容问题:

  1. 标准浏览器中是浏览器给方法传递的参数,只需要定义形参 e 就可以获取到。

  2. 在 IE6~8 中,浏览器不会给方法传递参数,如果需要的话,需要到 window.event 中获取查找。

只要“||”前面为false, 不管“||”后面是true 还是 false,都返回 “||” 后面的值。
只要“||”前面为true, 不管“||”后面是true 还是 false,都返回 “||” 前面的值。
    <div>123</div>
    <script>
        var div = document.querySelector('div');
        div.onclick = function(e) {
                // 事件对象
                e = e || window.event;
                console.log(e);
        }
    </script>

事件对象的属性和方法

e.target 和 this 的区别

  • this 是事件绑定的元素(绑定这个事件处理函数的元素) 。

  • e.target 是事件触发的元素。

常情况下terget 和 this是一致的,
但有一种情况不同,那就是在事件冒泡时(父子元素有相同事件,单击子元素,父元素的事件处理函数也会被触发执行),
	这时候this指向的是父元素,因为它是绑定事件的元素对象,
	而target指向的是子元素,因为他是触发事件的那个具体元素对象。
    <div>123</div>
    <script>
        var div = document.querySelector('div');
        div.addEventListener('click', function(e) {
            // e.target 和 this指向的都是div
            console.log(e.target);
            console.log(this);

        });
    </script>

事件冒泡下的e.target和this

    <ul>
        <li>abc</li>
        <li>abc</li>
        <li>abc</li>
    </ul>
    <script>
        var ul = document.querySelector('ul');
        ul.addEventListener('click', function(e) {
              // 我们给ul 绑定了事件  那么this 就指向ul  
              console.log(this); // ul

              // e.target 触发了事件的对象 我们点击的是li e.target 指向的就是li
              console.log(e.target); // li
        });
    </script>

7.7 阻止默认行为

html中一些标签有默认行为,例如a标签被单击后,默认会进行页面跳转。

    <a href="http://www.baidu.com">百度</a>
    <script>
        // 2. 阻止默认行为 让链接不跳转 
        var a = document.querySelector('a');
        a.addEventListener('click', function(e) {
             e.preventDefault(); //  dom 标准写法
        });
        // 3. 传统的注册方式
        a.onclick = function(e) {
            // 普通浏览器 e.preventDefault();  方法
            e.preventDefault();
            // 低版本浏览器 ie678  returnValue  属性
            e.returnValue = false;
            // 我们可以利用return false 也能阻止默认行为 没有兼容性问题
            return false;
        }
    </script>

7.8 阻止事件冒泡

事件冒泡本身的特性,会带来的坏处,也会带来的好处。

    <div class="father">
        <div class="son">son儿子</div>
    </div>
    <script>
        var son = document.querySelector('.son');
		// 给son注册单击事件
        son.addEventListener('click', function(e) {
            alert('son');
            e.stopPropagation(); // stop 停止  Propagation 传播
            window.event.cancelBubble = true; // 非标准 cancel 取消 bubble 泡泡
        }, false);

        var father = document.querySelector('.father');
		// 给father注册单击事件
        father.addEventListener('click', function() {
            alert('father');
        }, false);
		// 给document注册单击事件
        document.addEventListener('click', function() {
            alert('document');
        })
    </script>

阻止事件冒泡的兼容性处理

7.9 事件委托

事件委托:把事情委托给别人,代为处理。
事件委托也称为事件代理,在 jQuery 里面称为事件委派。

说白了就是,不给子元素注册事件,给父元素注册事件,把处理代码在父元素的事件中执行。

js事件中的代理:

事件委托的原理

​ 给父元素注册事件,利用事件冒泡,当子元素的事件触发,会冒泡到父元素,然后去控制相应的子元素。

事件委托的作用

  • 我们只操作了一次 DOM ,提高了程序的性能。

  • 动态新创建的子元素,也拥有事件。

    <ul>
        <li>知否知否,点我应有弹框在手!</li>
        <li>知否知否,点我应有弹框在手!</li>
        <li>知否知否,点我应有弹框在手!</li>
        <li>知否知否,点我应有弹框在手!</li>
        <li>知否知否,点我应有弹框在手!</li>
    </ul>
    <script>
        // 事件委托的核心原理:给父节点添加侦听器, 利用事件冒泡影响每一个子节点
        var ul = document.querySelector('ul');
        ul.addEventListener('click', function(e) {
            // e.target 这个可以得到我们点击的对象
            e.target.style.backgroundColor = 'pink';
        })
    </script>

8、Dom操作

关于dom操作,我们主要针对于元素的操作。主要有创建、增、删、改、查、属性操作、事件操作。

8.1. 创建

8.2. 增加

8.3. 删

8.4. 改

8.5. 查

8.6. 属性操作

===================待更新============================