前端经验积累

294 阅读28分钟

判断一个对象是不是数组

  1. instanceof操作符用来判断要检测对象的原型链上是否存在某个构造函数的prototype属性。
var a={};
var b=[];
console.log(a instanceof Object);//true
console.log(b instanceof Array);//true

注意!!!!!!
console.log(b instanceof Object);//true

  1. Array.isArray()

  2. 使用Object.prototype上的原生toString()方法判断。

console.log(Object.prototype.toString.call(a));//[object Object]
console.log(Object.prototype.toString.call(b));//[object Array]
同理判断一个对象是否是函数:
console.log(Object.prototype.toString.call(obj)==='[object Function]')    //true或false
  1. obj.constructor===Array //true

真值 / 假值(Truthy / Falsy)

以下为false

  • false
  • 0
  • ""(空字符串)
  • null
  • undefined
  • NaN
!0 // true -- 0 is false, 所以返回true
!!0 // false -- 0 is falsy so !0 returns true so !(!0) returns false
!!"" // false -- empty string is falsy so NOT (NOT false) equals false

new Boolean(0) // false
new Boolean(1) // true

let a = [] == true // a is false since [].toString() give "" back.
let b = [1] == true // b is true since [1].toString() give "1" back.
let c = [2] == true // c is false since [2].toString() give "2" back.

在内部,当一个对象与布尔值比较时,
比如[] == true,它其实进行的是[].toString() == true

JS语句为什么不能以“function”和大括号开头

  • 以function开头,但必须是一个函数声明语句
  • 以大括号开头,但该大括号不再被当做一个对象处理,而是当做一个语句块处理
  • 综上两条说明,JS语句可以以function,也可以以大括号作为开头,前提是必须符合JS中的语法规范
{a: 'a'}.a;   // Uncaught SyntaxError: Unexpected token .
function(){}.toString();    // Uncaught SyntaxError: Unexpected token (
{}.toString();    // Uncaught SyntaxError: Unexpected token .

“{}.toString();” 等同于: “; .toString();” 未通过对象主体调用“toString”方法,不符合JS中期待的表达式

Object.assign, JSON.stringify是深拷贝吗

不是的话怎么解决

shallowCopy(浅拷贝)或 deepCopy(深拷贝)

深拷贝造成了 CPU 和内存的浪费

深拷贝如果是只有最底层一个元素改变,会造成性能影响,怎么解决? immutable

Object.assign()可以对非嵌套对象进行深拷贝的方法, 如果对象中出现嵌套情况,那么其对被嵌套对象的行为就成了普通的浅拷贝. Object.assign 方法只会拷贝源对象自身的并且可枚举的属性到目标对象。

对象解构运算,也是浅拷贝。

JSON对象中包含两个方法, stringify()和parse(),前者可以将对象JSON化,而后者可以将JSON格式转换为对象.这是一种可以实现深拷贝的方法. 但这种方法的缺陷是会破坏原型链,并且无法拷贝属性值为function的属性 所以如果只是想单纯复制一个嵌套对象,可以使用此方法

缺点是你创建一个临时的,可能很大的字符串,只是为了把它重新放回解析器。另一个缺点是这种方法不能处理循环对象。而且循环对象经常发生。例如,当您构建树状数据结构,其中一个节点引用其父级,而父级又引用其子级。另外,诸如 Map, Set, RegExp, Date, ArrayBuffer 和其他内置类型在进行序列化时会丢失。

const x = {};
const y = {x};
x.y = y; // Cycle: x.y.x.y.x.y.x.y.x...
const copy = JSON.parse(JSON.stringify(x)); // throws!

let bar = JSON.parse(JSON.stringify(foo));
Object.assign({}, foo);

let obj = {
    a: 0,
    b: 20,
}
obj = {...obj, a: obj.a + 1}

Structured Clone 结构化克隆算法
MessageChannel
缺点是它是异步的。虽然这并无大碍,
但是有时候你需要使用同步的方式来深度拷贝一个对象
function structuralClone(obj) {
  return new Promise(resolve => {
    const {port1, port2} = new MessageChannel();
    port2.onmessage = ev => resolve(ev.data);
    port1.postMessage(obj);
  });
}

const obj = /* ... */;
const clone = await structuralClone(obj);

es7 ... 的方式 直接{...obj}赋值属于浅复制,在修改值时{...obj,a:1}就起到了类深复制的效果 更新一个 Object

immutable.js 这个专门处理不变性数据的库(也是facebook出品),它可以使用类似赋值的方式生成浅复制的不变性数据,下面来看看它怎么简化我们的开发

链接

Immutable数据就是一旦创建,就不能更改的数据。每当对Immutable对象进行修改的时候,就会返回一个新的Immutable对象,以此来保证数据的不可变。

为什么immutable比较两个对象不同会快?原因如下: 和js中对象的比较不同,在js中比较两个对象比较的是地址,但是在Immutable中比较的是这个对象hashCode和valueOf,只要两个对象的hashCode相等,值就是相同的,避免了深度遍历,提高了性能。用法:is(map1,map2)

这是原来的 reducer:

case 'apple/EAT_APPLE':
    newState = Object.assign({}, state, {
        apples: [
            ...state.apples.slice(0, action.payload),
            Object.assign({}, state.apples[action.payload], { isEaten: true }),
            ...state.apples.slice(action.payload + 1)
        ]
    })
    return newState;

这是使用 immutable.js 库的reducer :

import { fromJS } from 'immutable';

case 'apple/EAT_APPLE':
    return fromJS(state).setIn(['apples',action.payload,'isEaten'], true).toJS();

团队约定 state 都用 immutable 内部的数据类型,就可以连 fromJS 和 toJS 的转化都省了,超级方便!

JSONP 后端返回alert(data),前端会执行吗

因为后端把回调方法名转成另一个名字将data包在里边,所以不会执行alert。

为什么是jsonp, img, iframe也可以,为什么?

jsonp全名叫做json with padding 函数调用,数据都被包裹传递到参数中了,

将服务端的数据用padding包起来,所谓的padding就相当于一个函数。假设我们原来有数据 {"data","something"},用一个名为callback的padding包起来之后 就成了 callback({"data":"something"}),这样将script标签加载完毕之后,会立马执行这个函数,并将数据当做参数传了进去。如果我们可以事先在前端事先指定一个函数名,并定义这个函数。然后告诉后端,用这个函数名作为padding包裹数据。那么当前端动态创建script标签之后,script加载完毕了就会执行这个函数,这样我们就成功的跨域获得了数据~

从开发者工具里面可以看到实际发送的请求

Request URL:api.douban.com/v2/book/122…

这里可以看出来jQuery自动生成的padding是jsonp1430727166924返回的数据:;jsonp1430727166924({"rating":{"max":10,"numRaters":336,"average":"7.0","min":0},"price":"15.00元"});

js,css执行顺序,并行串行,阻塞

链接 DOM文档的加载顺序是由上而下的顺序加载;

1、DOM加载到link标签

==css文件的加载是与DOM的加载并行的==,也就是说,css在加载时Dom还在继续加载构建,而过程中遇到的css样式或者img,则会向服务器发送一个请求,待资源返回后,将其添加到dom中的相对应位置中;

2、DOM加载到script标签

由于==js文件不会与DOM并行加载==,因此需要等待js整个文件加载完之后才能继续DOM的加载,倘若js脚本文件过大,则可能导致浏览器页面显示滞后,出现“假死”状态,这种效应称之为“阻塞效应”;会导致出现非常不好的用户体验;

js阻塞其他资源的加载的原因是:浏览器为了防止js修改DOM树,需要重新构建DOM树的情况出现;

