整理

本文探讨了前端开发中的安全问题,如XSS和CSRF防护策略,以及JavaScript的函数特性和事件处理。深入讲解了webpack加载器原理、变量作用域、DOM事件模型、错误监控、性能优化技巧,包括回流与重绘、资源加载优化和本地HTTP服务器的访问限制。此外,文章还涵盖了数据结构操作、数组去重方法、HTTP与HTTPS的区别、CSS3新特性、浏览器兼容性问题以及前端页面构建流程。通过对这些知识点的总结,帮助开发者更好地理解和掌握前端开发的核心概念和最佳实践。
摘要由CSDN通过智能技术生成

1.前端安全问题有哪些,如何防范
主要有XSS攻击和CSRF攻击
xss:跨站脚本攻击,在网页里植入一段恶意代码,在该网站的作用域下执行了这段代码
防范:
1.在服务端设置对cookie的保护,也就是设置httponly,防止用户通过document.cookie来读取cookie
2.对用户的输入进行编码和解码,以及转义和过滤用户的输入内容,过滤掉用户输入的dom属性以及style iframe script等等

CSRF: 跨站请求伪造,用户登录了一个目标网站,诱使用户访问一个攻击页面,利用用户对目标网站的信任,发起伪造请求。
防范:
1.使用验证码或者anti-csrf-token
token大概流程
用户登录网页,服务端生成一个token,放在用户的session或者浏览器cookie中
然后给表单一个附带token参数
提交表单的时候检查 表单的token是否与用户 的token一致

2.什么时候不要使用箭头函数?
在需要动态上下文的场景

1.定义对象的方法

const calculator = {
    array: [1, 2, 3],
    sum: () => {

        console.log(this === window); // => true
        return this.array.reduce((result, item) => result + item);
    }
};

console.log(this === window); // => true

// Throws "TypeError: Cannot read property 'reduce' of undefined"
calculator.sum();

注意这里使用箭头函数定义calculator对象里的方法 this指向是window

不使用箭头函数

const calculator = {
    array: [1, 2, 3],
    sum() {
        console.log(this === calculator); // => true
        return this.array.reduce((result, item) => result + item);
    }
};
calculator.sum(); // => 6

this指向正确的指向了 calculator

2.定义原型上的方法

function Cat(name) {
    this.name = name;
}

Cat.prototype.sayCatName = () => {
    console.log(this === window); // => true
    return this.name;
};

const cat = new Cat('Mew');
cat.sayCatName(); // => undefined

不使用箭头函数

function Cat(name) {
    this.name = name;
}

Cat.prototype.sayCatName = function () {
    console.log(this === cat); // => true
    return this.name;
};

const cat = new Cat('Mew');
cat.sayCatName(); // => 'Mew'

3.定义事件的回调函数
常见的 DOM 事件回调函数(event listenner)绑定

const button = document.getElementById('myButton');
button.addEventListener('click', () => {
    console.log(this === window); // => true
    this.innerHTML = 'Clicked button';
});

不使用箭头函数

const button = document.getElementById('myButton');
button.addEventListener('click', function() {
    console.log(this === button); // => true
    this.innerHTML = 'Clicked button';
});

4.定义构造函数
使用箭头函数会报 Message不是一个构造函数

const Message = (text) => {
    this.text = text;
};
// Throws "TypeError: Message is not a constructor"
const helloMessage = new Message('Hello World!');

不使用箭头函数

const Message = function(text) {
    this.text = text;
};
const helloMessage = new Message('Hello World!');
console.log(helloMessage.text); // => 'Hello World!'

3.webpack.load的原理
loader相当于是源码的一个转换元件
是使用node.js运行的
把源文件作为参数,返回新的资源函数

4.let和const
let是更完美的var
1.let声明的变量具有块级作用域
2.let声明的全局对象 不是全局对象的属性 但仍然可以用 window.变量名的方式访问
3.形如for(let i=0; …) 每次循环的时候会为i创建一个新的绑定
4.let声明的变量只有在控制流到达该变量被执行的代码时才会被装载,在此之前调用会报错

const
const定义的常量 不可以重新赋值
const定义的变量 可以改变这个变量的属性

const obj = {a:1, b:2}
obj.a = 3
obj = {} // 重新赋值 会报错 
console.log(obj.a) // 3

5.box-sizing
box-sizing的作用?
设置css的盒子模型为标准模型还是ie模型
标准模型的的宽只计算content的宽 而ie模型还包括padding和border

box-sizing的三个值
content-box 只计算content的宽
padding-box 宽度为content+padding的宽
border-box 宽度为content+padding+border

6.h5标签语义化和seo
标签语义化 有利于 seo 方便搜索引擎更容易读懂网页表达的意思
例如 标题使用h1-h6
列表使用ul、ol
需要强调的文字使用 strong等等

7.git 如何批量删除分支

git branch  | grep ‘branchName’ | xargs git branch -D

举例
假设我们创建了很多分支
$ git branch
brabch
branch2
branch3
branch4
chucklu_zhCN

git branch查看分支
以bra开头的是我们需要删除的分支
使用命令逐个删除太麻烦
$ git branch | grep ‘bra‘
brabch
branch2
branch3
branch4

于是就有了我们的 git branch | grep ‘branchName’ | xargs git branch -D

解释一下
grep 可以用于在文件或流中查找匹配的结果
xargs 将前命令的执行结果作为参数传递给后一条命令
| 连接两个命令

这条语句的意思是 :
列出本地所有的分支
搜索含有branchName的分支
将搜索结果逐个传个删除分支的函数

8.创建对象的几种方法
1.工厂方法

function createPerson (name) {
	// 1.原料
	var obj = new Object()
	// 2.加工
	obj.name = name
	obj.showName = function () {
		alert(this.name)
	}
	// 3.出场
	return obj
	
}
var  man = createPerson('小明')
man.showName() // 小明

工厂方式的优点
解决了创建类似对象的问题,把实例化放在函数内部来做
缺点:无法区分是哪个对象产生的实例

2.构造函数的方法
有参数的 写法

function CreatePerson (name)  {
	this.name = name
	this.showNmae = function () {
		alert(this.name)
	}
}
var person  = new CreatePerson("小明")
person.showName() // 小明

不传参的写法

function CreateSong ()  {
}
var person = new CreateSong()
person.name = '小明'
person.showName = function () {
	alert(person.name)
}
person.showName() // 小明

构造函数的优点:
它的每个实例都会被视作一个特定的类型,这点是工厂方法所不及的
缺点
每个属性和方法都需要在实例上重新创建一次

3.字面量方式

var person = {
	name: '小明',
	showName: function () {
		alert(this.name)
	}
}
person.showName()  // 小明

