事件冒泡和捕获
当事件发生后,这个事件就要开始传播(从里到外或者从外向里)。
为什么要传播呢?因为事件源本身(可能)并没有处理事件的能力,即处理事件的函数(方法)并未绑 定在该事件源上。
例如我们点击一个按钮时,就会产生一个click事件,但这个按钮本身可能不能处理这个事件,事件必须 从这个按钮传播出去,从而到达能够处理这个事件的代码中(例如我们给按钮的onclick属性赋一个函数 的名字,就是让这个函数去处理该按钮的click事件)
document.getElementById("d1").onclick = function(e){ console.log("d1"); }
事件委托
事件委托,通俗地来讲,就是把一个元素响应事件(click、keydown......)的函数委托到另一个元素;
一般来讲,会把一个或者一组元素的事件委托到它的父层或者更外层元素上,真正绑定事件的是外层元 素,当事件响应到需要绑定的元素上时,会通过事件冒泡机制从而触发它的外层元素的绑定事件上,然 后在外层元素上去执行函数。
举个例子,比如一个宿舍的同学同时快递到了,一种方法就是他们都傻傻地一个个去领取,还有一种方法就是把这件事情委托给宿舍长,让一个人出去拿好所有快递,然后再根据收件人一一分发给每个宿舍同学;
在这里,取快递就是一个事件,每个同学指的是需要响应事件的 DOM 元素,而出去统一领取快递的宿 舍长就是代理的元素,所以真正绑定事件的是这个元素,按照收件人分发快递的过程就是在事件执行 中,需要判断当前响应的事件应该匹配到被代理元素中的哪一个或者哪几个。
举例说明:
1、傻傻地一个个去领取
var list = document.getElementById("list");
var li = list.getElementsByTagName("li");
for(var i=0; i<li.length; i++) {
li[i].onclick = function(){
this.style.color = 'red';
}
}
2、委托 将事件绑定到父节点,点击子节点通过事件冒泡到父并处理
var list = document.getElementById("list");
list.onclick = function(e){
if (e.target.nodeName === 'LI') {
e.target.style.color = 'red';
}
}
函数式编程与面向对象编程的异同
函数式编程写法
window.onload = function(){
var testA = document.querySelector(".testA");
testA.onmousemove = function(){
//移入处理
};
testA.onmouseout= function(){
//移出处理
};
}
面向对象编程写法
var web = {
bind:function(){
this.testA = document.querySelector(".testA");
this.testA.onmousemove = function(){
//移入处理
};
this.testA.onmouseout= function(){
//移出处理
};
}
}
window.onload = function(){
web.bind();
}
原型
定义
用原型实例指向创建对象的类,使用于创建新的对象的类的共享原型的属性与方法。
原型是一个对象,其它对象可以通过它实现属性继承
JS对象分两种
普通对象object和函数对象function
prototype是函数才有的属性
__proto__是每个对象都有的属性
普通对象和函数对象区别
凡是通过new Function创建的对象都是函数对象,其他都是普通对象(通常通过Object创建),可以通 过typeof来判断。
function f1(){};
typeof f1 //"function"
var o1 = new f1(); //函数实例
typeof o1 //"object"
var o2 = {};
typeof o2 //"object"
1、每一个函数对象都有一个prototype属性,但是普通对象是没有的; prototype下面又有个constructor,指向这个函数。
2、每个对象都有一个名为__proto__的内部属性,指向它所对应的构造函数的原型对象,原型链基于 __proto__;
原型的写法
function Person(){}; //定义一个函数对象
Person.prototype.name="fangfang"; //原型对象中添加属性
Person.prototype.phone = "18040505058";
var p1 = new Person(); //实例化
var p2 = new Person();
原型分解图
为什么使用function中的prototype? 为什么要继承
需求:生成多个实例
var cat1 = {};//创建一个空对象
cat1.name="大明";
cat1.color ="黄色";
var cat2 = {};//创建一个空对象
cat2.name="小明";
cat2.color ="白色";
//缺点,如果有几十个实例,写起来麻烦,且实例与原型没有关联
封装一个函数
function cat(name,color){
return {
name:name,
color:color
}
};
var cat1 = cat("大明","黄色");
var cat2 = cat("小明","白色");
构造函数
function Cat(name,color){ //构造函数
this.name = name;
this.color = color;
};
var cat1 = new Cat("大明","黄色");
var cat2 = new Cat("小明","白色");
如果当前cat函数中添加了eat()方法和type属性
function Cat(name,color){ this.name = name; this.color = color; this.type='动物'; this.eat = function(){console.log("吃老鼠")}; };
存在一个浪费内存的问题,那就是对于每一个实例对象,type属性和eat()方法都是一模一样的内容,既不环保,也缺乏效率
解决方法:
function Cat(name,color){ //构造函数
this.name = name;
this.color = color;
//this.type='动物';
//this.eat = function(){console.log("吃老鼠")};
};
Cat.prototype.type='动物';
Cat.prototype.eat = function(){console.log("吃老鼠")};
验证
console.log('name' in p1); //in 不管自身的还是原型 都返回true
console.log('type' in p1);
console.log(p1.hasOwnProperty('name')); //hasOwnProperty() 自身返回true 原型 返回
false
console.log(p1.hasOwnProperty('type'));
理解构造函数和原型对象的区别
function Person(){}; Person.prototype.name="fangfang"; Person.prototype.age = 18; var p1 = new Person(); var p2 = new Person();
构造函数是生成对象的模板,一个构造函数可以生成多个对象,每个对象都有相同的结构。 Person是构造函数,而Person.prototype是构造函数的原型对象。
构造函数自身的属性和方法无法被共享,而原型对象的属性和方法可以被所有实例对象所共享。
原型链
function F1(){
this.name1 = 'f1'
};
F1.prototype.name = 'object';
function F2(){
this.name2= 'f2'
};
function F3(){
this.name3 = 'f3'
};
F2.prototype = new F1(); //f2的原型是f1
F3.prototype = new F2(); //f3的原型是f2
var f = new F3(); //实例化的处理
f.name1;
f.__proto__.__proto__.__proto__.name= '12414';
//修改
//f.__proto__.__proto__.__proto__.name= '12414';
//删除
delete f.__proto__.__proto__.__proto__.name;
原型链深入理解
原型链的理解
function Animal(){
this.type = "动物"
};
Animal.prototype.eat = function(){console.log('吃')};
function Cat(name,color){
this.name = name;
this.color = color;
};
Cat.prototype = new Animal();
var c1 = new Cat('tom','白色');
var c2 = new Cat('ben','黑色');
看图说明
继承
继承的几种常见方式
1.原型继承
function Animal(){
this.type = "动物"
};
function Cat(name,color){
this.name = name;
this.color = color;
};
Cat.prototype = new Animal();
var c1 = new Cat('x','白色');
var c2 = new Cat('t','花色');
c1.type
// 优点:同一个原型对象
// 缺点:不能修改原型对象,会影响所有实例
function Animal(){ //动物对象
this.type = '动物'
};
function Cat(name,color){ //猫对象
this.name = name;
this.color = color;
this.type='我是猫';
};
Cat.prototype = new Animal(); //猫的原型对象指向了动物函数
var cat1 = new Cat("大明","黄色");
var cat2 = new Cat("大明","黄色");
console.log(cat1.type); //获取的是当前构造器中的属性
console.log(cat2.type); //获取的是当前构造器中的属性
//想获取Animal成员值
console.log(cat1.__proto__.type);
console.log(cat2.__proto__.type);
//当我们访问一个原型对象的属性时,__proto__是一级级来获取,当继承关系很复杂,未知继承时
构造函数的继承
function Animal(){
this.type = "动物"
};
function Cat(name,color){
Animal.apply(this); //将Animal对象的成员放到Cat对象上
this.name = name;
this.color = color;
};
var cat1 = new Cat("大明","黄色");
var cat2 = new Cat("小明","白色");
cat1.type = '我是黄猫';
cat2.__proto__.type = '我是动物';
console.log(cat1.type); //'我是黄猫' cat1被修改
console.log(cat2.type); //"动物"
// 优点:不存在修改原型对象影响所有实例,各自拥有独立属性
// 缺点:父类的成员会被创建多次,存在冗余且不是同一个原型对象
// 通过apply/call只能拷贝成员,原型对象不会拷贝
组合继承
function Animal(){
this.type = '动物'
};
Animal.prototype.eat = function(){console.log('吃')};
function Cat(name,color){
this.name = name;
this.color = color;
Animal.call(this);
};
Cat.prototype = new Animal();
var cat1 = new Cat("大明","黄色");
var cat2 = new Cat("小明","白色");
cat1.type = '我是黄猫'; //修改当前构造器中的属性
cat2.__proto__.type = '我是动物';//修改了原型对象的值,但并不影响cat1,cat2的值
console.log(cat1.type); //'我是黄猫' //原型对象的值变化,并不影响构造函数值
console.log(cat2.type); //'动物'
console.log(cat2.__proto__.type); //'我是动物
cat1.eat(); //还可以调用原型对象中eat()方法