所有浏览器在下载JS的时候,会阻止一切其他活动,比如其他资源的下载,内容的呈现等等。至到JS下载、解析、执行完毕后才开始继续并行下载其他资源并呈现内容。

嵌入JS会阻塞所有内容的呈现,而外部JS只会阻塞其后内容的显示,2种方式都会阻塞其后资源的下载。

浏览器会维持html中css和js的顺序,样式表必须在嵌入的JS执行前先加载、解析完。而嵌入的JS会阻塞后面的资源加载,所以就会出现上面CSS阻塞下载的情况。

3、解决方法

前提,js是外部脚本;

在script标签中添加 defer=“ture”,则会让js与DOM并行加载,待页面加载完成后再执行js文件,这样则不存在阻塞;

在scirpt标签中添加 async=“ture”,这个属性告诉浏览器该js文件是异步加载执行的,也就是不依赖于其他js和css,也就是说无法保证js文件的加载顺序,但是同样有与DOM并行加载的效果;

同时使用defer和async属性时,defer属性会失效;

可以将scirpt标签放在body标签之后,这样就不会出现加载的冲突了。

进制转换

//十进制转其他
var x=110;  
alert(x);
alert(x.toString(8));  
alert(x.toString(32));  
alert(x.toString(16));  
//其他转十进制
var x='110';
alert(parseInt(x,2));  
alert(parseInt(x,8));  
alert(parseInt(x,16));  
//其他转其他  
//先用parseInt转成十进制再用toString转到目标进制  
alert(String.fromCharCode(parseInt(141,8)))  
alert(parseInt('ff',16).toString(2));  

两个对象判断==

引用下温特大大的总结就是: 只要记住 null 只和undefined 相等, 有 number 都转 number, 有 boolean 也转 number, 有 string 都转 string, 对象互相不等, NaN 互相不等就可以了。

console.log([] == []) // false
console.log(![] == false) // false
console.log([] == false) // true
console.log(!!'hello') // true
console.log('hello' == true) // 'hello'==1  false
console.log(typeof(typeof('hello')))


typeof的运算数未定义,返回的就是 “undefined”.

运算数为数字 typeof(x) = “number”

字符串 typeof(x) = “string”

布尔值 typeof(x) = “boolean”

对象,数组和null typeof(x) = “object”

函数 typeof(x) = “function

FOUC无样式内容闪烁

Flash of Uncompiled Content

原因大致为: 1,使用import方法导入样式表。 2,将样式表放在页面底部 3,有几个样式表,放在html结构的不同位置。

解决方法: 使用LINK标签将样式表放在文档HEAD中。

  1. v-cloak

  2. 用v-text

  3. v-if, v-show

    <span v-text="msg"></span>
    <!-- same as -->
    <span>{{msg}}</span>

    // <div> 不会显示,直到编译结束。
    [v-cloak] {
    display: none;
    }
    <div v-cloak>
    {{ message }}
    </div>

js实现jQuery.clone

逐级递归;

递归中收集每个元素的全部信息,包括该元素上绑定的所有事件。

复制时候重现按照获得的事件列表逐个加载回去。

除此之外主要可能是考虑各种兼容,
包括cloneNode方法的兼容,
获取元素属性时候的某些标签的兼容。

i18n

(其来源是英文单词 internationalization的首末字符i和n,18为中间的字符数)是“国际化”的简称。

ajax参数

链接

1.url: 发送请求的地址
2.type: 请求方式(post或get
3.timeout: 请求超时时间(毫秒)
4.async: 默认设置为true,异步请求
5.cache: 从浏览器缓存中加载请求信息
6.data: 发送到服务器的数据
7.dataType: xml,html, json,text
8.beforeSend:发送请求前,执行一些操作
9.complete:完成后调用的回调函数
10.success:请求成功后调用的回调函数
11.error:
12.contentType:
13.dataFilter:

16.ifModified:



jQuery插件

//闭包限定命名空间
(function ($) {
    $.fn.extend({
        "highLight":function(options){
            //do something
        }
    });
})(window.jQuery);

//调用
$("p").highLight(); //调用自定义 高亮插件

exports, module.exports区别

exports是引用 module.exports的值。module.exports 被改变的时候,exports不会被改变,而模块导出的时候,真正导出的执行是module.exports,而不是exports

exportsmodule.exports 被改变后,失效。

1. module.exports 初始值为一个空对象 {}
2. exports 是指向的 module.exports 的引用
3. require() 返回的是 module.exports 而不是 exports

上传文件方式

  • form表单上传文件

  • 原生js实现ajax上传文件 var xml=new XMLHttpRequest();

    var data=new FormData; //创建formdata对象

    data.append("testfile",document.getElementById("file_upload").files[0]);//找到对象之后的file[0]对应的就是文件对象

    xml.open("POST","/test/",true);

  • jquery实现ajax上传文件

var data=new FormData;
data.append("testfile",document.getElementById("file_upload").files[0]);
$.ajax({
    url:"/test/",
    type:"POST",
    dataType:"JSON",
    data:data,
    contentType: false,
    processData: false,
    success:function(rst){

        })
    })

form+iframe上传文件

Sass

@mixin声明混合,可以传递参数,参数名以$符号开始,多个参数以逗号分开,也可以给参数设置默认值。声明的 @mixin通过 @include来调用。

@mixin通过 @include调用后解析出来的样式是以拷贝形式存在的,而继承则是以联合声明的方式存在的,

//sass style
//-------------------------------
@mixin center-block {
    margin-left:auto;
    margin-right:auto;
}
.demo{
    @include center-block;
}

//css style
//-------------------------------
.demo{
    margin-left:auto;
    margin-right:auto;
}

cookie和session区别

1,session 在服务器端,
    cookie 在客户端(浏览器)
2,session 默认被存在在服务器
    的一个文件里(不是内存)
3,session 的运行依赖 session id,
    而 session id 是存在 cookie 中的,
    也就是说,如果浏览器禁用了 cookie ,
    同时 session 也会失效
    (但是可以通过其它方式实现,
    比如在 url 中传递 session_id)
4,session 可以放在 文件、数据库、或内存中都可以。
5,用户验证这种场合一般会用 session
因此,维持一个会话的核心就是
客户端的唯一标识,即 session id

AMD, CMD, CommonJS

==记忆方法==

AR AMD-- require.js

CS CMD-- sea.js

A AMD A在前,提前加载

C CMD C在后,延迟加载

node的module遵循CommonJS规范,
requirejs遵循AMD,seajs遵循CMD// a.js

// -------- node -----------
module.exports = {
  a : function() {},
  b : 'xxx'
};

// ----------- AMD or CMD ----------------
define(function(require, exports, module){
  module.exports = {
    a : function() {},
    b : 'xxx'
  };
});

AMD"Asynchronous Module Definition"的缩写,意思就是"异步模块定义"。
它采用异步方式加载模块,模块的加载不影响它后面语句的运行。
所有依赖这个模块的语句,都定义在一个回调函数中,
等到加载完成之后,这个回调函数才会运行。


区别:

1. 对于依赖的模块,AMD 是提前执行,CMD 是延迟执行。
不过 RequireJS2.0 开始,
也改成可以延迟执行(根据写法不同,处理方式不同)。
CMD 推崇 as lazy as possible.

2. CMD 推崇依赖就近,AMD 推崇依赖前置。



ES6标准发布后,module成为标准,标准使用是以export指令导出接口,以import引入模块,但是在我们一贯的node模块中,我们依然采用的是CommonJS规范,使用require引入模块,使用module.exports导出接口。

import引入模块

import语法声明用于从已导出的模块、脚本中导入函数、对象、指定文件(或模块)的原始值。