4.原型方法

function Person () {
}
Person.prototype.name = '小明'
Person.prototype.showName = function () {
	alert(this.name)
}

原型方法的优点:
它所有的实例都共享它的属性和方法
缺点:
但是共享也是它的缺点,实例一般是有自己的单独属性的

5.混合方法
混合方法使用构造方法定义实例的属性 而使用原型定义共享的方法和 属性

function CreatePerson (name) {
	this.name = name
}
CreatePerson.prototype.showName = function () {
	alert(this.name)
}

var person1 = new CreatePerson('小明') 
person1.showName() // 小明

var person2 = new CreatePerson('小明') 
person2.showName() // 小明
console.log(person1.showName === person2.showName) // true

总结
方法使用原型定义
属性看情况,如果属性不可变,那么定义到原型上,否则用构造函数的
方法定义

9.浏览器渲染的过程(原理)
1.html解析成dom 树形结构
2.css解析成cssOM
3.dom和cssOM整合成render tree
4.显卡绘制在屏幕上(绘制的过程就依照回流与重绘)

10.路由
什么是路由?
根据不同的url显示不同的页面和内容

使用场景?
spa单页面应用 因为单页面应用前后端分离,后端不会向前端提供路由

实现路由的方法
h5 history api (见上)

路由的优缺点
优点:相比后端路由每次访问新页面都要向服务器发送请求,这个过程还有延迟,前端路由访问新页面只用改变一下路径,
没有延迟,对用户体验是一个提升

缺点:
浏览器前进后退都会重新发送请求,没有合理利用缓存
无法在前进后退的时候记住当前滚动条的位置

11.script标签的defer和async
defer是等html解析完成后再执行脚本 如果有多个defer 按次序执行
async是在脚本加载完成后立即执行 执行顺序与加载顺序无关

12.同源与跨域
什么是浏览器同源策略?
限制一个源的脚本与文档与另外一个源的交互,用来隔离恶意文件

什么样叫同源?
协议名、主机名和端口号相同

ttp://www.123.com/index.html 调用 http://www.123.com/server.php (非跨域)

http://www.123.com/index.html 调用 http://www.456.com/server.php (主域名不同:123/456,跨域)

http://abc.123.com/index.html 调用 http://def.123.com/server.php (子域名不同:abc/def,跨域)

http://www.123.com:8080/index.html 调用 http://www.123.com:8081/server.php (端口不同:8080/8081,跨域)

http://www.123.com/index.html 调用 https://www.123.com/server.php (协议不同:http/

一个url的组成 (协议)://(主机名):(端口号)/(文件路径)/(文件名 )

什么是跨域?
指浏览器不能执行其他网站的脚本,它是由浏览器同源策略造成的,是浏览器对脚本的安全措施

跨域的解决方案
jsonp
原理: 客户端通过script标签的src属性向服务端发送请求,服务端返回一个js形式的数据内容,客户端通过执行函数接收数据内容。
缺点:①只能发送GET请求,复杂的请求内容无法提交。
②必须保证服务端返回的数据内容能被js处理

CORS
优点:①支持多种请求方式
②支持主域名不同的情况的跨域
③数据包装简单

cors方法是在服务器php文件里添加header(‘Access-Control-Allow-Origin:*’)
服务端代理
简单了解一下
1、在localhost:81/a.html中,向同源下的某个代理程序发出请求

$.ajax({
    url:'/proxy.php?name=hello&info=information',   //服务器端的代理程序
    type:'GET',
    success:function (){}
})

然后再代理程序里 proxy.php中,向非同源下的服务器发出请求,获得请求结果,将结果返回给前端。

<?php 

$name=$_GET['name'];
$info = $_GET['info'];
$crossUrl = 'http://b.com/sub?name='.$name;   //向其他域下发出请求
$res = file_get_contents($crossUrl);
echo $res; 
 ?>

简述一下过程
发起一个ajax,向同源的某个代理程序发出请求,这个代理程序向非同源的服务器发送请求,获得请求结果,将结果返回给前端。

h5的postMessage

//     http://test.com/index.html

<div style="width:200px; float:left; margin-right:200px;border:solid 1px #333;">
        <div id="color">Frame Color</div>
    </div>
    <div>
        <iframe id="child" src="http://lsLib.com/lsLib.html"></iframe>
    </div>
   //  http://test.com/index.html通过postMessage()方法向跨域的iframe页面http://lsLib.com/lsLib.html传递消息
   window.function(){
            window.frames[0].postMessage('getcolor','http://lslib.com');
        }

test.com上面的页面向lslib.com发送了消息,那么在lslib.com页面上如何接收消息呢,监听window的message事件就可以

// http://lslib.com/lslib.html

window.addEventListener('message',function(e){
                if(e.source!=window.parent) return;
                var color=container.style.backgroundColor;
                window.parent.postMessage(color,'*');
            },false);
           

简单概括一下步骤
test页面 使用window.frames[0].postMessage(data, "目标网址")
然后目标网址通过window.addEventLIstener(‘message’, func) 监听message事件

13.原型
原型是什么?
原型是一个普通对象,几乎所有对象都有原型,原型能够定义方法和属性,构造函数创建的对象可以引用原型上的方法

如何查看原型?

function Test () {
}
// 1.使用prototype (显示原型)
Test.protoType

//2. __proto__ (隐式原型)
var p = new Test()
p.__proto__

/ /3.es6推荐使用  Object.getPrototypeOf()
Object.getPrototypeOf(p)

顺便补充 一下 isPrototypeOf()

 	function A(){ }
 	
            var a = new A();
            //通过A创建一个对象a,所以a的__proto__是指向A.prototype的
            console.log(A.prototype.isPrototypeOf(a));
            //A.__proto__是指向Object.prototype的
            console.log(Object.prototype.isPrototypeOf(A));
            //由于A.prototype的__proto__是指向Object.prototype的,所以a也就含有Object.prototype咯
            console.log(Object.prototype.isPrototypeOf(a));

闭包是什么?
当一个内部函数被外部函数以外的变量引用的时候,就形成了闭包
例如:

<script>
    function  outer(){
        var num=0;//内部变量
       return  function add(){//通过return返回add函数,就可以在outer函数外访问了。
            num++;//内部函数有引用,作为add函数的一部分了
           console.log(num);
        };
    }
    var func1=outer();//
    func1();//实际上是调用add函数, 输出1
    func1();//输出2
    var func2=outer();
    func2();// 输出1
    func2();// 输出2
</script>

闭包的特性?
封闭性: 外部无法访问闭包内部的数据
持久性:一般来说,函数调用完毕,会被注销,而闭包的外部函数被调用后,闭包结构依然存在

