前端工程师必备技能(二)

JS

1、原型链和作用域链

作用域链:
作用域是针对变量的,比如我们创建了一个函数,函数里面又包含了一个函数,那么现在就有三个作用域,
作用域先在自己的变量范围中查找,如果找不到,就会沿着作用域往上找,这个查找的过程就叫作用域链
原型链:
对象之间的继承关系,是通过prototype对象指向父类对象,直到指向Object对象为止,这样就形成了一个原型指向的链条,
专业术语称之为原型链。

原型链解读参考:https://www.jianshu.com/p/dee9f8b14771

2、instanceof

该运算符用来检测一个对象是否在另外一个对象的原型链上
例如:
function C(){}
function D(){}
var o = new C();
o instanceof C; // true,因为 Object.getPrototypeOf(o) === C.prototype
o instanceof D; // false,因为 D.prototype不在o的原型链上
o.constructor == c; // true

2、闭包

概念:
闭包就是能够读取其他函数内部变量的函数
闭包的用途:
一个是前面提到的可以读取函数内部的变量,
另一个就是让这些变量的值始终保持在内存中,不会在f1调用后被自动清除。原因就在于f1是f2的父函数,
而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,
不会在调用结束后,被垃圾回收机制(garbage collection)回收。
使用闭包的注意点:
1、由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的
性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
2、闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,
把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),
这时一定要小心,不要随便改变父函数内部变量的值。

3、内存泄露

什么是内存泄漏:
不再用到的内存,没有及时释放,就叫做内存泄漏(memory leak)
垃圾回收机制:
语言引擎有一张"引用表",保存了内存里面所有的资源(通常是各种值)的引用次数。
如果一个值的引用次数是0,就表示这个值不再用到了,因此可以将这块内存释放。
如果一个值不再需要了,引用数却不为0,垃圾回收机制无法释放这块内存,从而导致内存泄漏。
const arr = [1, 2, 3, 4];
console.log('hello world');
变量arr是仅有的对这个值的引用,因此引用次数为1。尽管后面的代码没有用到arr,它还是会持续占用内存。
arr = null;
上面代码中,arr重置为null,就解除了对[1, 2, 3, 4]的引用,引用次数变成了0,内存就可以释放出来了。
内存泄漏的识别方法:
1、打开开发者工具,选择 Timeline 面板,在顶部的Capture字段里面勾选 Memory,点击左上角的录制按钮,
在页面上进行各种操作,模拟用户的使用情况,一段时间后,点击对话框的 stop 按钮,面板上就会显示这段时间的内存占用情况
常见发生内存泄露情况:
2、令行可以使用 Node 提供的process.memoryUsage方法
1、全局变量引起的内存泄漏
function leaks(){
leak = 'xxxxxx';//leak 成为一个全局变量,不会被回收
}
2、闭包引起的内存泄漏
var leaks = (function(){
var leak = 'xxxxxx';// 被闭包所引用,不会被回收
return function(){
console.log(leak);
}
})()
3、dom清空或删除时,事件未清除导致的内存泄漏
$('#container').bind('click', function(){
console.log('click');
}).off('click').remove();

4、js继承方式

1、原型链继承
Cat.prototype = new Animal();
Cat.prototype.name = 'cat';
var cat = new Cat();
console.log(cat.name);
2、构造继承
3、实例继承
4、拷贝继承
5、组合继承
通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用
function Cat(name){
Animal.call(this);
this.name = name || 'Tom';
}
Cat.prototype = new Animal();

5、apply call bind方法

call:
给第一个参数,就是把函数添加到哪个环境中,改变this参数的指向,可添加多个参数
var a = {
user:"追梦子",
fn:function(){
console.log(this.user); //追梦子
}
}
var b = a.fn;
b.call(a, 1 ,2);
apply:
跟call一样,但是不同的是,第二个参数必须是一个数组,apply和call如果第一个参数是null,则指向window
b.apply(a, [1, 2]);
bind:
跟上面原理一样,返回的是一个函数,参数可以执行的时候再次添加,但是要注意的是,参数是按照形参的顺序进行的。
var c = b.bind(a,10);
c(1,2);