import模块导入与export模块导出功能相对应,也存在两种模块导入方式:命名式导入(名称导入)和默认导入(定义式导入)。

注意:import必须放在文件的最开始.import命令是编译阶段执行的,在代码运行之前,表达式和变量只有在运行时才能得到结果的语法结构。import命令会被 JavaScript 引擎静态分析,先于模块内的其他模块执行(叫做”连接“更合适)所以import中不能含有表达式或者变量,因此无法实现动态加载. 因此,import和export命令只能在模块的顶层,不能在代码块之中(比如,在if代码块之中,或在函数之中)。 这样的设计,有利于编译器提高效率,但也导致无法在运行时加载模块。在语法上,条件加载就不可能实现。如果import命令要取代 Node 的require方法,这就形成了一个障碍。因为require是运行时加载模块,import命令无法取代require的动态加载功能。

ES6 模块与 CommonJS 模块的差异

CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
CommonJS 模块是运行时加载,ES6 模块是编译时输出接口
ES6 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。换句话说,ES6 的import有点像 Unix 系统的“符号连接”,原始值变了,import加载的值也会跟着变。因此,ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。

position取值

ralative是指相对定位
元素仍保持其未定位前的形状,它原本所占的空间仍保留。

absolute是指绝对定位
    即完全离开文档流, 相关于position属性非static值的比来父级元素进行偏移。
    如果不存在这样的父对象,则依据body对象。而其层叠通过z-index属性定义
fixed: 固定定位
    固定定位:即完全离开文档流,相关于视区进行偏移。
static:元素框正常生成
inherit:继承值,对象将继承其父对象相应的值。

类数组转换为数组的方法

Array.prototype.slice.call(arguments);

call和apply, bind区别

这三个参数的返回值区别

作用完全一样,唯一的区别就在参数上

call 接收的参数不固定,
第一个参数是函数体内 this 的指向,
第二个参数以下是依次传入的参数。

apply接收两个参数,
第一个参数也是函数体内 this 的指向。
第二个参数是一个集合对象(数组或者类数组)

记忆方法:

apply ---- array 要传数组
call  ---- 逗号隔开



三个的使用区别:
都是用来改变函数的this对象的指向的;
第一个参数都是this要指向的对象;
都可以利用后续参数传参;
bind是返回对应函数,便于稍后调用,apply、call是立即调用;
bind()--也是改变函数体内this的指向;
bind会创建一个新函数,称为绑定函数,当调用这个函数的时候,绑定函数会以创建它时传入bind()方法的第一个参数作为this,传入bind()方法的第二个及以后的参数加上绑定函数运行时本身的参数按照顺序作为原函数的参数来调用原函数;


let obj1={
    a:222
};
let obj2={
    a:111,
    fn:function(){
        alert(this.a);
    }
}
obj2.fn.call(obj1);//222

call 和 apply 两个主要用途就是

1.改变 this 的指向(把 this 从 obj2 指向到 obj1 )

2.方法借用( obj1 没有 fn ,只是借用 obj2 方法)

箭头函数怎么绑定全局this

箭头函数不绑定this, 它会捕获其所在(即定义的位置)上下文的this值, 作为自己的this值,

var obj = {
  i: 10,
  b: () => console.log(this.i, this),
  c: function() {
    console.log( this.i, this)
  }
}
obj.b();  // undefined window{...}

作为方法的箭头函数this指向全局window对象,
而普通函数则指向调用它的对象

es6新特性

链接

foreach, map, reduce, filter区别

函数声明&函数表达式的区别

函数声明中函数名是必须的;函数表达式中则是可选的
 //函数声明
function sum(a, b) {
    return a + b;
}
alert(sum(1, 2));

//函数表达式
/* var s = function sum(a, b) {
    return a + b;
}
alert(s(1, 2)); */

var s = function(a, b) {
    return a + b;
}
alert(s(1, 2));
//以上两种都可以

二、用函数声明定义的函数,函数可以在函数声明之前调用,而用函数表达式定义的函数只能在声明之后调用。

【根本原因在于解析器对这两种定义方式读取的顺序不同:解析器会事先读取函数声明,即函数声明放在任意位置都可以被调用;

对于函数表达式,解析器只有在读到函数表达式所在那行的时候才执行】

使用var 表达式定义函数, 只有变量声明提前了,变量初始化代码仍然在原来的位置

DOM事件流

事件捕获阶段 处于目标阶段 事件冒泡阶段

addEventListener(evtype,fn,useCapture)
useCapture是true,则事件处理函数在捕获阶段被执行,否则 在冒泡阶段执行

因为如果被监听的元素没有子元素,那么哪个监听代码写在前面,就先执行哪个!

<label>Click me <input type="text"></label>
<script>
    document.querySelector('label').addEventListener('click',function () {
        console.log(1)
    })
    document.querySelector('input').addEventListener('click',function () {
        console.log(2)
    })
</script>

因为label和input是有绑定的
点击label后,浏览器自动帮你再点击一次label
过程就是先进行一次事件机制,这一次对内部input元素的事件监听是不管不问的,所以先打出1
结束后,再进行一次事件机制,这一次,按照正常事件机制流程走,所以接着打出了2,1

阻止冒泡 window.event.cancelBubble = true

e.preventDefault();

return false

都能阻止 stopPropagation()方法既可以阻止事件冒泡,也可以阻止事件捕获,也可以阻止处于目标阶段。 stopImmediatePropagation()方法来阻止事件捕获,另外此方法还可以阻止事件冒泡

git 问题

git回退到某个commit版本

git reset --hard commitId
强制提交
git push -f origin master
删除分支
git branch -d branchname  

git 删除远程分支
git push origin branchname

rebase 和 merge区别

rebase,合并的结果好看,一条线,但合并过程中出现冲突的话,比较麻烦(rebase过程中,一个commit出现冲突,下一个commit也极有可能出现冲突,一次rebase可能要解决多次冲突);merge,合并结果不好看,一堆线交错,但合并有冲突的话,只要解一次就行了;

commit 粒度把握得好,就直接 merge,把握不好,先 rebase 把粒度调整好了,再 merge

let var const 区别

let 的「创建」过程被提升了,但是初始化没有提升。

let在未定义之前使用,会报错

var 的「创建」和「初始化」都被提升了。

function 的「创建」「初始化」和「赋值」都被提升了。


let 声明的变量的作用域是块级的;
let 不能重复声明已存在的变量;
let 有暂时死区,不会被提升。


for( let i = 0; i< 5; i++) 这句话的圆括号之间,有一个隐藏的作用域
for( let i = 0; i< 5; i++) { 循环体 } 在每次执行循环体之前,JS 引擎会把 i 在循环体的上下文中重新声明及初始化一次。

js链接

闭包

链接

闭包就是能够读取其他函数内部变量的函数。

闭包是在某个作用域内定义的函数,它可以访问这个作用域内的所有变量。

在Javascript中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成"定义在一个函数内部的函数"。

所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。

最大用处有两个, 一是可以读取函数内部的变量, (创建局部变量,保护局部变量不会被访问和修改)。 另一个就是让这些变量的值始终保持在内存中。

闭包常见用途:

创建特权方法用于访问控制 事件处理程序及回调

let add=(function(){
let now=0;
return {
 doAdd:function(){
    now++;
    console.log(now);
}
}
})()

add.doAdd() //1
add.doAdd() //2
add.doAdd() //3

now 这个变量,并没有随着函数的执行完毕而被回收,
而是继续保存在内存里面。

由于 add 里面有函数是依赖于 now 这个变量。所以 now 不会被销毁,回收。
由于 now 在外面访问不到

 var name = "The Window";

  var object = {
    name : "My Object",

    getNameFunc : function(){
      return function(){
        return this.name;
      };

    }

  };

  alert(object.getNameFunc()());