什么场景需要闭包?
如果想封装一个私有变量或者私有方法可以使用闭包

闭包的作用?
保存私有变量,对外提供接口,但外部不能直接访问这个变量

闭包的缺点?
消耗内存,因为不会被垃圾回收机制回收,使用不当,会造成内存泄漏

什么是垃圾回收机制?
1.当某个变量不再被引用就会被垃圾回收
2.当两个变量相互引用,但不被第三个变量引用, 也会被回收

垃圾回收机制的方式?
1.标记清除:变量进入环境,给它一个进入标记,离开环境,给它一个清除标记。垃圾回收器会过滤,保留进入环境的变量,剩余被清除
2.计数清除:变量的引用次数,被引用一次则加1,当这个引用计数为0时,被视为准备回收的对象。

开发过程中遇到的内存泄露情况,如何解决的?
内存泄漏:内存泄露是指一块被分配的内存既不能使用,又不能回收,直到浏览器进程结束。C#和Java等语言采用了自动垃圾回收方法管理内存,几乎不会发生内存泄露。
什么情况会发生?
1.当页面中元素被移除或替换时,若元素绑定的事件仍没被移除

<div id="myDiv">
    <input type="button" value="Click me" id="myBtn">
</div>
<script type="text/javascript">

    var btn = document.getElementById("myBtn");
    btn.onclick = function(){
        document.getElementById("myDiv").innerHTML = "Processing...";
    }
</script>

解决办法

<div id="myDiv">
    <input type="button" value="Click me" id="myBtn">
</div>
<script type="text/javascript">
    var btn = document.getElementById("myBtn");
    btn.onclick = function(){
    btn.onclick = null;
        document.getElementById("myDiv").innerHTML = "Processing...";
    }
</script>

或者使用事件委托

var fans = document.getElementById('fans')
    fans.onclick = function (e) {
        if (e.target.id === 'fan') {
            console.log('sdad')
        }
    }

2.闭包引起内存泄漏

function outer() {
    var fan = document.getElementById('fan')
    fan.onclick = function () {
        console.log('click')
    }
}

在函数内部定义函数,引用的时候外爆形成了闭包

解决办法

将回调函数定义在外部

function outer() {
    var fan = document.getElementById('fan')
    fan.onclick = handle
}

function handle() {
    console.log('dh')
}

outer()

或者在定义事件处理函数的外部函数中,删除对dom的引用

function outer() {
    var fan = document.getElementById('fan')
    fan.onclick = function () {

    }
    fan = null
}

参考 https://www.cnblogs.com/redpen/p/8242196.html
如何创建闭包?
1.函数作为参数传递
2.函数作为返回值

 var a = 200
    // 函数作为返回值
    function F1() {
        var a = 100
        return function () {
            console.log(a)
        }
    }
    var f1 = F1() // a为100

    // 函数作为参数传递
    function F2(fn) {
        var a = 300
        fn()
    }
    F2(f1) //  a还是 100

14.错误监控
前端错误监控分为 即时运行错误(代码错误)资源加载错误

错误监控的方式
即时运行错误
try …catch
window.onerror

资源加载错误
object.onerror : img标签、script标签都可以添加onerror事件,用来捕获资源加载错误;
performance.getentries: 可以获取到所有已经加载的资源的加载时间,间接拿到加载失败的资源
举例说明
浏览器打开一个网站,在Console控制台下
输入

performance.getentries().foreach(item => {
	console.log(item.name) // 打印加载成功的资源名字
})

// 再输入查找img标签
document.getElementsByTagName(‘img’)

// 对比这俩数组就能间接知道加载失败的资源

跨域js运行错误可以捕获吗?错误提示?怎么解决?
可以捕获 显示script error 解决跨域问题见上

上报错误的方式
1.采用ajax上报
2.采用img对象上报 (推荐)
只需要动态创建一个img对象即可

(new Image()).src = ‘xxxx’

在浏览器的network里就可以 看到啦

15.dom事件

// dom 0级事件
element.onclick = function () {
}

// dom 2级事件
element.addeventListener(‘click’, function () {})

dom的事件模型: 事件冒泡和事件捕获

什么是事件冒泡和捕获呢?
事件冒泡: 事件从一个具体的元素开始,逐级向上传递直到window
事件捕获: 事件从window开始,逐级向下传递到具体的元素, ie不支持捕获

以下面的例子为例

<!DOCTYPE html>
<html>
<head>
         <title>example</title>
</head>
<body>
         <div>click</div>     
</body> 
<html>   

事件冒泡的过程为 div =》 body =》 html =》 document
事件捕获的过程 document =》 html =》 body =》 div

事件流: 事件捕获阶段 处于目标阶段 事件冒泡阶段
还是以上面的例子
在这里插入图片描述

event对象常见的方法
e.stopPropagation()
e.preventDefault()
e.target()// 返回触发事件的元素 例如li
e.currentTarget() // 返回绑定事件的元素 例如ul
e.stopImmediatePropagation() 和 e.stopPropagation()区别
假如说我给btn绑定了两个click事件 ,回调函数分别打印1和2
使用e.stopImmediatePropagation() 只会打印1 除了阻止冒泡 ,还会 停止当前节点后续同类的事件

自定义事件的写法