6、JS对象数组浅拷贝和深拷贝

浅拷贝:
var arr = ["One","Two","Three"];
var arrto = arr;
arrto[1] = "test";
document.writeln("数组的原始值:" + arr + "<br />");//Export:数组的原始值:One,test,Three
document.writeln("数组的新值:" + arrto + "<br />");//Export:数组的新值:One,test,Three
深拷贝:
1、slice返回一个数组的一段。(仍为数组)
var arr = ["One","Two","Three"];
var arrtoo = arr.slice(0);
2、concat方法用于连接两个或多个数组
var arr = ["One","Two","Three"];
var arrtooo = arr.concat();
3、属性遍历
var deepCopy= function(source) {
var result={};
for (var key in source) {
result[key] = typeof source[key]===’object’? deepCoyp(source[key]): source[key];
}
return result;
}

7、js设计模式

单例模式:
var Car = new Object;
Car.color = "blue";
Car.door = 4;
Car.showColor = function() {
alert(this.color);
}
Car.showColor(); //"blue";
工厂模式:
function createCar() {
var Car = new Object;
Car.color = "blue";
Car.door = 4;
Car.showColor = function() {
alert(this.color);
}
return Car;
}
var car1 = createCar();
var car2 = createCar();
car1.showColor() //blue;
car2.showColor() //blue;
模块模式:
模块模式的思路是为单体模式添加私有变量和私有方法能够减少全局变量的使用
var singleMode = (function(){
// 创建私有变量
var privateNum = 112;
// 创建私有函数
function privateFunc(){
// 实现自己的业务逻辑代码
}
// 返回一个对象包含公有方法和属性
return {
publicMethod1: publicMethod1,
publicMethod2: publicMethod1
};
})();
代理模式:
本地对象注重的去执行页面上的代码,代理则控制本地对象何时被实例化,何时被使用
// 先申明一个奶茶妹对象
var TeaAndMilkGirl = function(name) {
this.name = name;
};
// 这是京东ceo先生
var Ceo = function(girl) {
this.girl = girl;
// 送结婚礼物 给奶茶妹
this.sendMarriageRing = function(ring) {
console.log("Hi " + this.girl.name + ", ceo送你一个礼物:" + ring);
}
};
// 京东ceo的经纪人是代理,来代替送
var ProxyObj = function(girl){
this.girl = girl;
// 经纪人代理送礼物给奶茶妹
this.sendGift = function(gift) {
// 代理模式负责本体对象实例化
(new Ceo(this.girl)).sendMarriageRing(gift);
}
};
// 初始化
var proxy = new ProxyObj(new TeaAndMilkGirl("奶茶妹"));
proxy.sendGift("结婚戒"); // Hi 奶茶妹, ceo送你一个礼物:结婚戒

8、原生js实现上拉下拉加载效果

//--------------上拉加载更多---------------
//获取滚动条当前的位置
function getScrollTop() {
var scrollTop = 0;
if(document.documentElement && document.documentElement.scrollTop) {
scrollTop = document.documentElement.scrollTop;
} else if(document.body) {
scrollTop = document.body.scrollTop;
}
return scrollTop;
}
//获取当前可视范围的高度
function getClientHeight() {
var clientHeight = 0;
if(document.body.clientHeight && document.documentElement.clientHeight) {
clientHeight = Math.min(document.body.clientHeight, document.documentElement.clientHeight);
} else {
clientHeight = Math.max(document.body.clientHeight, document.documentElement.clientHeight);
}
return clientHeight;
}
//获取文档完整的高度
function getScrollHeight() {
return Math.max(document.body.scrollHeight, document.documentElement.scrollHeight);
}
//滚动事件触发
window.onscroll = function() {
if(getScrollTop() + getClientHeight() == getScrollHeight()) {
34 }
}
//-----------------结束--------------------

9、touch事件,左右滑怎么实现

touchstart,touchmover,touchend
首先touchstart获取点击坐标
startX = e.originalEvent.changedTouches[0].pageX,
startY = e.originalEvent.changedTouches[0].pageY;
再touchend获取点击坐标,然后通过差值判断上下左右滑动
可使用Hammer.js插件调用事件

