前端总结
css 盒模型
说明:
margin(外边距):清除边框外的区域,外边距是透明的
border(边框):围绕在内边距和内容外的边框
padding(内边距):清除内容周围的区域,内边距是透明的
content(内容):盒子的内容,显示文本和图像
-
总元素的宽度 = 宽度 + 左填充 + 右填充 + 左边框 + 右边框 + 左边距 + 右边距
-
标准盒模型下盒子的大小 = content + border + padding + margin
-
怪异盒模型下盒子的大小 = width(content + border + padding )+ margin
-
标准盒模型:box-sizing : conent-box,IE盒模型(怪异盒模型):box-sizing : border-box
BFC
BFC(块级格式化上下文),是Web页面中盒模型布局的css的渲染模式,指一个独立的渲染区域或者说是一个隔离的独立容器。
BFC形成的条件:
- 浮动元素,float除none之外的值;
- 定位元素,position(absolute,fixed);
- display为一下其中之一的值inline-block,table-cell,table-caption(表格标题);
- overflow除了visible以外的值(hidden,auto,scroll)
BFC的特性: - 内部的Box会在垂直方向上一个接一个的放置;
- 垂直方向上的距离有margin决定;(解决外边距重叠问题)
- bfc的区域不会与float的元素区域重叠;(防止浮动文字环绕)
- 计算bfc的高度时,浮动元素也参与计算;(清除浮动)
- bfc就是页面上的一个独立容器,容器里面的子元素不会影响外边元素
DOM事件
DOM 0级:写法:el.onclick.function(){}
当希望为同一个元素/标签绑定多个同类型事件的时候(如给上面的btn元素绑定3个点击事件),是不被允许的。DOM 0事件绑定,给元素的事件行为绑定方法,这些方法都是在当前元素事件行为的冒泡阶段(或者目标阶段)执行的。
由于DOM 1级中没有事件的相关内容,所以没有DOM 1级事件
DOM 2级 写法:el.addEventListener(event-name,callback,useCapture)
1.event-name:事件名称,可以是标准的DOM事件
2.callback:回调函数,当事件触发时,函数会被注入一个参数为当前的事件对象event
3.useCapture:默认是false,代表事件句柄在冒泡阶段执行
DOM 3级 写法和DOM 2级 一致,只是在DOM 2级事件基础上添加了更多的时间类型
-
UI事件:当用户与页面上的元素交互时触发,如:laod、scroll
-
焦点事件:当元素获得或失去焦点时触发,如:blur、focus
-
鼠标事件:当用户通过鼠标在页面执行操作时触发如:dblclick、mouseup
-
滚轮事件:当使用鼠标滚轮或类似设备时触发,如:mousewheel
-
文本事件:当在文档中输入文本是触发,如:textInput
-
键盘事件:当用户通过键盘在页面上执行操作时触发,如:keydown、keypress
-
合成事件:当为IME(输入发编辑器)输入字符时触发,如:compositionstart
-
变动事件:当底层DOM结构发生变化时触发,如:DOMsubtreeModified
-
同时DOM 3级事件也被允许使用者自定义一些事件
- DOM事件模型 事件流
流的概念现在在JavaScript中比较多,比如vue中单向数据流,node中的管道流。用术语说流是对输入输出设备的抽象。以程序的角度来说,流是具有方向的数据。
事件模型分为:捕获和冒泡
事件流:
(1)捕获阶段:事件从window对象自上而下向目标节点传播的阶段;
(2)目标阶段:真正的目标节点正在处理事件的阶段;
(3)冒泡阶段:事件从目标节点自下而上向window对象传播的阶段;
由于事件会在冒泡阶段向上传播到父节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件。这种方法叫做事件的代理(delegation)。
通俗的讲就是利用冒泡的原理,把事件加到父级上,触发执行效果。
优点:
提高性能
- 阻止默认行为:event.preventDefault()
a链接跳转、右键呼出菜单事件、提交(submit)事件 - 阻止冒泡:event.stopPropagation()方法阻止事件冒泡到父元素,阻止任何父事件处理程序被执行。stoplmmediatePropagation既能阻止事件向父元素冒泡也能阻止同事件类型的其它监听器被触发
- event.target&event.currentTarget
<div id="a">
aaaa
<div id="b">
bbbb
<div id="c">
cccc
</div>
<div id="d">
dddd
</div>
</div>
</div>
//=======================js
document.getElementById("a").addEventListener("click", function (e) {
console.log(
"target:" + e.target.id + "¤tTarget:" + e.currentTarget.id
);
});
document.getElementById("b").addEventListener("click", function (e) {
console.log(
"target:" + e.target.id + "¤tTarget:" + e.currentTarget.id
);
});
document.getElementById("c").addEventListener("click", function (e) {
console.log(
"target:" + e.target.id + "¤tTarget:" + e.currentTarget.id
);
});
document.getElementById("d").addEventListener("click", function (e) {
console.log(
"target:" + e.target.id + "¤tTarget:" + e.currentTarget.id
);
});
当我们点击里层的元素时,会依次输出
由以上例子可知:event.currentTarget始终是监听事件者,而event.target是事件的真正发出者
//创建事件, Event是无法传递参数的
var event = new Event('build');
//创建事件, CustomEvent是可以传递参数的
var event = new CustomEvent('build', { detail: elem.dataset.time })
//监听事件Listen for the event.
elem.addEventListener('build', function (e) { ... }, false);
//分发/触发事件Dispatch the event.
elem.dispatchEvent(event);
有兴趣的童鞋可以看看这篇文章:https://blog.youkuaiyun.com/qq_27053493/article/details/100682150
JS 相关
//1.对象字面量
let a = {name:'xxx'};
//2.构造函数
function Person(name) {
this.name = name
}
let b = new Person('xxx')
//3. Object.create(proto,[propertiesObject])
//Object.create()方法创建对象时,属性是在原型链下面的
let c = Object.create({name:'XXX'})
运行结果:
// instanceof在继承关系中的用法============================
//instanceof可以在继承关系中用来判断一个实例是否属于它的父类型
function Foo(){}
function Aoo(){}
Foo.prototype = new Aoo() //原型继承
var foo = new Foo()
console.log(foo instanceof Foo) //true
console.log(foo instanceof Aoo) //true
// new运算符原理============================
function _new(func){
//第一步 创建新对象
let obj = {}
//第二步 空对象的_proto_指向了构造函数的prototype成员对象
obj.__porto__ = func.prototype;
// 一二步合并就相当于 let obj = Object.create(func.prototype)
//第三步 使用apply调用构造器函数,属性和方法被添加到this引用的对象中
let result = func.apply(obj)
if(result && (typeof(result) == "object" || typeof(result) == "function")){
//如果构造函数执行的结果返回的是一个对象,那么返回这个对象
return result
}
//如果构造函数返回的不是一个对象,返回创建的新对象
return obj
}
//定义一个父类
function Father(name){
//属性
this.name = name || "father";
//实例方法
this.sayName = function(){
console.log (this.name)
}
this.color = ["red","blue"];
}
//原型方法
Father.prototype.age = 18;
Father.prototype.sayAge = function(){
console.log(this.age)
}
- 原型链继承:将父类的实例作为子类的原型
function Son(name) {
this.name = name || "son";
}
Son.prototype = new Father();
let s1 = new Son("s1");
let s2 = new Son("s2");
s1.color.push("black");
console.log("将父类的实例作为子类的原型:",s1.name); //s1
console.log("将父类的实例作为子类的原型:",s1.color); //['red','blue','black']
console.log("将父类的实例作为子类的原型:",s1.age); //18
s1.sayAge(); //18
console.log("将父类的实例作为子类的原型:",s2.name); //s2
console.log("将父类的实例作为子类的原型:",s2.color); //['red','blue','black']
优点:
- 简单,易于实现
- 父类新增原型方法、原型属性,子类都能访问到
缺点:
- 无法实现多继承,因为原型一次只能被一个实例更改
- 来自原型对象的所有属性被所有实例实例共享(上述例子中的color属性)
3.创建子类实例时,无法向父构造函数传参
2.构造继承:复制父类的实例属性给子类
//构造继承:复制父类的实例属性给子类==================================================
function Son(name) {
Father.call(this, "我是传给父类的参数");
this.name = name || "son";
}
let s = new Son("son");
console.log(s.name); // son
//s.sayAge(); // 抛出错误(无法继承父类原型方法)
s.sayName(); // son
console.log(s.age); // undefined (无法继承父类原型属性)
console.log(s instanceof Father); // false
console.log(s instanceof Son); // true
优点:
- 解决了原型链继承中子类实例共享父类引用属性的问题
- 创建子类实例中,可以向父类传递参数
- 可以实现多继承(call 多个父类对象)
缺点:实例并不是父类的实例,只是子类的实例
- 只能继承父类实例的属性和方法,不能继承其原型上的属性和方法
- 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
- 组合继承:将原型链和借用构造函数的技术组合到一起。使用原型链实现对原型属性和方法的继承,而通过构造函数来实现对实例属性的继承
function Son(name){
//第一次调用父类构造器 子类实例增加父类实例
Father.call(this, "我是传给父类的参数");
this.name = name || "son";
}
//经过new运算符 第二次调用父类构造器 子类原型也增加了父类实例
Son.prototype = new Father();
let s = new Son("son");
console.log(s.name);//son
s.sayAge(); // 18
s.sayName(); // son
console.log(s.age); // 18
console.log(s instanceof Father); // true
console.log(s instanceof Son); // true
console.log(s.constructor === Father); // true
console.log(s.constructor === Son); // false
优点:
- 弥补了构造函数的缺点,现在既可以继承实例的属性和方法,也可以继承原型的属性和方法
- 既是子类的实例,也是父类的实例
- 可以向父类传递参数
- 函数可以复用
缺点:
- 调用了两次父类构造函数,生成了两份实例
- constructor指向问题
- 实例继承:为父类实例添加新特征,作为子类实例返回
//实例继承:为父类实例添加新特征,作为子类实例返回
function Son(name){
let f = new Father('传给父类的参数')
f.name = name || 'son'
return f
}
let s = new Son("son"); //或者直接调用子类构造函数 let s = Son("son"
console.log(s.name);//son
s.sayAge(); //18
s.sayName();//son
console.log(s.age)
console.log(s instanceof Father) // true
console.log(s instanceof Son) //false
console.log(s.constructor === Father) //true
console.log(s.constructor === Son) // false
优点:
- 支持多继承
缺点:
- 效率低,性能差,占用内存高(因为需要拷贝父类属性)
- 无法获取父类不可枚举的方法(不可枚举的方法,不能使用for-in访问到)
// 5.拷贝继承:对父类实例中的方法与属性拷贝给子类的原型
function Son(name){
let f = new Father("传给父类的参数");
for(let k in f){
Son.prototype[k] = f[k];
}
Son.prototype.name = name;
}
let s = new Son("son")
console.log(s.name)
s.sayAge(); //18
s.sayName(); //son
console.log(s instanceof Father); //false
console.log(s instanceof Son); //true
console.log(s.constructor === Father); //false
console.log(s.constructor === Son); //true
- 寄生组合继承:通过寄生方式,砍掉父类的实例属性,避免了组合继承生成两份实例的缺点
function Son(name){
Father.call(this);
this.name = name || "son"
}
//方法一 自己动手创建一个中间类
(function () {
let NoneFun = function(){};
NoneFun.prototype = Father.prototype;
Son.prototype = new NoneFun();
Son.prototype.constructor = Son;
})()
// 方法二 直接借用Object.create()方法
Son.prototype = Object.create(Father.prototype);
//修复构造函数指向
Son.prototype.constructor = Son;
let s = new Son("son");
s.sayAge(); //18
s.sayName(); //son
console.log(s.age); //18
console.log(s instanceof Father) //treu
console.log(s instanceof Son) // true
console.log(s.constructor === Father); //false
console.log(s.constructor === Son) // true
优点:
- 比较完美(js实现继承首选方式)
缺点:
- 实现起来较为复杂(可通过Object.create简化)
- es6-- Class 继承:使用extends表明继承自哪个父类,并且在子类构造函数中必须调用super
class Son extends Father{
constructor(name) {
super (name);
this.name = name || "son";
}
}
let s = new Son("son");
console.log(s.name); //son
s.sayAge(); //18
s.sayName(); //son
console.log(s.age); //18
console.log(s instanceof Father); //true
console.log(s instanceof Son); //true
console.log(s.constructor === Father); //false
console.log(s.constructor === Son); //true
JS防抖与节流(性能优化)
防抖:动作停止后的时间超过设定的时间时执行一次函数。注意:这里的动作停止表示你停止了触发这个函数,从这个时间点开始计算,当间隔时间等于你设定时间,才会执行里面的回调函数。如果你一直在触发这个函数并且两次触发间隔小于设定时间,则函数一直不会执行。
function debance(fn,delay){
let timer = null;
return () => {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
fn()
},delay)
}
}
window.addEventListener(
"scroll",
debance(() => {
console.log(1111)
},1000)
)
防抖应用场景:
- search搜索联想,用户在不断输入值时,用防抖来节约请求资源
- window 触发resize的时候,不断的调整浏览器窗口大小会不断的触发这个事件,用防抖来让其只触发一次
节流:一定时间内执行的操作只执行一次,也就是说即预先设定一个执行周期,当调用动作的时刻大于等于执行周期则执行该动作,然后进入下一个新周期。