事件
事件:指的是文档或者浏览器窗口中发生的一些特定交互瞬间。我们可以通过侦听器(或者处理程序)来预定事件,以便事件发生的时候执行相应的代码。
事件流
- 事件流:描述的是在页面中接受事件的顺序
- 事件冒泡:由最具体的元素接收,然后逐级向上传播至最不具体的元素的节点(文档)
- 事件捕获:最不具体的节点先接收事件,而最具体的节点应该最后接收事件
- 先捕获后冒泡假如我们点击一个div, 实际上是先点击document,然后点击事件传递到div,而且并不会在这个div就停下,div有子元素就还会向下传递,最后又会冒泡传递回document
- 兼容触发DOM上的某个事件时,会产生一个事件对象event 只有在事件处理程序执行期间,event对象才会存在,一旦事件处理程序执行完毕,event对象就会被销毁
事件处理
- HTML事件处理:直接添加到HTML结构中
- DOM0级事件处理:把一个函数赋值给一个事件处理程序属性,以这种方式添加的事件处理程序会在事件流的冒泡阶段被处理
//DOM0的事件,对于同一个dom节点而言,只能注册一个,后边注册的 同种事件 会覆盖之前注册的
var btn5 = document.getElementById('btn5');
btn5.onclick=function(){
console.log(this.id);//btn5
};
- DOM2级事件处理:
addEventListener("事件名","事件处理函数",布尔值)
false时表示在事件冒泡阶段调用事件处理程序,一般建议在冒泡阶段使用,特殊情况才在捕获阶段;
true:事件捕获
false:事件冒泡
removeEventListener();
eval
// eval 方法可以执行执行过的js脚本代码
<script id="a">
console.log("aaa")
</script>
<script>
eval(document.getElementById("a").innerText)
</script>
原型链
// instanceof检测左侧的__proto__原型链上,是否存在右侧的prototype原型
1. 一个没有原型的构造函数的construct指向自己
commonjs es6 模块的区别
- 输出是值的拷贝,即原来模块中的值改变不会影响已经加载的该值 2 静态分析,动态引用,输出的是值的引用,值改变,引用也改变
- this 指向当前模块 2 this 指向undefined
es6 note
// 1. let 没有预编译,不存在变量提升
// 2. 不可以重复定义变量
// 3 箭头函数没有 arguments
// 4 箭头函数不能做构造函数
let file_name = "huahua.png"
// 包含字符串
console.log(file_name.includes("hua"))
// string 是以谁开头
console.log(file_name.startsWith("hua"))
// 结尾
console.log(file_name.endsWith("png"))
function test(){
// arguments 是类数组对象 不是数组
let arg = arguments;
// let res = Array.prototype.slice.apply(arg)
// 1. 把类数组变成数组 2. 把字符串变成数组
let res = Array.from(arg)
}
Proxy
var person = {
name: "tom",
age: 23
};
var handler = {
get: function(target, key, receiver){
console.log(receiver)
console.log(this) // handler
return target["name"];
},
set: function(target, key, value , receiver){
target[key] = value
return true;
},
/**
* @name in 操作的钩子
*/
has: function(target, prop){
console.log("--proxy-has")
console.log(target)
console.log(prop)
return true;
},
// 当读取代理对象的原型时
getPrototypeOf(target) {
console.log(target)
},
/**
* @name 用于拦截对对象的 Object.defineProperty() 操作
* @param target
* @param property
* @param descriptor
* @returns {boolean}
* @eg
* Object.defineProperty(proxy, "sex", {
* value:"male",
* writable:true, // 可以被覆盖
* enumerable: true //可以被遍历
* })
*/
defineProperty(target, property, descriptor){
console.log(target, property, descriptor)
// 必须返回一个布尔值,否则报错
return true;
},
/**
* @name 拦截对象的delete属性操作
* @param target
* @param property
* @returns {boolean}
* @eg delete proxy.name;
*/
deleteProperty(target, property){
console.log(property)
return true;
}
}
var proxy = new Proxy(person,handler);
console.log(name in proxy)
bind 深入理解
/*
存在this指向的情况
1 函数嵌套函数
2 闭包
3 构造函数
bind只是返回一个函数,不执行
Note
1. 事件处理,回调函数最好单独处理,便于维护
2. 构造函数不执行就就是普通的函数,否则就生成指向实例的this
3. 构造函数bind, 会对象实例化的this,就不是bind的this
*/
function Dog(){
console.log(this)
console.log(this.food)
}
let context = {
name: 'context1', food: "banana"
}
let Dog1 = Dog.bind(context);
let dog1 = new Dog1()
console.log(Function.prototype.bind().name)// bound
Function.prototype.binding = function () {
const _this = this;
const [context, ...args] = Array.from(arguments)
// 圣杯模式解决prototyoe问题
const tempFn = function () {}
const bound = function (args1) {
// if no args the args1 is undefined will throw error
args1 = args1 || [];
args1 = Array.from(args1);
// 这里的return是模拟函数里的return
return _this.apply(this instanceof _this ? this : context, [...args, ...args1])
}
tempFn.prototype = _this.prototype;
bound.prototype = new tempFn();
return bound;
}
代理模式
var data = { id: 'hua', val: ''}
function bindDomToData(data){
const dom = document.getElementById(data.id);
function handleChange(){
data.val = this.value;
}
dom.addEventListener("input", handleChange)
}
bindDomToData(data)
const handler = {}
handler.set = function (target, key, value, receiver) {
document.getElementById(data.id).value = value;
return Reflect.set(target, key, value, receiver);
}
handler.get = function (target, key, receiver) {
return target[key]
}
var proxy = new Proxy(data,handler);
document.getElementById("click").onclick = function(){
proxy.val = Math.random().toFixed(6)
}
reflect
有这么一个全局对象,上面直接挂载了对象的某些特殊方法,这些方法可以通过Reflect.apply这种形式来使用,当然所有方法都是可以在 Object 的原型链中找到的
函数式编程
javascript是函数式编程和面向对象编程的混编,可扩展性强,
函数式编程属于 生命式编程的范畴,
函数式编程的特点是函数的参数可以是函数,返回值也可以是函数,
高阶函数,如下
var shout = function(out){
var random = (Math.random() * 321).toFixed(3);
return function(message){
out(`#${random} ${message.toUpperCase()}!!!`)
}
}
var out = function(mes){
console.log(mes)
}
var tell = shout(out)
tell("I am a cool boy")
function compute(obj){
return {
add: function(){
return obj.a + obj.b;
},
minus: function(){
return obj.a - obj.b;
},
multiply: function(){
return obj.a * obj.b;
}
}
var data = compute({a : 10 , b : 2})
console.log(data.add())
console.log(data.minus())
console.log(data.multiply())
手写instanceof
function _instanceof(A, B) {
var O = B.prototype;// 取B的显示原型
A = A.__proto__;// 取A的隐式原型
while (true) {
//Object.prototype.__proto__ === null
if (A === null)
return false;
if (O === A)// 这里重点:当 O 严格等于 A 时,返回 true
return true;
A = A.__proto__;
}
}
</pre>
深拷贝
function deepClone(data) {
if(typeof data === "object" && data !== null){
var type = data.constructor;
var result = new type();
for (var key in data) {
if (data.hasOwnProperty(key)) {
result[key] = deepClone(data[key]);
}
}
return result;
}
return data;
}
数组降维
var arr = [1, 2, [3]];
var res = Array.prototype.concat.apply([], arr);
console.log(res);
var arr2 = [1];
console.log(111);
console.log(arr2.concat(11));
// es6
let flatten = arr => arr.reduce((begin,current)=>{
Array.isArray(current)?
begin.push(...flatten(current)):
begin.push(current);
return begin
},[])
tofixed返回string
let aa = 10937843.44;
console.log(typeof aa.toFixed(3));
函数声明和函数表达式
let test = function aa(){} // 这是一个表达式,表达式忽略名字的
let test1 = function(){}
console.log(test)
console.log(test.name) // aa
console.log(test1.name) //test1
console.log(aa)
函数形参和实参
function tmp(a,b){
console.log(tmp.length) // 2 表示函数形参的个数
}
tmp(1)
function sum(a,b,c){
a = 11;
console.log(arguments[0]) // 形参和实参映射关系(两个都存在才映射)
c = 2;
console.log(arguments[2]) // undefined
}
sum(1,2)
js 执行顺序
// - 1 语法分析
// - 2 预编译 发生在函数执行的前一刻
// 函数声明整体提升,变量只是声明提升
// 预编译的过程(主要是读变量声明)
// 1. 创建AO对象(Active Object)
// 2. 查找函数形参及函数内变量声明,形参名及变量名作为AO对象的属性,值为undefined
// 3. 实参形参相统一,实参值赋给形参
// 4. 查找函数声明,函数名作为AO对象的属性,值为函数引用
全局的就是GO 就是window
function test(){
console.log(b)
if(a){
var b = 10; // 不要管if ,预编译看到声明就处理
}
}
// - 3 解释执行
数字
// js里的数字都是64位,0-51是数字,52-63是指数,64位是符号位
// 位运算是用32位进行二进制执行
// toString 可以进制转换
var a = 10;
a.toString(2) // "1010"
正则
默认是贪婪匹配,就是尽可能多的匹配
// 开启懒惰匹配
var reg = /\d+/g;
var reg1 = /\d+?/g;
var str = '11aa22';
console.log(str.match(reg)) // [ '11', '22' ]
console.log(str.match(reg1))// [ '1', '1', '2', '2' ]
继承
- 原型链继承
/**
* @title 原型链继承
*
* @good 简单明了,容易实现
* @good 实例是子类的实例,实际上也是父类的一个实例
* @good 父类新增原型方法/原型属性,子类都能访问到
*
* @bad 所有子类的实例的原型都共享同一个超类实例的属性和方法
* @bad 无法实现多继承
*/
function A() {
this.name = 'A'
}
A.prototype.list = []
const a = new A()
function B() {
this.name = 'B'
}
B.prototype = new A();
const b = new B();
console.log(b)
- 构造继承
/**
* @title 构造继承
*
* @good 解决了父类属性,子类共享问题
* @good 可以多继承,call多个
* @good 可以向父类传参数
*
*
* @bad 无法继承原型链上的属性和方法
* @bad 实例并不是父类的实例,只是子类的实例 cat instance of Animal 是false
* @bad 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
*/
function C() {
A.call(this)
this.say = ';llllll'
}
const c = new C()
console.log(c)
- 经典继承
function create(obj) {
if(Object.create) {
return Object.create(obj);
} else {
function F(){};
F.prototype = obj;
return new F();
}
}
vue2
vdom diff
简单的diff,遍历newVnode,每一个在oldVnode里find,找到了,移动位置或者修改子节点,没找到是新增节点,最后在oldVnode里没有被处理的删掉
// base on snabbdom
// 同级比较
vue2 响应式实现
// vue2 缺点
// 默认会递归
// 数组改变length无效
// 对象不存在的属性,无法被拦截
let OldProto = Array.prototype;
let proto = Object.create(OldProto);
['push', 'shift', 'unshift'].forEach(item => {
// 函数劫持 函数重写
proto[item] = function () {
OldProto[item].call(this, ...arguments)
updateView()
}
});
function updateView() {
console.log("udpate")
}
function observer(target) {
if(typeof target !== 'object' || !target){
return target;
}
if(Array.isArray(target)){
target.__proto__ = proto;
target.forEach(item => {
observer(item)
})
}else {
for(let key in target){
defineReactive(target, key, target[key])
}
}
}
function defineReactive(target, key, value) {
// 递归
observer(value);
Object.defineProperty(target, key, {
get(){
return value;
},
set(newValue){
if(newValue !== value){
observer(newValue)
updateView();
value = newValue;
}
}
});
}
let data = {
name: 'huahua',
age: [1,2,3]
};
observer(data);
data.name = 'lala'
data.age.push(88)
拖拽优化
import $ from "jquery";
import state from "../state"
const doc = document;
let E_SIZER = {};
let ELEMENT = null;
let start = false;
export default function initDrag() {
function moveFn(e) {
if(start){
e.stopPropagation()
e.preventDefault()
let moveX = e.clientX - E_SIZER['distX'];
let moveY = e.clientY - E_SIZER['distY'];
E_SIZER['moveX'] = moveX
E_SIZER['moveY'] = moveY
ELEMENT.style.transform = `translate3d(${moveX}px, ${moveY}px, 1px)`
}
}
$("#onlineReport").on("mousedown", '.draggable', function (event) {
console.log("drag-start...");
if ($(event.target).hasClass("draggable")) {
start = true;
E_SIZER = {}
ELEMENT = event.target;
event.stopPropagation()
event.preventDefault()
let matrix3dReg1 = /^matrix3d\((?:[-\d.]+,\s*){12}([-\d.]+),\s*([-\d.]+)(?:,\s*[-\d.]+){2}\)/;
let matrixReg = /^matrix\((?:[-\d.]+,\s*){4}([-\d.]+),\s*([-\d.]+)\)$/;
let matrix3dSourceValue = $(event.target).css('transform');
let matrix3dArrValue = matrix3dSourceValue.match(matrix3dReg1) || matrix3dSourceValue.match(matrixReg);
// 记录鼠标点击时的坐标
E_SIZER['clientX'] = event.clientX;
E_SIZER['clientY'] = event.clientY;
// 记录 matrix 解析后的 translateX & translateY 的值
E_SIZER['targetX'] = matrix3dArrValue[1];
E_SIZER['targetY'] = matrix3dArrValue[2];
// 计算坐标边界巨鹿
E_SIZER['distX'] = E_SIZER['clientX'] - E_SIZER['targetX'];
E_SIZER['distY'] = E_SIZER['clientY'] - E_SIZER['targetY'];
// 绑定 mousemove 事件
doc.addEventListener('mousemove', moveFn, false)
}
});
doc.addEventListener('mouseup', function (e) {
if(start){
e.stopPropagation()
e.preventDefault()
doc.removeEventListener('mousemove', moveFn)
let item = state.currentData[state.current.item];
item.moveX = E_SIZER.moveX;
item.moveY = E_SIZER.moveY;
start = false;
}
}, false);
}