10、移动端click300ms延迟原理和解决办法

当用户一次点击屏幕之后,浏览器并不能立刻判断用户是要进行双击缩放,还是想要进行单击操作。因此,iOS Safari 就等待 300 毫秒,以判断用户是否再次点击了屏幕。
于是,300 毫秒延迟就这么诞生了。
可使用FastClick.js,tab.js,zepto.js或者直接使用touch事件解决300ms问题

11、事件冒泡和事件捕获

html结构:
<div id="parent">
  <div id="child" class="child"></div>
</div>
document.getElementById("parent").addEventListener("click",function(e){
alert("parent事件被触发,"+this.id);
})
document.getElementById("child").addEventListener("click",function(e){
alert("child事件被触发,"+this.id)
})
child事件被触发,child
parent事件被触发,parent
结论:先child,然后parent。事件的触发顺序自内向外,这就是事件冒泡
document.getElementById("parent").addEventListener("click",function(e){
alert("parent事件被触发,"+e.target.id);
},true)
document.getElementById("child").addEventListener("click",function(e){
alert("child事件被触发,"+e.target.id)
},true)
parent事件被触发,parent
child事件被触发,child
结论:先parent,然后child。事件触发顺序变更为自外向内,这就是事件捕获。
点击事件添加下面方法可以阻止冒泡发生
e.stopPropagation()

12、js作用域