// 由于方法里有this.name, 这里的this的是window,所以这里是The Window

var name = "The Window";

  var object = {
    name : "My Object",

    getNameFunc : function(){
      var that = this;
      return function(){
        return that.name;
      };

    }

  };

  alert(object.getNameFunc()());
  
// 这里的that指向object

判断是否为数组

if(typeof Array.isArray==="undefined"){
  Array.isArray = function(arg){
        return Object.prototype.toString.call(arg)==="[object Array]"
    };
}

找出数组中的最大值

//第一种方法
var a=[1,2,3,6,5,4];
var max=Math.max.apply(null,a);
console.log(max);
//第二种方法
var a=[1,2,3,6,5,4];
var max=eval('Math.max('+a.toString()+')');
console.log(max);

Math.max(...[-1, 5, 11, 3])
 Math.max.apply(Math, [-1, 5, 11, 3])

Javascript的this用法

链接 链接2

this是Javascript语言的一个关键字。

它代表函数运行时,自动生成的一个内部对象,只能在函数内部使用。

随着函数使用场合的不同,this的值会发生变化。但是有一个总的原则,那就是this指的是,调用函数的那个对象。


匿名函数,定时器中的函数,由于没有默认的宿主对象,所以默认this指向window

问题: 如果想要在setTimeout/setInterval中使用这个对象的this引用呢?

用一个 变量提前把正确的 this引用保存 起来, 我们通常使用that = this, 或者 _this = this来保存我们需要的this指针!

也可以使用 func.bind(this) 给回调函数直接绑定宿主对象, bind绑定宿主对象后依然返回这个函数, 这是更优雅的做法

var a = 1;
var obj = {
  a: 2,
  test: function() {
    setTimeout(function(){
      console.log(this.a);
    }.bind(this), 0);
  }
};

obj.test();  //  2

// setTimeout不止两个参数
setTimeout(function(a, b){
  console.log(a);   // 3
  console.log(b);   // 4
},0, 3, 4);



箭头函数比较特殊,没有自己的this,它使用封闭执行上下文(函数或是global)的 this 值。

var x=11;
var obj={
 x:22,
 say:()=>{
   console.log(this.x); //this指向window
 }
}
obj.say();// 11
obj.say.call({x:13}) // 11
x = 14
obj.say() // 14
//对比一下
var obj2={
 x:22,
 say() {
   console.log(this.x); //this指向window
 }
}
obj2.say();// 22
obj2.say.call({x:13}) // 13

一、纯粹的函数调用 属于全局性调用,因此this就代表全局对象Global

在严格模式下,默认绑定会将 this 指向 undefined

二、作为对象方法的调用 this就指这个上级对象

三 作为构造函数调用

所谓构造函数,就是通过这个函数生成一个新对象(object)。这时,this就指这个新对象。

四 apply调用

this指的就是这第一个参数。 apply()的参数为空时,默认调用全局对象。

五 this指向绑定事件的dom元素

document.querySelector("#id").onclick =function(){ this == document.querySelector("#id") }



if(!("a" in window)){
    var a = 10;
}
console.log(a); // undefined

// window.hasOwnProperty("a") === true ..
//不用多说了. a 还真存在原型链上
// 值是 Location 这个 API 的数据
var count = 0;

console.log(typeof count === "number"); // true , 这个不用解释了

console.log(!!typeof count === "number"); // false

// 这里涉及到就是优先级和布尔值的问题
// typeof count 就是字符串"number"
// !!是转为布尔值(三目运算符的变种),非空字符串布尔值为 true
// 最后才=== 比较 , true === "number" , return false

(function(){
  var a = b = 3;
})()

console.log(typeof a === "undefined"); // false
console.log(typeof b === "undefined"); // false

// 这里涉及的就是立即执行和闭包的问题,还有变量提升,运算符执行方向(=号自左向右)
// 那个函数可以拆成这样

(function()
  var a; /* 局部变量,外部没法访问*/
  b = 3; /* 全局变量,so . window.b === 3 , 外部可以访问到*/
  a = b;
})()
// 注意a只有在闭包里才能访问(a=3),在外部是undefined  !!!!!!!!!!!!!!!!!


// 若是改成这样,这道题应该是对的
console.log(typeof b === "number" && b ===3
); // true



function foo(something){
  this.a = something;
}

var obj1 = {
  foo:foo
};

var obj2 = {};

obj1.foo(2)

console.log(obj1.a) // 2 ,此时的 this 上下文还在 obj1内,若是 obj1.foo 先保存当做引用再执行传参,则上下文为 window

obj1.foo.call(obj2,3); // 用 call 强行改变上下文为 obj2内
console.log(obj2.a); // 3

var  bar = new obj1.foo(4); // 这里产生了一个实例
console.log(obj1.a); // 2
console.log(bar.a); // 4;  new的绑定比隐式和显式绑定优先级更高

Q: 设计模式你了解多少

http://www.cnblogs.com/tugenhua0707/p/5198407.html

Q: JS 的基本数据类型有哪些

Object
Undefined
Null
Number
Boolean
String
Symbol (ECMAScript 6 新定义)

 5 种原始类型即
 Undefined
 Null
 Number
 Boolean
 String

 除了ObjectSymbol不是,其他都是

Q: null 和 undefined 的差异

大体说一下,想要知其所以然请引擎搜索

相同点:

在 if 判断语句中,值都默认为 false
大体上两者都是代表 无 ,具体看差异
差异:

null 转为数字类型值为0,而 undefined 转为数字类型为 NaN(Not a Number)
undefined 是代表调用一个值而该值却没有赋值,这时候默认则为 undefined
null 是一个很特殊的对象,最为常见的一个用法就是作为参数传入(说明该参数不是对象)
设置为 null 的变量或者对象会被内存收集器回收

Q: 清除浮动的方式有哪些?比较好的是哪一种

常用的一般为三种 .clearfix , clear:both , overflow:hidden ;

.clearfix:after {
  font-size: 0;
  content:"";//设置内容为空
 height:0;//高度为0
 line-height:0;//行高为0
 display:block;//将文本转为块级元素
 visibility:hidden;//将元素隐藏
 clear:both//清除浮动
}

另一种写法

.clearfix:before, .clearfix:after {
 content:"";
 display:table;
}
.clearfix:after{
 clear:both;
 overflow:hidden;
}
.clearfix{
    zoom:1;// 为了兼容IE
}

<!--
用display:table 是为了避免外边距margin重叠导致的margin塌陷,
内部元素默认会成为 table-cell 单元格的形式
-->
clear:both :若是用在同一个容器内相邻元素上,那是贼好的...有时候在容器外就有些问题了, 比如相邻容器的包裹层元素塌陷

overflow:hidden :这种若是用在同个容器内,可以形成 BFC 避免浮动造成的元素塌陷

Q: 跨域问题

详情

我一般用这三种, cors , nginx反向代理 , jsonp

jsonp : 单纯的 get 一些数据,局限性很大...就是利用script标签的src属性来实现跨域。

浏览器支持好
调用失败不会返回各种HTTP状态码
给后端传json格式的数据会报415错误,请求格式不正确
callback添加恶意script标签,造成xss漏洞
只能够实现get请求
参数可见

nginx 反向代理 : 主要就是用了 nginx.conf 内的 proxy_pass http://xxx.xxx.xxx
,会把所有请求代理到那个域名,有利也有弊吧..


cors

可控性较强,需要前后端都设置,兼容性 IE10+ ,
CORS需要浏览器和服务器同时支持。IE8+:IE8/9需要使用XDomainRequest对象来支持CORS。