// 创建自定义事件
var e = document.createEvent('HTMLEvents')
// 自定义事件另一种写法  var e = new Event('HAHA')
// 初始化事件
e.initEvent('HAHA', false, true) // 三个参数  事件名称 是否冒泡  是否阻止浏览器默认行为
// 监听事件
elem.addEventListener('HAHA', function () {
	alert('触发事件!')
}
// 触发自定义事件
elem.dispatchEvent(e)

ie8及以下浏览器不支持createEvent怎么办呢?

虽然ie8不支持createEvent,但是ie8有onpropertychange事件
当dom元素的属性值发生变化会触发
也就是说,我们给dom设置一个属性a ,监听onpropertychange事件,判断变化的属性是不是a
是的话就执行自定义事件
代码如下:

<h1>
        请使用IE8或更低版本的浏览器测试
</h1>
<button id="btn" eventAttr="0">点击修改按钮的eventAttr属性值</button>
<script>  
	var btn = document.getElementById('btn')

	btn.attachEvent('onpropertychange', function (e) {
		if (e.propertyName === 'eventAttr') {
			alert('触发了')
		}
	}

	btn.attachEvent('onclick', function () {
		btn.setAttribute('eventAttr', 1)
	}
</script> 

什么是事件委托?
事件委托指的是,不在事件的发生地设立监听函数,而是在事件发生地的父元素或者祖先元素设置监听器函数,这样可以大大提高性能,因为可以减少绑定事件的元素

实现一个通用事件绑定的函数
支持传三个或者四个参数
传三个参数的时候为普通的绑定事件
传四个参数为事件代理

 function delegate(elem, type, selector, fn) {
    if (fn == null) {
        fn = selector
        selector = null
    }
    if (elem.addEventListener) {
        elem.addEventListener(type, _handle)
    } else {
        elem.attachEvent('on' + type, _handle)
    }
    function _handle(e) {
       var  e = e || window.Event
        var target = e.target || e.srcElement
        if (!selector) {
            fn.call(target, e)
        } else if (matchSelector(target, selector)) {
            console.log(this) // ul
            fn.call(target, e)
        }
    }

    function matchSelector(elem, selector) {
        // use id
        if (selector.charAt(0) === '#') {
            return elem.id === selector.slice(1)
        } else if (selector.charAt(0) === '.') { // use class
            return (" "  + elem.className +" ").indexOf(" " + selector.slice(1) + " ") > 0
        } else { // use tagname
            return elem.tagName.toLowerCase() === selector.toLowerCase()
        }
    }
}

var wrap = document.getElementById('wrap')
    delegate(wrap, 'click', 'li', function (e) {
        console.log(e.target.innerHTML)
    })
    var d = document.getElementById('d')
    delegate(d, 'mouseover', function (e) {
        console.log(e.target.id)
    })

DOM事件级别?
dom0级事件 elem.onclick = function () {}
dom2级事件 elem.addEventListener(‘click’, func, false)
dom3级事件 elem.addEventListener(‘keyup’, func, false)

16.本地建立的http server 为什么只能在同一个wifi下访问
没有 公网ip 所以不能被外网访问 是个局域网

17.回流与重绘
回流
浏览器根据每个盒子模型的样式来计算,样式包括盒子的尺寸、布局、显隐,并根据计算结果将元素放在它该出现的位置,这个过程 叫回流

重绘
当盒子模型的颜色、字体大小等等不影响布局,只影响外观的属性, 浏览器将这些属性绘制出来,呈现在页面上,这个过程叫重绘

什么情况下会触发重绘与回流?
对dom结构的添加、删除 (回流+重绘)
仅修改dom元素的字体、颜色等等 (只触发重绘,因为不需要调整布局)
dom元素位置改变 (回流+重绘)
浏览器resize事件 (回流+重绘)

总结:影响盒子尺寸、布局、显隐就会触发 回流
只影响 盒子的外观 支出法重绘
回流是一定会触发重绘的!

如何避免回流 与 重绘?
1.使用cssText
设置style属性改变结点样式的话,每一次设置都会触发一次reflow
使用cssText一次性改变样式或者直接切换元素的className
cssText只触发一次

var left  = 5,
	top = 5
	fff = 5  // cssText里拼接top会失效 不知道原因
	// 不好的写法
el.style.left = left + 'px'
el.style.top = top  + 'px'	

// 好的写法
el.style.cssText += "; left: " + left + "px; top: " + fff + "px;";

2.使用documentFragment进行缓存,那么就只用一次回流与重绘

// 不好的写法 触发了两次回流与重绘
var p, t
p = document.createElement('div')
t = document.createNodeText('我是1')
p.appendChild(t)
document.body.appenChild(p)

p = document.createElement('div')
t = document.createNodeText('我是2')
p.appendChild(t)
document.body.appenChild(p)


// 好的写法
var p, t, frag
frag = document.createDocumentFragment()
p = document.createElement('div')
t = document.createNodeText('我是1')
p.appendChild(t)
frag.appenChild(p)

p = document.createElement('div')
t = document.createNodeText('我是2')
p.appendChild(t)
frag.appenChild(p)

document.body.appendChild(frag)

3.使用cloneNode和replaceChild
思想和上述的documentFragment类似

var oldNode = document.getElementById('targetId')
clone = oldNode.cloneNode(true) // 深拷贝
// 操作克隆的节点
...
// 操作完毕后
oldNode.parentNode.replaceChild(clone, oldNode)

4.不要经常访问会导致回流的属性,如果一定要访问,最好缓存

// 不好的写法
for(循环) {
	el.style.left = el.offsetLeft + 5 + "px";
	el.style.top = el.offsetTop + 5 + "px";
}

// 好的写法
var left = el.offsetLeft,
	top = el.offsetTop,
	e = el.style; 
	for (循环) { 
	left += 10; 
	top += 10; 
	e.left = left + "px"; 
	e.top = top + "px"; 
}

5.需要使用动画的元素,使用绝对定位或者fixed

6.尽量不使用table布局

18.数组去重
第零种方法 (效率最低 一个一个对比)

var arr = [1, 1, '1', '2',2,2, '1']

    function unique(arr) {
      var _arr = arr.slice()
      for (var i = 0; i < _arr.length; i++) {
          for (var j = i + 1; j < _arr.length; j++) {
              if (_arr[i] === _arr[j]) {
                  _arr.splice(j, 1) // splice会修改原数组 返回被删除或者添加的元素
                  j--
              }
          }
      }
      return _arr
  }

  console.log(unique(arr))

第一种写法
利用下标

function unique (arr) {
        var res = arr.filter(function (item, index, array) {
            // 参数array就是传入的arr 
            // 数组和字符串都有indexOf方法
            return array.indexOf(item) === index // 如果不重复 那么 这两者一定是相等  
        })
        return res
    }

    var a = [1, 1, '1', '2', 1]
    console.log(unique(a)) // [ 1, '1', '2']

上面的写法可能还不满意 复杂度高

第二种 写法 sort
先排序然后与相邻的对比

  function unique(a) {
        // a.concat() 是为了不改变当前数组 我习惯用slice了
        // 对a的副本sort 相同的值应该是相邻的 [1, 1, 1, 2, 2, 3, 4]
        console.log(a.concat().sort())
        return a.slice().sort().filter(function (item, index, arr) {
            return !index || item !== arr[index - 1]
        })
    }

    var a = [1, 1, 3, 2, 1, 2, 4];
    console.log(unique(a))

这种 写法的缺点是只能针对值全部是Number类型,如果String类型混进去了 ,由于toStirng方法 字符串‘1’和 数字1会被认为相等

修改一下

var a = [1, 1, '1', '2', 2,2]
    function unique(arr) {
        var _arr  = arr.slice().sort()
        var res = []
        for (var i = 0; i < _arr.length; i++) {
             if (!i || _arr[i] !== _arr[i - 1]) {
                 res.push(_arr[i])
             }
        }
        return res
    }
    console.log(unique(a))

第三种写法 es6

 function unique(a) {
      // Array.from类数组转数组 set的一个特性元素不能重复
      return Array.from(new Set(a))
  }
  let a = [{name: "hanzichi"}, {age: 30}, new String(1), new Number(1), 1, '1', 1, 1]; //  [object, object, String,  Number, 1, '1']
  console.log(unique(a))

第四种写法
借用对象的属性进行区分
时间最快 牺牲了空间

    var a = [1, 1, '1', '2',2,2, '1']
    function unique(arr) {
        var res = []
        var obj = {}
        var type
        for (var i = 0; i < arr.length; i++) {
            type = typeof arr[i]
           if (!obj[arr[i]]) {
               obj[arr[i]] = [type]
               res.push(arr[i])
           } else if (obj[arr[i]].indexOf(type) < 0) {
                obj[arr[i]].push(type)
                res.push(arr[i])
           }
        }
        return res
    }
    console.log(unique(a))

19.深拷贝 浅拷贝
浅拷贝
复制的是指向某个对象的指针,而不是复制对象本身,新旧对象公用一块内存
深拷贝
创建一个和旧对象一模一样的对象, 新旧对象不公用内存,修改也不会相互影响

举个例子

// 浅拷贝  修改obj2.b , obj1.b也会改变
 let obj1 = {a: 1, b: 2}
    let obj2 = obj1
    obj2.b = 3
    console.log(obj1.b) // 3
// 深拷贝 修改不会相互影响
    let obj1 = {a: 1, b: 2}
    let obj2 = {a: obj1.a, b: obj1.b}
    obj2.b = 3
    console.log(obj1.b) // 2

实现深拷贝的方法
1.如上 一个一个复制

2.es6提供的一个object.assign方法

    let obj1 = {a: 1, b: 2}
    let obj2 = Object.assign({}, obj1)
    obj2.b = 3
    console.log(obj1.b) // 2

3.使用JSON的方法
缺点是只有JSON格式的对象才能使用

    let obj1 = {test: {a:1}}
    let obj2 =  JSON.parse(JSON.stringify(obj1))
    obj2.test.a = 2
    console.log(obj1.test.a) // 1

4.jquery中提供了$.extend()

var $ = require('jquery');
var obj1 = {
    a: 1,
    b: { f: { g: 1 } },
    c: [1, 2, 3]
};
var obj2 = $.extend(true, {}, obj1); // true代表深拷贝
console.log(obj1.b.f === obj2.b.f); // false

5.lodash库提供了一个_.cloneDeep

var _ = require('lodash');
var obj1 = {
    a: 1,
    b: { f: { g: 1 } },
    c: [1, 2, 3]
};
var obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f);  // false

20.如何合成雪碧图
1.gulp中可以使用一个插件 gulp.spritesmith
参考https://www.cnblogs.com/qianlitiaotiao/p/5054231.html
2.webpack同样可以使用一个插件 webpack.spritesmith
https://www.cnblogs.com/myfirstboke/p/8743966.html
3.有一些网站提供 在线合成

21.做了哪些代码优化
html:
1.代码语义化
2.尽量不适用iframe框架
3.避免使用重定向
例如window.location.href='hello.html';

css:
1.布局代码写在前面
2.删除空样式
3.不滥用浮动、字体
4.选择器性能优化
5.避免使用id做选择器

js:
代码压缩,减少重复代码 webpack.optimize.uglifyJsPlugin
webpack插件进行压缩

HtmlWebpackPlugin打包合并 html
MiniCssExtractPlugin打包css
uglifyJsPlugin压缩代码

图片优化:
使用雪碧图,或者图片用webP格式(有在线转化器)
图片懒加载

减少dom操作:
缓存已经 访问过的节点
“离线更新”节点 如上 使用documentFragment或者cloneNode + replaceChild
将一些事件放在DOMContentLoaded里去尽早的执行
事件节流

其他:
1.cookie优化
如果这个网站不需要cookie,把他的expires设为0或-1,关闭的同时清理掉cookie
将cookie的大小减到最小
设置合理的过期时间
获取 cookie
document.cookie

设置cookie

   function setCookie(name, value, day) {
        if (day !== 0)  { // 设置为0 即为关闭浏览器立即清除
            var expires = day * 24 * 60 * 60 * 1000;
            var date = new Date(+new Date()+expires);
            document.cookie = name + '=' + value + ';expires=' + date.toUTCString()
        } else {
            document.cookie = name + '=' + value
        }
    }
    setCookie('hello', 'nihao', 1)

删除cookie setCookie('hello', '', -1)

修改
重复赋值就可以覆盖掉上一个

cookie的格式

document.cookie = "cookieName=mader; expires=Fri, 31 Dec 2017 15:59:59 GMT; path=/mydir; domain=cnblogs.com; max-age=3600; secure=true";

cookieName=mader :name=value,cookie的名称和值

expires=Fri, 31 Dec 2017 15:59:59 GMT: expires,cookie过期的日期,如果没有定义,cookie会在对话结束时过期。日期格式为 new Date().toUTCString()

path=/mydir: path=path (例如 ‘/’, ‘/mydir’) 如果没有定义,默认为当前文档位置的路径。

domain=cnblogs.com: 指定域(例如 ‘example.com’, ‘.example.com’ (包括所有子域名), ‘subdomain.example.com’) 如果没有定义,默认为当前文档位置的路径的域名部分。

max-age=3600: 文档被查看后cookie过期时间,单位为秒

secure=true: cookie只会被https传输 ,即加密的https链接传输

封装cookie的使用方法

var cookieUtils = {
        set: function (name, value, expires, path, domain, secure) {
            var cookieText = ''
            cookieText += encodeURIComponent(name) + '=' + encodeURIComponent(value)
            if (expires > 0) {
                var day = new Date(+new Date() + expires * 24 * 60 *60 * 1000)
                cookieText += '; expires=' + day.toUTCString()
            }
            if (path) {
                cookieText += '; path=' + path
            }
            if (domain) {
                cookieText += '; domain=' + domain
            }
            if (secure) {
                cookieText += '; secure'
            }
            document.cookie = cookieText
        },
        get: function (name) {
            var cookieName = encodeURIComponent(name) + '='
            var cookieStart = document.cookie.indexOf(cookieName)
            var cookieValue = ''
            if (cookieStart > 0) {
                var cookieEnd = document.cookie.indexOf(';', cookieStart)
                if (cookieEnd < 0) {
                    cookieEnd = document.cookie.length
                }
                cookieValue = decodeURIComponent(document.cookie.substring(cookieStart + cookieName.length, cookieEnd))
            }
            return cookieValue
        },
        unset: function (name) {
            this.set(name, '', -1)
        }
    }


    cookieUtils.set('first', 'name', 1)

    console.log(cookieUtils.get('first'))
    cookieUtils.unset('first')

参考 https://www.cnblogs.com/lxf1117/p/6435612.html

2.使用cdn加速
cdn的原理 就是在不同的地点缓存内容,然后将用户的请求定向到最近的缓存服务器上

3.使用dns预解析

head中添加了以下代码(用以DNS预解析):

<meta http-equiv="x-dns-prefetch-control" content="on" />
<link rel="dns-prefetch" href="http://bdimg.share.baidu.com" />                                 
<link rel="dns-prefetch" href="http://nsclick.baidu.com" />
<link rel="dns-prefetch" href="http://hm.baidu.com" />
<link rel="dns-prefetch" href="http://eiv.baidu.com" />

22.HTTP和HTTPS
HTTP: 超文本协议,是用来提供一种 发布和接受html的方法
HTTPS: 超文本传输安全协议,提供一个身份验证,保护交换数据的隐私和完整性

HTTP协议的特点?
简单快速: 输入URI就可以访问某个资源了?(图片、网页等 每个资源的URI是固定的)
灵活: 可以传输任何数据类型
无连接:连接一次就会断开,不会保持连接
无状态: 例如客户端向服务端发起请求,http建立连接,请求结束,断开,客户端 再发起一个请求,http不会保存这个状态,无法判断是否和上次连接相同

HTTP报文组成部分
请求报文:
请求行
请求头
空行
请求体

响应报文 :
状态行
响应头
空行
响应体

HTTP方法:
GET:获取资源
POST: 传输资源
PUT: 更新资源
DELETE: 删除资源
HEAD: 获取报文头部

POST和GET区别
1.浏览器点击后退,post会再次发送请求,而 get不用
2.浏览器会主动缓存get请求,而不会缓存post
3.get没有post安全,因为请求的参数直接暴露到url上

HTTP状态码
1xx 请求接受,继续处理
2xx 请求成功接受
3xx 重定向
4xx 客户端错误
5xx 服务端错误

200 请求成功
301 请求的页面已转向新的url
304 表示请求资源没有修改, 可以直接使用浏览器缓存.
404  找不到  服务器上不存在客户机所请求的资源
500  服务器内部错误

持久连接?
1.HTTP1.0采用的 请求-应答 模式,每个请求/应答 客户端和服务端会 建立一个连接,请求完毕就断开
2.HTTP1.1支持持久连接,使用的keep-alive模式,使客户端和服务端的连接持续有效,当出现了后续请求,避免了重新建立连接

管线化?
举个例子,使用持久连接的响应如下
请求1 响应1 请求2 响应2 请求3 响应3
而管线化是将请求打包,一次传输,也会将响应打包,一次传输
请求1 请求2 请求3 响应1 响应2 响应3

管线化的特点?
1.管线化基于持久连接,所以只有 HTTP1.1支持
2.只有GET和HEAD请求可以使用管线化,POST使用有限制
3.管线化不会影响响应的顺序

cookie是什么?
本身是用来客户端与服务端通信用的
但是它有本地存储的功能,所以被拿来做缓存
使用document.cookie的方法来操作它

cookie的缺点?
1.存储量较小 只有4kb
2.每次请求都需要携带,影响性能

cookie和locationStorage/sessionStorage的区别?
1.cookie存储量只有4kb 后两者存储量为5M
2.每次请求cookie会被携带 而后两者不会
3.cookie一般需要封装才能使用 而后两者有locationStorage.getItem(key) setItem(key, value)两个api

cookie是否会被覆盖?
会被覆盖,如果写入一个重名的cookie,就会将之前的cookie覆盖掉

封装一个对cookie增删改查的函数

前置知识:
cookie在生成时会被指定一个expires值,代表cookie的生存周期,超过了这个周期,cookie会被自动清理掉
有些页面将expires值设置为0或者负数,这样在关闭浏览器的同时,会自动清理cookie

cookie同样也满足同源策略
虽然网站images.google.com与网站www.google.com同属于Google,但是域名不一样,二者同样不能互相操作彼此的Cookie。
问题来了 举个例子:
访问 zhidao.baidu.com 再访问wenku.baidu.com还需要重新登陆百度账号吗?
解决办法: 设置document.domain = ‘baidu.com’;
让页面属于这个基础域名下(那么此页面和任何二级域名为baidu.com的)

function setCookie (key, value,iDay) {
	var oDate =  new Date()
	oDate.setDate(oDate.getDate() + iDay) // 将当前的天数加上cookie保存的天数就是cookie过期的天数
	document.cookie = key + '=' + value + ';expires=' + oDate
}

function removeCookie (key) {
	setCookie(key, '', -1) // 设置expiress时间为负数就可以删除
}

function getCookie(key) {
	var cookieArr = document.cookie.split(';')
	for (var i = 0; i < cookieArr.length; i++) {
		var arr  =cookieArr[i].split('=')
		return arr[0] === key ? arr[1] : ''
	}
	return false
}

如何保存登录状态?
可以把登录信息保存在cookie中,并控制cookie的保存时间,下次访问直接验证cookie中的登录信息即可,
但是这样不安全,密码暴露在cookie中
所以还有一种方案是将密码加密后保存到cookie中,需要验证的时候,先解密再验证。

cookie由哪几部分组成?
由变量名和值组成,还有一个有效日期,是以变量=值的方式保存的

如果把ajax请求来的数据放在localStorage中,调用的时候直接去localStorage中取是否可行?
这样取到的数据不具有实时性,只是我们之前请求来的数据,而不是当前最新的数据

HTTP1.0和1.1的区别?

  1. http1.0是非持久连接,每次请求都会为客户端与服务端创建一个连接,完成后就中断连接,而1.1支持持久连接,一次连接可以传多个请求和响应。
  2. http1.0只有16个状态码,对错误的描述不够详细。而1.1对错误的描述更具体了,增加了24个状态码

HTTP和HTTPS的区别?
1.HTTPS协议需要申请证书
2.HTTP运行在tcp上,所有传输内容都是明文,而HTTPS是运行在SSL/TLS(加密解密)上,SSL/TLS运行在tcp上,所有传输内容加密
3.HTTPS有效防止了运营商劫持

tcp
tcp是一种面向连接的协议,通信双方发送数据前需要先建立一个连接,这个连接就是在客户端和服务端内存里保存一份关于对方的信息,如ip地址,端口号
当tcp接收到另一端数据时,它会发送一个确认,一般会延迟一会,ACK是累积的,一个确认字号N的ACK代表所有直到N的的字节(不包括N)都成功接收了。这样的好处是如果某个ACK丢失了,后续ACK足以确认前面的报文。
ACK 确认号
RST 重置连接
SYN 初始化序列号
FIN 报文发送方已经结束向对方发送数据

为什么建立连接需要三次握手?
目的是 客户端和服务端都需要确认双方的发送、接收功能正常

第一次握手 客户端向服务端发送一个包,服务端收到,这样能确认客户端发送以及服务端接收都是正常的

第二次握手 服务端向客户端发送包,客户端收到,客户端能确定 客户端发送接受以及服务端接受、发送都是正常的

第三次握手 客户端向服务端发包,服务端收到, 服务端就能确定 客户端发送、接收以及客户端接收、发送都是正常的

为什么要四次挥手?
第一次挥手 当有一方要关闭连接,会发送指令告诉对方,我要断开了
第二次挥手 对方会返回一个ACK,然后一方连接断开
第三次挥手 但是另一方的连接还可以继续传输数据,等发送完所有数据,会发送一个FIN来关闭这一方的连接
第四次挥手 接收到FIN的一方会返回一个ACK确认关闭

参考 https://blog.csdn.net/csdnnews/article/details/86570658

建立tcp连接和断开连接的过程?
建立连接需
要三次握手:
1.客户端的tcp向服务端tcp发送一个连接请求报文
2.服务端接受到请求报文后,若同意请求,向客户端发送一个确认报文。
3.客户端接受到确认报文,还要向服务端给出确认

断开连接四次挥手:
1.客户端打算断开连接,发送一个连接释放报文
2.服务端接受连接释放报文,随即发出确认
3.若服务端没有数据需要传输,通知tcp释放连接
4.客户端接受到连接释放报文,必须发出确认才会断开

23.如何实现响应式图片
1.手动绑定一个resize事件,然后在不同的size下加载不同的图片 (不推荐)
2.给图片添加srcset方法
这个属性是用来让浏览器根据不同的宽高、像素来加载不同的图片
属性格式:图片地址 宽度描述w 像素密度描述x,多个资源之间用逗号分隔

<img src="small.jpg " srcset="big.jpg 1440w, middle.jpg 800w, small.jpg 1x" />
  1. 使用svg矢量图

24.如何判断是否为一个数组
constructor 属性返回对创建此对象的数组函数的引用。

var a = []
// 1. instanceof
a instanceof Array

// 2.constructor
a.constructor === Array

// 3. isPrototypeOf
Array.prototype.isPrototypeOf(a)
// 4 . getPrototypeOf
Object.getPrototypeOf(a) === Array.prototype
// 5. Array.isArray 方法
Array.isArray(a)

25.unicode和utf-8
unicode是字符集
utf-8是编码规则

utf-8是对unicode字符集进行编码的一种编码方式

26.null 和 undefined的区别
1.
null表示对象的值为空
undefined表示变量we赋值或者值不存在

2.typeof null 的结果是Object
typeof undefined 结果是undefined

补充几个容易混淆的东西

 console.log(null == undefined) // true
 console.log(null === undefined) // false

29.如何让js文件延迟加载
1.将js文件放在最底部
2.给js文件加上defer=“defer” 或者async=“async”属性
3.动态创建一个 script元素

//这些代码应被放置在</body>标签前(接近HTML文件底部)
<script type="text/javascript">  
   function downloadJSAtOnload() {  
       var element = document.createElement("script");  
       element.src = "defer.js";  
       document.body.appendChild(element);  
   }  
   if (window.addEventListener)  
      window.addEventListener("load",downloadJSAtOnload, false);  
   else if (window.attachEvent)  
      window.attachEvent("onload",downloadJSAtOnload);  
   else 
      window.onload =downloadJSAtOnload;  
</script>  

4.使用jQuery的getScript方法

$.getScript("xxx.js",function(){ //  回调函数,成功获取文件后执行的函数  
      console.log("脚本加载完成")  
});

30.document.write和innerHTML的区别
前者只能重绘整个页面
后者可以只重绘一小部分页面

31.闭包的几个题目

function fun(n,o) {
  console.log(o)
  return {
    fun:function(m){
      return fun(m,n);
    }
  };
}
var a = fun(0);  a.fun(1);  a.fun(2);  a.fun(3);//undefined,?,?,?
var b = fun(0).fun(1).fun(2).fun(3);//undefined,?,?,?
var c = fun(0).fun(1);  c.fun(2);  c.fun(3);//undefined,?,?,?
//问:三行a,b,c的输出分别是什么?

答案 a打印结果分别是 undefined 0 0 0
b undefined 0 1 2
c undefined 0 1 1

32.创建函数的几种方法
1.函数声明

function fn1(){}

2.函数表达式

var fn1=function (){}

这种方式创建的其实是匿名函数

3.有名字的函数表达式

var fn1=function xxcanghai(){};

采用此种方法创建的函数在函数外层只能使用fn1不能使用xxcanghai的函数名

4.使用Function构造

console.log(Function('alert(1)'))

打印结果 也是匿名函数

anonymous() {
alert(1)
}

33.关于变量提升的题目

  function Foo ()  {
        getName = function () {
            alert(1)
        }
        console.log(this) // window
        return this
    }
        Foo.getName = function () {
            alert(2)
        }
        Foo.prototype.getName = function () {
            alert(3)
        }
        var getName = function () {
            alert(4)
        }
        function getName() {
            alert(5)
        }

//请写出以下输出结果:
Foo.getName();
getName();
Foo().getName();
getName();
new Foo.getName();
new Foo().getName();
new new Foo().getName();

我们首先根据变量提升来重新排列一下:
要记住 函数表达式的提升会被拆成两部分,声明部分提升,赋值并没有提升
函数声明本身会被提升

所以重排后的顺序为

    function Foo ()  {
        getName = function () {
            alert(1)
        }
        console.log(this) // window
        return this
    }
    
    var getName

    function getName() {
        alert(5)
    }

    Foo.getName = function () {
        alert(2)
    }
    Foo.prototype.getName = function () {
        alert(3)
    }
    getName = function () {
        alert(4)
    }

第一问
Foo.getName();结果显而易见是2

第二问
getName()
这里变量声明提升 然后给getName赋值为

 function () {
        alert(4)
    }

所以答案是4

第三问
Foo().getName();
先调用了Foo函数,执行的过程遇到第一行getName由于在当前函数的作用域内没有找到定义,所以到父级作用域也就是window中找到了getName的声明,然后赋值

 function () {
            alert(1)
        }

将之前的alert(4)给覆盖了
而Foo()调用的结果为return this 这里this指向window
所以是window.getName 答案是1

第四问
getName();
window对象的getName已经被替换成了alert(1) 所以答案是1

第五问
new Foo.getName(); 根据符号的优先级 .优先级大于 New
所以先执行Foo.getName() 答案是2

第六问
new Foo().getName();
括号优先级最大 所以先执行 new Foo() 将Foo作为构造函数了
在构造函数内没有找到getName方法 所以到原型上找 于是答案为3

第七问
new new Foo().getName();由优先级 所以 真正的执行顺序为 new ((new Foo()).getName())
答案同上还是3

34.git管理代码
上传代码
github上创建仓库
打开git bash
cd进入你的项目文件夹
git init 初始化
git add .
git commit -m “注释修改内容”
git remote add origin “建立与远程仓库的连接”
git push -u origin master
输入账号密码就可以push到仓库了
如果这里报了莫名其妙的错误 ssh可能会报错 使用https,但是要清理掉之前的连接 git remove rm origin

下载仓库的代码
cd 进入你想要放置代码的文件夹
git clone https/ssh
如果本地已经有对应仓库的文件夹
直接git pull即可获取最新的代码

git branch xx
git checkout xx 切换到xx分支
git push origin xx 将新的分支提交到仓库
…修改代码…
使用git status可以查看哪个文件被修改
git diff xxx.html 可以查看xxx.html被修改的具体内容 (对txt修改不显示的哦)
依然可以使用git commit -m “添加修改注释”
git push -u origin xx 将修改后的代码提交到xx分支

团队中的流程
git branch dev 创建一个名字叫dev的分支 我们一般不会直接在master上修改代码
git checkout dev 切换到dev分支
代码编写
git add 目录/文件/.(.代表目录下的全部文件)名 编写完添加到本地代码库
git commit -m “注释”
这个时候有一些改变了没有提交的代码会变红色,在你 切换到master之前,应该暂存当前dev的开发一下到栈里。
git stash
一天任务完成
git push origin dev 将本地分支提交到远程仓库

如果需要别人的分支代码
git pull origin 分支名
尽量不要使用rebase 和merge不一样 被合并的分支就没了

如果要继续之前的工作
git stash pop 回到dev开发,要从stash中取出暂存的状态

如果需要合并到master
git checkout master
git merge dev
git push

总结流程如下:
git clone URL
(master branch now)
git branch dev
(new dev bransh to develop)
git checkout dev
(switch dev bransh now)
(…coding …add … delete…modify…)
git add ./src ./res
(just add src and res in the local reposity)
git stash
(push the status into git stack,as some files modified but not add and committed)
git checkout master
git pull
(update the code from server .)
git checkout dev
git rebase master
(rebase from master.)
(solve the conflict code)
git checkout master
git merge dev
(merge the code from dev)
git push
(upload the new code to server)
git checkout dev
git stash pop
(ok, continue coding ,man )

如果不小心在master分支改动了代码怎么办?
git status
(you will the modification is red)
git stash
(temporary store the modification into stack)
git status
(no red file now, as the branch rallback to the latest commit)
git pull
(update the code from the server)
(if you want to save the modification, you need still step on following steps)
git checkout dev
git rebase master
(update the code base on master)
git stash pop
(pop the stored code out)

最常见的冲突是内容冲突:你修改了一个函数的实现,而远程仓库版本跟你的不一样,当主版本和你的修改合并时,这段代码的修改到底听谁的,所以就冲突了。通常出现在git pull与git merge的过程中
冲突文件标识
<<<<< ========= >>>>>>
解决办法 直接修改冲突的文件
当处理完所有冲突后,执行git add与git commit即可。

项目模块依赖管理 使用的npm

35.手写事件模型和事件代理(委托)

IE和W3C不同绑定事件解绑事件的方法有什么区别?
w3c下绑定事件的方法为 element.addEventListener(‘click’,handle, false)
三个参数分别为 事件名,处理函数, 事件是否在捕获或者冒泡阶段执行
ie下的绑定事件方法为 elemetn.attachEvent(‘onclick’, handle)

w3c解绑事件方法为 element.removeEventListener(‘click’, handle, false)
ie element.detachEvent(‘onclick’, handle)

w3c的事件对象和ie的事件对象的区别?
w3c的事件对象e = arguments.callee.caller.arguments[0]
argments.callee就是函数体本身
arguments.callee.caller就是调用这个函数体的另一个函数体
举个例子

function  a(){
    b();
}

function  b(){
    alert(b  ===  arguments.callee) 
    alert(b.caller  ===  a)
    alert(arguments.callee.caller  ===  a)

}
a();
执行结果是三个 true

假如有个点击函数 elem.onclick = function (e) {}
那么arguments.callee.caller就是这个click函数
arguments.calleee.caller.arguments[0] 也就是它的第一个参数 e

ie的事件对象e则是 window.event

事件代理的原理?
利用了事件冒泡

事件代理的优缺点?
优点:
1.大量节省内存,减少事件的注册,例如可以在ul上代理所有li的点击事件
2.可以实现在新增子元素的时候,无需再对其进行事件绑定,对于动态部分来说很实用

缺点:
如果都使用事件代理,有可能会出现事件误判,也就是不该触发事件的元素被绑定了事件

实现一个兼容浏览器的事件代理

<ul id="ul">
    <li>1</li>
    <li>2</li>
    <li>3</li>
</ul>



<script>
    function delegateEvent(elem, selector, type, fn) {
        if (elem.addEventListener) { // 绑定事件
            elem.addEventListener(type, _handleFunc)
        } else {
            elem.attachEvent('on' + type, _handleFunc)
        }

        function _handleFunc(e) { // 处理事件
            var e = e || window.event
            var target = e.target || e.srcElement
            if (matchSelector(target, selector)) { // 判断点击的是不是我们需要代理的元素
                if (fn) {
                    fn.call(target, e)
                }
            }
        }
    }

    function matchSelector(ele, selector) { // ele是触发事件的元素 也就是 target selector是传入的需要代理的元素
        // console.log(ele,selector)  // 打印<li>x<li> 'li'
        
        // if use id
        if (selector.charAt(0) === "#") {
            return ele.id === selector.slice(1)
        }
        // use class
        if (selector.charAt(0) === ".") {
            return (" " + ele.className + " ").indexOf(" " + selector.slice(1) + " ") !== -1
        }
        // use tagName
        return ele.tagName.toLowerCase() === selector.toLowerCase()
    }
    
    var ul = document.getElementById('ul')
    delegateEvent(ul, 'li', 'click', function (e) {
        alert(e.target.innerHTML)
    })
</script>

实现事件模型
即写一个类或是一个模块,有两个函数,一个bind一个trigger,分别实现绑定事件和触发事件,核心需求就是可以对某一个事件名称绑定多个事件响应函数,然后触发这个事件名称时,依次按绑定顺序触发相应的响应函数

实现:创建一个类或是匿名函数,在bind和trigger函数外层作用域创建一个字典对象,用于存储注册的事件及响应函数列表,bind时,如果字典没有则创建一个,key是事件名称,value是数组,里面放着当前注册的响应函

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值