代码例子:
for(var i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
名函数保持对外部变量 i 的引用,此时for循环已经结束, i 的值被修改成了 10,每次打印10
改为:
for(var i = 0; i < 10; i++) {
(function(e) {
setTimeout(function() {
console.log(e);
}, 1000);
})(i);
}
外部的匿名函数会立即执行,并把 i 作为它的参数,此时函数内 e 变量就拥有了 i 的一个拷贝。
当传递给 setTimeout 的匿名函数执行时,它就拥有了对 e 的引用,而这个值是不会被循环改变的。

13、js插件怎么写

基本例子
(function(window,document){
<!-- 内部方法 -->
var MaskShare = function(){
};
<!-- 给对象添加熟悉和方法-->
MaskShare.prototype = {};
<!-- 暴露内部方法给外部 -->
window.MaskShare = MaskShare;
}(window,document));

14、js数组操作常用方法

1、读取数组 for循环
2、合并数组 concat
3、数组变为字符串格式 join
4、删除最后一个元素 pop,shift(删除最开头一个元素)
5、添加最后一个元素 push,unshift(添加最开头一个元素)
6、反转数组 reverse
7、数组排序 sort a-b正向 b-a 反向
var arr1=[3,1,5,8,28]
var arr2=arr1.sort(function(a,b) {
return a-b;
})
console.log(arr2) //[1,3,5,8,28];
8、数组截取
slice(m, n) 截取下标m到n的数组 [n, m) 原数组不变 类似字符串截取substring
splice(m,n,"") 原数组改变,m删除坐标,n是个数,""需要添加的元素 [n, n+m]

15、异步promise和setTImeout区别

setTimeout(function(){
console.log(2);
},0);
new Promise(function(resolve){
console.log(3);
resolve();
console.log(4);
}).then(function(){
console.log(5);
});
console.log(6);
打印出: 3 4 6 5 2

16、ajax原理

Ajax的原理简单来说通过XmlHttpRequest对象来向服务器发异步请求,从服务器获得数据,
然后用javascript来操作DOM而更新页面。

17、CommonJS,AMD与CMD区别

0、CommonJS,定义模块 ,模块输出,加载模块将Node发扬光大
1、AMD推崇依赖前置,在定义模块的时候就要声明其依赖的模块,有个浏览器的实现require.js
2、CMD推崇就近依赖,只有在用到某个模块的时候再去require,有个浏览器的实现sea.js

设备API

1、怎么获取浏览器信息

采用js原生对象 navigator

2、js与原生APP怎么通信

Android与iOS都支持在打开H5页面的时候,向H5页面的window对象上注入一个JavaScript可以访问到的对象,Android端使用的是
webView.addJavascriptInterface(myJavaScriptInterface, “bridge”);
iOS则可以使用JavaScriptCore这个库来完成

3、jsonp原理

请求一个url地址,服务器根据地址,返回一个js变量,也就是一个json串,供当前域名下使用
// 得到航班信息查询结果后的回调函数
var flightHandler = function(data){
alert('你查询的航班结果是:票价 ' + data.price + ' 元,' + '余票 ' + data.tickets + ' 张。');
};
// 提供jsonp服务的url地址(不管是什么类型的地址,最终生成的返回值都是一段javascript代码)
var url = "//flightQuery.com/jsonp/flightResult.aspx?code=CA1998&callback=flightHandler";
// 创建script标签,设置其属性
var script = document.createElement('script');
script.setAttribute('src', url);
// 把script标签加入head,此时调用开始
document.getElementsByTagName('head')[0].appendChild(script);

页面性能

1、计算时间复杂度

时间复杂度概述:
当一个程序产生的时候,就自然而然产生了执行时间,我们不可能每次都去一个一个运行进行比较。
于是一种省时省力的方法产生了,这就是时间复杂度的来源。
时间复杂度计算:
1.找出所有语句的频度并组成执行次数T(n)
2.T(n)的数量级,忽略常量、低次幂和最高次幂的系数,f(n)=T(n)的数量级
3.T(n)=O(f(n))
int num1, num2;
for(int i=0; i<n; i++){
num1 += 1;
for(int j=1; j<=n; j*=2){
num2 += num1;
}
}
1.语句int num1, num2;的频度为1;
语句i=0;的频度为1;
语句i<n; i++; num1+=1; j=1; 的频度为n;
语句j<=n; j=2; num2+=num1;的频度为n*log2(n)*;
(为什么会出现log2(n)呢?是因为循环x次,j=2^x ,当j=n时停止循环,就是2^x=n则有log2(n)=x时停止 ,即循环次数为log2(n)。)
**T(n) = 2 + 4n + 3n*log2n
2.忽略掉T(n)中的常量、低次幂和最高次幂的系数。
f(n) = n*logn
3.代入公式
T(n) =O(f(n))= O(n*logn)
时间复杂度比较:
常数阶O(1), 对数阶O(logn), 线性阶O(n), 线性对数阶O(nlogn), 平方阶O(n^2), 立方阶O(n^3),..., k次方阶O(n^k), 指数阶O(2^n) 。
Ο(1)表示基本语句的执行次数是一个常数。
O(logn)、Ο(n)、Ο(nlog2n)、Ο(n2)和Ο(n3)称为多项式时间。
Ο(2n)和Ο(n!)称为指数时间。(它们的大小和n有关。)

2、页面优化

1、页面级别
 1.减少 HTTP请求数
1>从设计实现层面简化页面
2>合理设置 HTTP缓存
3>资源合并与压缩
4>CSS Sprites
5>data: URL scheme的方式将图片嵌入到页面或 CSS
6>Lazy Load Images
2.将外部脚本置底,减少网络阻塞,将样式和图片提前加载
3.异步执行 inline脚本
4.将 CSS放在 HEAD中
5.减少不必要的 HTTP跳转,可能隐藏301
6.避免重复的资源请求
2、代码级优化
1.减少DOM操作
2.减少作用域链查找
3.CSS选择符尽量浅一点
4.Image压缩

3、浏览器输入url发生了什么

DNS解析
TCP连接
发送HTTP请求
服务器处理请求并返回HTTP报文
浏览器解析渲染页面
连接结束

4、服务器端渲染和浏览器端渲染的区别和特点

服务器端渲染:
页面呈现速度:快,受限于用户的带宽
可维护性:差
seo友好度:好
前端渲染:
页面呈现速度:主要受限于带宽和客户端机器的好坏,优化的好,可以逐步动态展开内容,感觉上会更快一点
可维护性:好,前后端分离,各施其职,代码一目明了
SEO友好度:差
编码效率:高

5、如何提高首屏渲染

1、css内联到页面头部
2、js减少与服务器交互时间,懒执行
3、图片使用懒加载,压缩图片
4、首屏内容做静态缓存

6、前端安全XSS和CRSF是什么

XSS攻击(Cross-Site Scripting)跨站脚本攻击
当应用收到含有不可信的数据,js代码,在没有适当的验证和转义的情况下,就将他发送给一个网页浏览器,就会产生跨站脚本攻击。
CSRF攻击
一个跨站请求伪造攻击迫使登录用户的浏览器将伪造的http请求,包括改用户的会话cookie和其他认证信息,发送到一个存在
漏洞的web应用程序。这就允许了攻击者迫使用户浏览器向存在漏洞的应用程序发送请求,而这些请求会被认为是用户的合法请求。

react

1、React组件间信息传递方式

1.(父组件)向(子组件)传递信息
父组件添加属性xx,子组件通过this.props.xx获取值
2.(父组件)向更深层的(子组件) 进行传递信息 >>利用(context)
父组件添加
childContextTypes: {
color: React.PropTypes.string
},
getChildContext: function() {
return {color: "purple"};
}
组件上不用加属性
子组件定义
contextTypes: {
color: React.PropTypes.string
},
并通过this.context.color获取
3.(子组件)向(父组件)传递信息
父组件绑定callbackParent={this.onChildChanged},在子组件利用this.props.callbackParent(newState),
触发了父级的的this.onChildChanged方法,进而将子组件的数据(newState)传递到了父组件。
4.没有任何嵌套关系的组件之间传值(比如:兄弟组件之间传值)
引用PubSubJS库,但通过在ProductSelection组件中订阅一个消息,在Product组件中又发布了这个消息,使得
两个组件又产生了联系,进行传递的信息。
5.利用react-redux进行组件之间的状态信息共享

2、redux原理

action:
Action是一个对象,用来代表所有会引起状态(state)变化的行为(例如服务端的响应,页面上的用户操作)。
Reducer:
Reducer是一个函数
该函数接收两个参数,一个旧的状态previousState和一个Action对象
返回一个新的状态newState
Store:
维护应用的state内容
提供getState()方法获取 state
提供dispatch(action)方法更新 state
提供subscribe(listener)方法注册监听器
看到Store提供的方法,就可以把Action、Reducer和Store联系在一起了:
Store通过dispatch(action)方法来接收不同的Action
根据Action对象的type和数据信息,Store对象可以通过Reducer函数来更新state的内容
Middleware:
Redux中的Middleware会对特定类型action做一定的转换,所以最后传给reducer的action一定是标准的plain object

3、react-router传值方式有哪些

1、props.params
页面通过
var data = {id:3,name:sam,age:36};
data = JSON.stringify(data);
var path = `/user/${data}`;
<Link to={path}>用户</Link>
hashHistory.push(path);
接收参数:
var data = JSON.parse(this.props.params.data);
var {id,name,age} = data;
2、query,state
使用:
var data = {id:3,name:sam,age:36};
var path = {
pathname:'/user',
query:data,
}
收取:
var data = this.props.location.query;
var {id,name,age} = data;

es6

1、es6 有哪些方法,构造函数有哪些

1、let const
2、箭头函数
3、解构赋值 let [a, b, c] = [1, 2, 3] let { fo, bar } = { fo: 'cc', bar: 'wd' }
4、对象扩展运算符
function cc(...arg) {
console.log(arg[0])
console.log(arg[1])
console.log(arg[2])
console.log(arg[3])
console.log(arg[4])
}
cc(1, 2, 3, 4)
5、字符串模板
let cc = "乘冲"
let blog = `字符串拼接 ${cc},不以物喜不以己悲`
6、JSON数组格式转换
let json = {
'0': 'jspang',
'1': '技术胖',
'2': '大胖逼逼叨',
length:3
}
let arr = Array.from(json);//json字符串转换为数组
7、Array.of()方法:
<!-- 字符串转数组 -->
let arr1 = Array.of(3, 4, 5, 6);
.
.
.