比如
Access-Control-Allow-Origin: http://foo.example // 子域乃至整个域名或所有域名是否允许访问
Access-Control-Allow-Methods: POST, GET, OPTIONS // 允许那些行为方法
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type // 允许的头部字段
Access-Control-Max-Age: 86400 // 有效期
cros 的配置不仅仅这些,还有其他一些,具体引擎吧....

若是我们要用 nginx 或者 express 配置 cors 应该怎么搞起? 来个简易版本的

nginx
location / {
   # 检查域名后缀
    add_header Access-Control-Allow-Origin xx.xx.com;
    add_header Access-Control-Allow-Methods GET,POST,OPTIONS;
    add_header Access-Control-Allow-Credentials true;
    add_header Access-Control-Allow-Headers DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type;
    add_header Access-Control-Max-Age 86400;
}
express, 当然这货也有一些别人封装好的 cors 中间件,操作性更强...
let express = require('express');  
let app = express();  

//设置所有请求的头部
app.all('*', (req, res, next) =>  {  
    res.header("Access-Control-Allow-Origin", "xx.xx.com");  
    res.header("Access-Control-Allow-Headers", "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type");  
    res.header("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS");  
    next();  
});
有些还会跟你死磕,,除了这些还有其他姿势么...我说了一个HTML5的 postMessage ....

..因为真心没用过,只是以前查阅的时候了解了下..只能大体点下

这货用于 iframe 传递消息居多, 大体有这么两步步

window 打开一个实例,传递一个消息到一个x域名
x 域名下监听 message 事件,获取传递的消息
这货的兼容性没那么好,而且没考虑周全的下容易遭受 CSRF 攻击

Q: 对于XSS 和 CSRF 如何防范

 XSS主要是指跨脚本攻击, 其实就相当于执行js脚本.

XSS的防范

1. 验证用户输入的内容, 是否符合规则.
2. 转义 <> 造成代码直接运行的的标签..
    轮询或者正则替换


而面试官说这种的效率最低下,找相关资料
若是有用到 cookie ,设置为 http-only ,避免客户端的篡改

CSP(Content Security Policy)
以白名单的机制对网站加载或执行的资源起作用。
在网页中,这样的策略通过 HTTP 头信息或者 meta
元素定义。CSP虽然提供了强大的安全保护,
但是他也造成了如下问题:Eval及相关函数被禁用、
内嵌的JavaScript代码将不会执行、
只能通过白名单来加载远程脚本。

CSRF跨域假冒请求

有3个特性: 跨域, cookie, 请求方式.
CSRF的防范一般这几种

验证码,用户体验虽然不好,
验证 HTTP Referer 字段,判断请求来源
token加密解密 ,前端和后台双方协定一个token内容

尽量使用JSON类型传输
    form 传输的格式为:
    Content-Type: application/x-www-form-urlencoded
    而,JSON的传输类型为:
    Content-Type: application/json form
    没有办法去模仿JSON类型进行传输

DNS劫持, 事实上更偏向于User
    developer实际上对这个也无能为力。

HTTP(ISP) 劫持
    使用HTTPS 加密方式传输
    替换你的js的提供商,使用HTTPS路径进行加载。

验证码造成的体验不好,
token 滥用造成的性能问题,轮询替换造成的响应时间等

Q: 谈谈你对 Promise 的理解? 和 ajax 有关系么

链接

promise 是对异步编程的一种抽象。它是一个代理对象,代表一个必须进行异步处理的函数返回的值或抛出的异常。

好处

异步操作队列化,比传统的异步操作回调函数和事件更合理更强大。

能把原来的回调写法分离出来,在异步操作执行完后,用链式调用的方式执行回调函数。

==Promise对象三种状态:==

Pending(进行中) Fulfilled(已完成,又称为Resolved) Rejectd(已失败)

一些需要注意的小点,如下

在 Pending 转为另外两种之一的状态时候,状态不可在改变..

Promise 的 then 为异步.而( new Promise() )构造函数内为同步

Promise 的 catch 不能捕获任意情况的错误(比如 then 里面的 setTimout 内手动抛出一个 Error )

Promise 的 resolve 若是传入值而非函数,会发生值穿透的现象

Promise 还有一些自带的方法,比如 race , all ,前者有任一一个解析完毕就返回,后者所有解析完毕返回...

==Promise 方法:==

then: 用链式调用的方式执行回调函数。通过 resolve 方法把 Promise 的状态置为完成态(Resolved),这时 then 方法就能捕捉到变化,并执行“成功”情况的回调。 then方法返回的是一个新的Promise实例

reject: 把 Promise 的状态置为已失败(Rejected),这时 then 方法执行“失败”情况的回调

catch:和 then 的第二个参数一样,用来指定 reject 的回调 当执行 resolve 的回调(也就是上面 then 中的第一个参数)时,如果抛出异常了(代码出错了),那么也不会报错卡死 js,而是会进到这个 catch 方法中。

all: 提供了并行执行异步操作的能力,并且在所有异步操作执行完后才执行回调。

  • 当该数组里的所有Promise实例都进入Fulfilled状态,Promise.all返回的实例才会变成Fulfilled状态。并将Promise实例数组的所有返回值组成一个数组,传递给Promise.all返回实例的回调函数。

  • 当该数组里的某个Promise实例都进入Rejected状态,Promise.all返回的实例会立即变成Rejected状态。并将第一个rejected的实例返回值传递给Promise.all返回实例的回调函数。

race:只要有一个异步操作执行完毕,就立刻执行 then 回调。 注意:其它没有执行完毕的异步操作仍然会继续执行,而不是停止。

Promise对象两个特点

  1. 对象状态只由异步操作结果决定。resolve方法会使Promise对象由pendding状态变为fulfilled状态;reject方法或者异常会使得Promise对象由pendding状态变为rejected状态。Promise状态变化只有上图这两条路径。

  2. 对象状态一旦改变,任何时候都能得到这个结果。 即状态一旦进入fulfilled或者rejected,promise便不再出现状态变化,同时我们再添加回调会立即得到结果。这点跟事件不一样,事件是发生后再绑定监听,就监听不到了。

  3. Promise构造方法接受一个方法作为参数,该方法传入两个参数,resolve和reject。

  4. resolve用来将Promise对象的状态置为成功,并将异步操作结果value作为参数传给成功回调函数。

  5. reject用来将Promise对象的状态置为失败,并将异步操作错误error作为参数传给失败回调函数。

  6. then方法绑定两个回调函数,第一个用来处理Promise成功状态,第二个用来处理Promise失败状态。


Promise 和 ajax 没有直接关系. promise 只是为了解决"回调地狱"
Promise 并不能消灭回调地狱,但是它可以使回调变得可控。

异步回调的问题:

嵌套层次深,难以维护
无法正常使用returncatch throw
多个回调之间难以建立联系,一个回调函数一旦开启,就无法对其操作
无法正常索引堆栈信息

从表面上看,Promise只是能够简化层层回调的写法,而实质上,Promise的精髓是“状态”,用维护状态、传递状态的方式来使得回调函数能够及时调用,它比传递callback函数要简单、灵活的多。


var promise = new Promise(function(resolve, reject) {
    // ... some code
    if ( /* 异步操作成功 */ ) {
        resolve(value);
    } else {
        reject(error);
    }
});

promise.then(function(value) {
    // success
}, function(error) {
    // failure
});

模拟回调函数
function runAsync(callback){
    setTimeout(function(){
        console.log('执行完成');
        callback('随便什么数据');
    }, 2000);
}

runAsync(function(data){
    console.log(data);
});


getUserAdmin()
    .then(getProjects)
    .then(getModules)
    .then(getInterfaces)
    .then(procResult)

Promise 必知必会(十道题)

链接

Generator 函数

链接

Generator 函数是一个普通函数,但是有两个特征。

一是,function关键字与函数名之间有一个星号;

二是,函数体内部使用yield语句,定义不同的内部状态

function* helloWorldGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}
var hw = helloWorldGenerator();

const gen = function* () {
  const f1 = yield readFile('/etc/fstab');
  const f2 = yield readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};
写成async函数,就是下面这样。

const asyncReadFile = async function () {
  const f1 = await readFile('/etc/fstab');
  const f2 = await readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};
一比较就会发现,async函数就是将 Generator 函数的星号(*)替换成async,将yield替换成await,仅此而已。


调用 Generator 函数,返回一个遍历器对象,代表 Generator 函数的内部指针。
以后,每次调用遍历器对象的next方法,
就会返回一个有着value和done两个属性的对象。
value属性表示当前的内部状态的值,是yield表达式后面那个表达式的值;
done属性是一个布尔值,表示是否遍历结束。

上面已经介绍了说yield是暂停标志,下面对yield进行一些介绍:

(1)、yield后面的表达式只有当调用next时,才会执行yield后面的表达式。

(2)、yield表达式只能写在Generator函数中 ,而不能写在普通的函数中。

(3)、yield表达式如果与其他表达式相结合,必须写在()里面。

yieldreturn的异同点:

相同点:都可以返回紧跟后面表达式的值

不同点:
(a)、在一个函数内部只能存在一个return语句,而可以存在多个yield语句;
(b)、return没有记忆功能,在Generator函数中只有当遍历器对象调用next时
    才会返回yield后面表达式的值,
    当下一次调用时从上一条yield语句后开始执行,
    而return后面的表达式只要函数执行就立即返回。
(c)、正常函数只有一个返回值,而Generator函数可以有多个返回值。


Generator函数被执行时,返回的是指向函数内部的遍历器对象,
只有调用遍历器对象的next方法时,才会返回yield后面函数表达式的值,
但是,其实yield表达式的值一直都是undefined。
如果在Generator的函数中传递参数且存在多个yield表达式时,
那么调用next方法时就要注意传参了:
next()中传递的参数就是上一个yield表达式的返回值。

面试题网站

链接

作用域

函数和变量的可访问范围。
作用域分为全局作用域  函数作用域和eval作用域。

原型

链接

原型其实就是上述所说的继承中的父类。

原型链 :利用原型串起一个继承链,让一个引用类型继承另一个引用类型的属性和方法,再以此类推下去. 当一个引用类型继承另一个引用类型的属性和方法时候就会产生一个原型连。 当某个函数当成构造函数来调用时,就会产生一个构造函数的实例。这个实例上会拥有一个 proto 属性,这个属性指向该实例的构造函数的原型对象(也可以称为该实例的原型对象)。

定律:

每个对象都有 proto 属性,但只有函数对象才有 prototype 属性

原型链是依赖于__proto__,而不是prototype

实例与原型

当读取实例的属性时, 如果找不到,就会查找与对象关联的原型中的属性, 如果还查不到,就去找原型的原型,一直找到最顶层为止。

通过一个构造函数创建出来的多个实例,如果都要添加一个方法,给每个实例去添加并不是一个明智的选择。这时就该用上原型了。

在实例的原型上添加一个方法,这个原型的所有实例便都有了这个方法。

var A = new Person(); Person.prototype = A;

原型对象(Person.prototype)是 构造函数(Person)的一个实例。↓

Person.prototype.constructor == Person
person1.proto == Person.prototype

实例的构造函数属性(constructor)指向构造函数。↓

person1.constructor == Person
Object.prototype.proto === null
person.constructor === Person.prototype.constructor == Person
obj.proto == Object.getPrototypeOf(obj)

原型和原型链是JS实现继承的一种模型。 原型链的形成是真正是靠__proto__ 而非prototype

什么是原型链

由于__proto__是任何对象都有的属性,而js里万物皆对象, 所以会形成一条__proto__连起来的链条,递归访问__proto__最终到头,并且值是null

当js引擎查找对象属性时,先查找对象本身是否存在该属性, 如果不存在,会在原型链上查找,但不会查找自身的prototype

   var A = function(){};
   var a = new A();
   console.log(a.__proto__); //A {}(即构造器function A 的原型对象)
   console.log(a.__proto__.__proto__); //Object {}(即构造器function Object 的原型对象)
   console.log(a.__proto__.__proto__.__proto__); //null

目录

actions
assets
components
reducers
store
views
index.js



prototype问题

function Foo() {
    getName = function () { alert (1); };
    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();




//答案:
Foo.getName();//2
getName();//4
Foo().getName();//1
getName();//1
new Foo.getName();//2
new Foo().getName();//3
new new Foo().getName();//3

var和函数的提前声明

function fn(a) {
  console.log(a);
  var a = 2;
  function a() {}
  console.log(a);
}

fn(1);
  输出:function  a() {} 2
  
  
我们知道varfunction是会提前声明的,
而且function是优先于var声明的(如果同时存在的话),
所以提前声明后输出的a是个function,
然后代码往下执行a进行重新赋值了,故第二次输出是2http://www.cnblogs.com/zichi/p/4359786.html

javascript实现将多个有序数组合并为一个有序数组的算法

链接

let ret=arr.reduce((arr1,arr2)=>arr1.concat(arr2)).sort((a,b)=>a-b);
ret=Array.from(new Set(ret));
console.log(ret);

new操作符具体干了什么呢

1、创建一个新对象,即{} 2、链接该对象(设置该对象的构造函数)到另一个对象 3、将步骤一新创建的对象作为this的上下文 4、返回新对象

function create() {
    // 1.获取构造函数,同时删除arguments的第一个参数
    let Con = [].shift.call(arguments);
    // 2. 创建一个空对象,并链接到原型,obj可以访问构造函数原型中的属性
    let obj = Object.create(Con.prototype);
    // 3. 绑定this实现继承,obj可以访问到构造函数中的属性
    let ret = Con.apply(obj, arguments);
    // 4. 优先返回构造函数返回的对象
    return ret instanceof Object ? ret : obj;
}

// ES6写法

function create(Parent, ...rest) {
    
    // 1.以构造器Parent的prototype为原型创建新对象
    const child = Object.create(Parent.prototype);
    // 2. 将this和调用参数传给构造器执行
    const result = Parent.apply(child, rest);
    return typeof result === 'object' ? result : child;
}

怎么画一条0.5px的边

链接:

可以通过直接设置宽高border0.5px、
设置box-shadow的垂直方向的偏移量为0.5px、
借助线性渐变linear-gradient、
使用transform: scaleY(0.5)的方法,
使用SVG的方法。
最后发现transfrom scale/svg的方法兼容性和效果都是最好的,
svg可以支持复杂的图形,
所以在viewport是1的情况下,
可以使用transform/SVG画0.5px,
而如果viewport的缩放比例不是1的话,那么直接画1px即可。

.hairline-border {
  box-shadow: 0 0 0 1px;
}
@media (min-resolution: 2dppx) {
  .hairline-border {
    box-shadow: 0 0 0 0.5px;
  }
}
@media (min-resolution: 3dppx) {
  .hairline-border {
    box-shadow: 0 0 0 0.33333333px;
  }
}
@media (min-resolution: 4dppx) {
  .hairline-border {
    box-shadow: 0 0 0 0.25px;
  }
}

从 arguments 到剩余参数

如果你想在 ES5 中让函数(或方法)接受任意数量的参数,必须使用特殊变量 arguments:

function logAllArguments() {
    for (var i=0; i < arguments.length; i++) {
        console.log(arguments[i]);
    }
}

ES6 中则可以通过 ... 运算符定义一个剩余参数(在下面示例中是args):

function logAllArguments(...args) {
    for (const arg of args) {
        console.log(arg);
    }
}

如果有一部分固定参数,剩余参数就更适用了:

function format(pattern, ...args) {
    ···
}

在 ES5 中处理同样的事情有点麻烦:

function format(pattern) {
    var args = [].slice.call(arguments, 1);
    ···
}

从浏览器地址栏输入url到显示页面的步骤(以HTTP为例)

连接

  1. 输入url
  2. 查看缓存
  3. 解析URL
  4. 组装HTTP请求报文
  5. 获取主机ip
  6. 建立tcp连接
  7. 发送http请求
  8. 服务器检查请求头信息
  9. 响应报文通过tcp返回
  10. 关闭tcp四次握手
  11. 检查状态码
  12. 缓存
  13. 解码
  14. 解析HTML文档,构件DOM树,下载资源,构造CSSOM树,执行js脚本
  15. 显示页面
在浏览器地址栏输入URL
浏览器查看缓存,如果请求资源在缓存中并且新鲜,跳转到转码步骤
如果资源未缓存,发起新请求
如果已缓存,检验是否足够新鲜,
足够新鲜直接提供给客户端,否则与服务器进行验证。
检验新鲜通常有两个HTTP头进行控制ExpiresCache-ControlHTTP1.0提供Expires,
值为一个绝对时间表示缓存新鲜日期
HTTP1.1增加了Cache-Control: max-age,
值为以秒为单位的最大新鲜时间
浏览器解析URL获取协议,主机,端口,path
浏览器组装一个HTTPGET)请求报文
浏览器获取主机ip地址,过程如下:
浏览器缓存
本机缓存
hosts文件
路由器缓存
ISP DNS缓存

DNS递归查询(可能存在负载均衡导致每次IP不一样)
打开一个socket与目标IP地址,端口建立TCP链接,
三次握手如下:
客户端发送一个TCPSYN=1Seq=X的包到服务器端口
服务器发回SYN=1ACK=X+1Seq=Y的响应包
客户端发送ACK=Y+1Seq=Z
TCP链接建立后发送HTTP请求

服务器接受请求并解析,将请求转发到服务程序,
如虚拟主机使用HTTP Host头部判断请求的服务程序

服务器检查HTTP请求头是否包含缓存验证信息如果验证缓存新鲜,
返回304等对应状态码
处理程序读取完整请求并准备HTTP响应,可能需要查询数据库等操作
服务器将响应报文通过TCP连接发送回浏览器
浏览器接收HTTP响应,然后根据情况选择关闭TCP连接或者保留重用,
关闭TCP连接的四次握手如下:
主动方发送Fin=1Ack=Z, Seq= X报文
被动方发送ACK=X+1Seq=Z报文
被动方发送Fin=1ACK=X, Seq=Y报文
主动方发送ACK=Y, Seq=X报文

浏览器检查响应状态吗:是否为1XX,3XX, 4XX, 5XX,
这些情况处理与2XX不同
如果资源可缓存,进行缓存
对响应进行解码(例如gzip压缩)

根据资源类型决定如何处理(假设资源为HTML文档)

解析HTML文档,构件DOM树,下载资源,构造CSSOM树,
执行js脚本,这些操作没有严格的先后顺序,以下分别解释
构建DOM树:
Tokenizing:根据HTML规范将字符流解析为标记
Lexing:词法分析将标记转换为对象并定义属性和规则
DOM construction:根据HTML标记关系将对象组成DOM树
解析过程中遇到图片、样式表、js文件,启动下载
构建CSSOM树:
Tokenizing:字符流转换为标记流
Node:根据标记创建节点
CSSOM:节点创建CSSOM树

根据DOM树和CSSOM树构建渲染树:
从DOM树的根节点遍历所有可见节点,
不可见节点包括:
1)script,meta这样本身不可见的标签。
2)被css隐藏的节点,如display: none
对每一个可见节点,找到恰当的CSSOM规则并应用
发布可视节点的内容和计算样式
js解析如下:
浏览器创建Document对象并解析HTML,
将解析到的元素和文本节点添加到文档中,
此时document.readystate为loading

HTML解析器遇到没有async和defer的script时,
将他们添加到文档中,然后执行行内或外部脚本。
这些脚本会同步执行,并且在脚本下载和执行时解析器会暂停。
这样就可以用document.write()把文本插入到输入流中。
同步脚本经常简单定义函数和注册事件处理程序,
他们可以遍历和操作script和他们之前的文档内容
当解析器遇到设置了async属性的script时,开始下载脚本并继续解析文档。
脚本会在它下载完成后尽快执行,但是解析器不会停下来等它下载。
异步脚本禁止使用document.write(),它们可以访问自己script和之前的文档元素

当文档完成解析,document.readState变成interactive
所有defer脚本会按照在文档出现的顺序执行,延迟脚本能访问完整文档树,
禁止使用document.write()
浏览器在Document对象上触发DOMContentLoaded事件
此时文档完全解析完成,浏览器可能还在等待如图片等内容加载,
等这些内容完成载入并且所有异步脚本完成载入和执行,
document.readState变为complete,window触发load事件
显示页面(HTML解析过程中会逐步显示页面)


# 浏览器渲染主要流程
HTML解析出DOM Tree
CSS解析出Style Rules
将二者关联生成Render Tree
Layout 根据Render Tree计算每个节点的信息
Painting 根据计算好的信息绘制整个页面

Q: 网站性能优化

原文

代码层面优化:

一、加载和执行

    css方面

    将样式表放到页面顶部
    不使用CSS表达式
    使用link不使用@import
    不使用IE的Filter

    Javascript方面

    将脚本放到页面底部,body标签内底部
    将javascript和css从外部引入
    压缩javascript和css
    删除不需要的脚本
    减少DOM访问
    合理设计事件监听器


    图片方面

    优化图片:根据实际颜色需要选择色深、压缩
    优化css精灵
    合并一些小图片( css sprite )压缩图片
    图片转dataUrl
    不要在HTML中拉伸图片
    保证favicon.ico小并且可缓存
    图片编码优化

二、 数据存取
    - 尽量使用字面量和局部变量
        (局部变量在方法运行过后会自行释放,用完手动置为null或undefined也行),
        减少使用对象和数组,

三、 DOM编程**(常见的性能瓶颈)
    - 最坏的方式就是在循环中操作或者访问DOM,非常消耗性能。
    减少DOM数量
    - 遍历dom
        querySelectorAll()是获取元素最快的API 返回的是一个NodeList
        querySelector() 返回的是element,
        querySelectorAll()还有一点就是可以同时获取两类元素

    - 重绘和重排都是代价昂贵;尽量减少
        重排何时发生:
        1.添加或删除可见DOM元素
        2.元素位置改变
        3.元素尺寸改变(内外边距、边框厚宽高等)
        4.内容改变 (内容导致尺寸变化的时候)
        5.页面渲染器初始化
        6.浏览器窗口尺寸变化

四、 算法和流程控制
    - 循环
        (当循环体复杂度为X时,优化方案优先减少循环体的复杂度,
        循环体复杂度大于X时,优化方案优先减少迭代次数 )
    - 条件语句
        当条件较少时 使用if-else更易读,
        而当条件较多时if-else性能负担比switch大,易读性也没switch好。
        优化if-else的方法是:尽可能的把可能出现的条件放在首位,

五、 字符串和正则表达式
    - 字符串
        join是比较快的,也是大量字符串拼接的唯一高效方式

六、 快速响应的用户界面
    - 浏览器UI线程
        浏览器限制JavaScript任务的运行时间,限制两分钟,
        可以防止恶意代码不断执行来锁定你的浏览器

        单个JavaScript操作的花费总时间应该小于等于100ms,
        这就意味着在100ms内响应用户的操作,不然就会让用户感受到迟钝感

    - 定时器让出时间片断
        使用时间戳计算获得程序运行时间,
        以便快速找到运行时间较长的代码部分进行优化

七、 Ajax
    - 数据传输
        数据的传输同样影响性能
    - 数据格式
    - Ajax性能
        避免不必要的请求:
        使Ajax可缓存:服务端设置HTTP头信息确保响应会被浏览器缓存
        客户端讲获取的信息存到本地避免再次请求
        (localstorage sessionstorage cookice)
        设置HTTP头信息,expiresgaosu告诉浏览器缓存多久
        减少HTTP请求,合并css、js、图片资源文件等或使用MXHR
        通过次要文件用Ajax获取可缩短页面加载时间

        减小cookie大小
        引入资源的域名不要包含cookie

八、编程实践
    - 避免双重求值
        eval()、Function慎用,
        定时器第一个参数建议函数而不是字符串都能避免字符串双重求值
    - 使用对象或者数组直接量
    - 避免重复工作
        A:延迟加载(懒加载)
        B:条件预加载
    - 使用JavaScript速度快的部分
        A.位操作
        B.原生方法,首先原生方法是最快的,而且浏览器会缓存部分原生方法
        C.复杂计算时多使用Math对象
        D.querySelector和querySelectorAll是查询最快的

 九、 构建并部署高性能JavaScript应用
    1.合并多个js文件
    2.预处理js文件
    3.js压缩
    4.js的HTTP压缩
    5.缓存js文件
    6.处理缓存问题
    7.使用内容分发网络(CDN)


移动方面

    保证组件小于25k
    Pack Components into a Multipart Document

    content方面

    按需加载资源
    非必须组件延迟加载
    未来所需组件预加载
        在网站 HTML 中的链接属性上增加
        rel=”prefetch”,rel=”dns-prefetch”,或者 rel=”prerender” 标记。

    将资源放到不同的域下:浏览器同时从一个域下载资源的数目有限,增加域可以提高并行下载量
    减少iframe数量
    不要404

    Server方面

    使用CDN
    减少DNS查询:DNS查询完成之前浏览器不能从这个主机下载任何任何文件。
    方法:DNS缓存、将资源分布到恰当数量的主机名,平衡并行下载和DNS查询
    避免重定向:多余的中间访问
    用dns-prefetch

    <link rel="dns-prefetch" href="//mat1.gtimg.com">

    动静分离
    使用nginx的反向代理,对静态资源的请求直接nginx处理,或放到CDN
    动态请求转发给tomcat处理

    添加Expires或者Cache-Control响应头
    对组件使用Gzip压缩
        Brotli 是一个比较新的文件压缩算法,目前正变得越来越受欢迎。
    配置ETag
    Flush Buffer Early
    Ajax使用GET进行请求
    避免空src的img标签


十、 工具


    若是打包的代码尽可能切割成多个 chunk ,减少单一 chunk 过大
    HTTP的缓存头使用的合理
    减小第三方库的依赖
    对于代码应该考虑性能来编写,比如使用 requestAnimationFrame 绘制动画,尽可能减少页面重绘(DOM 改变)
    渐进升级,引入 preload 这些预加载资源
    看情况用 server worker 来缓存资源(比如移动端打算搞 PWA)
    比如从服务端着手:

    带宽,域名解析, 多域名解析等
    使用负载均衡方案 多节点部署
    页面做服务端渲染,减小对浏览器的依赖(不用客户端解析)
    渐进升级,比如引入 HTTP2(多路复用,头部压缩这些可以明显加快加载速度)
    当然,这是这些都是很片面的点到...实际工作中去开展要复杂的多;


    使用索引加速数据库查询
    页面静态化CMS



多个维度考虑,优化 DOM 绘制时间,资源加载时间,域名解析这些;

要全面的优化一个项目是一个大工程...

优雅降级和渐进增强

优雅降级:
Web站点在所有新式浏览器中都能正常工作,
如果用户使用的是老式浏览器,
则代码会检查以确认它们是否能正常工作。
针对不同版本的hack为那些无法支持功能的
浏览器增加候选方案,
使之在旧式浏览器上以某种形式降级体验
却不至于完全失效.

渐进增强:
从被所有浏览器支持的基本功能开始,
逐步地添加那些只有新式浏览器才支持的功能,
向页面增加无害于基础浏览器的
额外样式和功能的。
当浏览器支持时,
它们会自动地呈现出来并发挥作用。

保证所有人都能访问页面的基本内容和功能
同时为高级浏览器和高带宽用户提供
更好的用户体验


js控制css3动画

开始事件 AnimationStart
结束事件 AnimationEnd
重复运动事件 AnimationIteration  每次开始动画迭代都触发animationiteration


W3c标准:animationstart animationiteration animationend
Webkit:webkitAnimationStart webkitAnimationIteration webkitAnimationEnd
Firefox:animationstart animationiteration animationend
Opera:animationstart animationiteration animationend
IE10MSAnimationStart MSAnimationIteration MSAnimationEnd

var e = document.getElementById("left1");  

e.addEventListener("animationend", function() {  
    alert('css3运动结束!');  
});  

Object循环key

  • for...in循环:只遍历对象自身的和继承的可枚举的属性。 通常用for in来遍历对象的键名 for in更适合遍历对象,不要使用for in遍历数组。

for in 循环会把数组其他扩展方法也循环

  • for of遍历的只是数组内的元素,而不包括数组的原型属性method和索引name

for-of循环是遍历实现iterator接口的成员

只要是一个对象部署了Symbol.interator接口,就可以用for...of遍历该对象,同时也可以调用该接口的Symbol.interator方法调用next()方法对对象进行遍历,不同的是for..of是对该对象的值的输出,而next()返回的是对象。

在ES6中,有三类数据结构原生具备Iterator接口:数组、某些类似数组的对象、Set和Map结构。

  • Object.keys():返回对象自身的所有可枚举的属性的键名。

  • JSON.stringify():只串行化对象自身的可枚举的属性。

  • Object.assign(): 忽略enumerable为false的属性,只拷贝对象自身的可枚举的属性。

  • ==for...in循环出的是key,for...of循环出的是value==

  • ==for...of不能循环普通的对象,需要通过和Object.keys()搭配使用==

如果实在想用for...of来遍历普通对象的属性的话,可以通过和Object.keys()搭配使用,先获取对象的所有key的数组 然后遍历:

结论

  1. 推荐在循环对象属性的时候,使用for...in,在遍历数组的时候的时候使用for...of。

  2. for...in循环出的是key,for...of循环出的是value

  3. 注意,for...of是ES6新引入的特性。修复了ES5引入的for...in的不足

  4. for...of不能循环普通的对象,需要通过和Object.keys()搭配使用

记忆jueqiao :

已核对 偶数v

in h(k)对象 o 数组 value

如果实在想用for...of来遍历普通对象的属性的话,可以通过和Object.keys()搭配使用,先获取对象的所有key的数组
然后遍历:

var student={
    name:'wujunchuan',
    age:22,
    locate:{
    country:'china',
    city:'xiamen',
    school:'XMUT'
    }
}
for(var key of Object.keys(student)){
    //使用Object.keys()方法获取对象key的数组
    console.log(key+": "+student[key]);
}

for in 可以遍历到myObject的原型方法method,
如果不想遍历原型方法和属性的话,
可以在循环内部判断一下,
hasOwnPropery方法可以判断
某属性是否是该对象的实例属性

for (var key in myObject) {
  if(myObject.hasOwnProperty(key)){
    console.log(key);
  }
}