JavaScript基础知识汇总

1、原型/原型链/构造函数/实例/继承

  js万物皆对象,用  var a={} 或   var a = new Object() 或者用构造函数的形式:  var a = new A() 建立一个对象时,该对象不只能够访问它自身的属性,还会根据  __proto__ 属性找到它原型链上的属性,直到找到  Object 上面的  null 。

  每一个函数都有 prototype  属性,除了  Function.prototype.bind() ,该属性指向原型。javascript

  每一个对象都有 __proto__  属性,指向了建立该对象的构造函数的原型。其实这个属性指向了  [[prototype]] ,可是  [[prototype]]  是内部属性,咱们并不能访问到,因此使用  __proto__  来访问。php

  对象能够经过 __proto__  来寻找不属于该对象的属性, __proto__  将对象链接起来组成了原型链。css

  实例指的就是实例对象,而实例对象能够经过构造函数建立。实例对象自己就有着__proto__属性,实例对象的__proto__属性指向原型对象。html

  构造函数与通常函数的区别在于构造函数是用于建立实例对象来使用的,因此构造函数通常都是带有new运算符的函数。构造函数有着全部函数都有的属性:prototype。构造函数的prototype属性指向原型对象。前端

  原型对象是由构造函数的prototype属性和这个构造函数建立的实例对象的__proto__属性共同指向的一个原型链上的对象。若是要判断一个构造函数与实例对象是否有着共同指向的原型对象,能够使用instanceof 来判断,具体用法是 实例对象 instanceof 构造函数。好比引用上面构造函数建立实例对象的例子:obj2 instanceof people,结果返回true。java

  原型对象顾名思义它也是一个对象,因此它也有对象的__proto__属性,那原型对象的__proto__属性也一样地会指向它上一层的原型对象,顺着下去,原型对象的原型对象可能还有它的上一层原型对象,这样一直到Object.prototype这个原型对象为止,这就是整个原型链。node

  instanceof不只仅判断实例对象与构造函数是否有着一样指向,实际上,但凡在这个实例对象的原型链上的构造函数与对象之间,使用instanceof来判断都会返回true,因此若是要找到实例对象是直接由哪一个构造函数建立的,使用instanceof不可行,这能够使用constructor来替代。好比 obj2 constructor people 就是返回true。android

建立对象的几种方法:

1. 工厂模式

function createPerson(name, age, job) {
    let o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function () {
        console.log(this.name);
    }
    return o;
}
let person1 = createPerson("Nicholas", 29, "Software Engineer");
let person2 = createPerson("Greg", 27, "Doctor");
工厂模式虽然解决了建立多个类似对象的问题,但却没有解决对象识别的问题(即怎样知道一个对象的类型)

2. 构造函数模式

function Person(name,age,job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function () {
        console.log(this.name);
    }
}
let person1 = createPerson("Nicholas", 29, "Software Engineer");
let person2 = createPerson("Greg", 27, "Doctor");

使用构造函数的主要问题,就是每一个方法都要在每一个实例上从新建立一遍 (person1.sayName !== person2.sayName)面试

3. 原型模式

function Person(){
}

Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
    alert(this.name);
};

var person1 = new Person();
person1.sayName();   //"Nicholas"

var person2 = new Person();
person2.sayName();   //"Nicholas"

alert(person1.sayName == person2.sayName);  //true

咱们建立的每一个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含能够由特定类型的全部实例共享的属性和方法,可是原型中全部属性是被实例共享的, 引用类型的属性会出问题。ajax

3. 组合使用构造函数模式和原型模式

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.friends = ["Shelby", "Court"];
}
Person.prototype = {
    constructor : Person,
    sayName : function(){
        console.log(this.name);
    }
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");

person1.friends.push("Van");
console.log(person1.friends);    //"Shelby,Count,Van"
console.log(person2.friends);    //"Shelby,Count"
console.log(person1.friends === person2.friends);    //false
console.log(person1.sayName === person2.sayName);    //true

建立自定义类型的最多见方式,就是组合使用构造函数模式与原型模式。构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。结果,每一个实例都会有本身的一份实例属性的副本,但同时又共享着对方法的引用,最大限度地节省了内存。在这个例子中,实例属性都是在构造函数中定义的,而由全部实例共享的属性constructor和方法sayName()则是在原型中定义的。而修改了person1.friends(向其中添加一个新字符串),并不会影响到person2.friends,由于它们分别引用了不一样的数组。 这种构造函数与原型混成的模式,是目前在ECMAScript中使用最普遍、认同度最高的一种建立自定义类型的方法。能够说,这是用来定义引用类型的一种默认模式。

4. 动态原型模式

function Person(name, age, job){

    //属性
    this.name = name;
    this.age = age;
    this.job = job;
    if (typeof this.sayName != "function"){
        console.log(1);
        Person.prototype.sayName = function(){
            console.log(this.name);
        };
    }
}
var person1 = new Person("Nicholas", 29, "Software Engineer"); //1
var person2 = new Person("Greg", 27, "Doctor");

person1.sayName();
person2.sayName();

把全部信息都封装在了构造函数中,而经过在构造函数中初始化原型(仅在必要的状况下),又保持了同时使用构造函数和原型的优势。换句话说,能够经过检查某个应该存在的方法是否有效,来决定是否须要初始化原型。这里只在sayName()方法不存在的状况下,才会将它添加到原型中。这段代码只会在初次调用构造函数时才会执行。此后,原型已经完成初始化,不须要再作什么修改了。不过要记住,这里对原型所作的修改,可以当即在全部实例中获得反映。所以,这种方法确实能够说很是完美其中,if语句检查的能够是初始化以后应该存在的任何属性或方法——没必要用一大堆if语句检查每一个属性和每一个方法;只要检查其中一个便可。对于采用这种模式建立的对象,还能够使用instanceof操做符肯定它的类型。

本部分参考博主连接:http://www.javashuo.com/article/p-wvzbwgee-t.html

2、有几种方式能够实现继承

1. 原型链继承

// 定义一个动物类
function Animal (name) {
  // 属性
  this.name = name || 'Animal'; // 实例方法 this.sleep = function(){ console.log(this.name + '正在睡觉!'); } } // 原型方法 Animal.prototype.eat = function(food) { console.log(this.name + '正在吃:' + food); }; --原型链继承 function Cat(){ } Cat.prototype = new Animal(); Cat.prototype.name = 'cat'; // Test Code var cat = new Cat(); console.log(cat.name);
  • 特色:基于原型链,既是父类的实例,也是子类的实例
  • 缺点:没法实现多继承

2. 构造函数继承

function Cat(name){
  Animal.call(this); this.name = name || 'Tom'; } // Test Code var cat = new Cat(); console.log(cat.name);
  • 特色:能够实现多继承
  • 缺点:只能继承父类实例的属性和方法,不能继承原型上的属性和方法。

3. 组合继承(1和2的组合)

function Cat(name){
  Animal.call(this); this.name = name || 'Tom'; } Cat.prototype = new Animal(); Cat.prototype.constructor = Cat; // Test Code var cat = new Cat(); console.log(cat.name);
  • 特色:能够继承实例属性/方法,也能够继承原型属性/方法
  • 缺点:调用了两次父类构造函数,生成了两份实例

4. 寄生组合继承

组合继承最大的问题就是不管什么状况下,都会调用两次父类构造函数: 一次是在建立子类型原型的时候, 另外一次是在子类型构造函数内部. 寄生组合式继承就是为了下降调用父类构造函数的开销而出现的.

其背后的基本思路是: 没必要为了指定子类型的原型而调用超类型的构造函数

function extend(subClass, superClass) {
  subClass.prototype = superClass.prototype; subClass.superclass = superClass.prototype; if(superClass.prototype.constructor == Object.prototype.constructor) { superClass.prototype.constructor = superClass; } } function Father(name){ this.name = name; this.colors = ["red","blue","green"]; } Father.prototype.sayName = function(){ alert(this.name); }; function Son(name,age){ Father.call(this,name);//继承实例属性,第一次调用Father() this.age = age; } extend(Son,Father)//继承父类方法,此处并不会第二次调用Father() Son.prototype.sayAge = function(){ alert(this.age); } var instance1 = new Son("louis",5); instance1.colors.push("black"); console.log(instance1.colors);//"red,blue,green,black" instance1.sayName();//louis instance1.sayAge();//5 var instance1 = new Son("zhai",10); console.log(instance1.colors);//"red,blue,green" instance1.sayName();//zhai instance1.sayAge();//10

补充:new 操做符

为了追本溯源, 我顺便研究了new运算符具体干了什么?发现其实很简单,就干了三件事情.

var obj  = {};
obj.__proto__ = F.prototype; F.call(obj);

第一行,咱们建立了一个空对象obj;

第二行,咱们将这个空对象的__proto__成员指向了F函数对象prototype成员对象;

第三行,咱们将F函数对象的this指针替换成obj,而后再调用F函数.

咱们能够这么理解: 以 new 操做符调用构造函数的时候,函数内部实际上发生如下变化:

一、建立一个空对象,而且 this 变量引用该对象,同时还继承了该函数的原型。

二、属性和方法被加入到 this 引用的对象中。

三、新建立的对象由 this 所引用,而且最后隐式的返回 this.

本身实现 new 操做符

  • new 操做符会返回一个对象,因此咱们须要在内部建立一个对象
  • 这个对象,也就是构造函数中的 this,能够访问到挂载在 this 上的任意属性
  • 这个对象能够访问到构造函数原型上的属性,因此须要将对象与构造函数连接起来
  • 返回原始值须要忽略,返回对象须要正常处理
function create(Con, ...args) {
 let obj = {}
 Object.setPrototypeOf(obj, Con.prototype)
 let result = Con.apply(obj, args)
 return result instanceof Object ? result : obj
}

本部分参考博主连接:http://www.javashuo.com/article/p-zugxclij-h.html

3、DOM

文档对象模型(Document Object Model,简称DOM),是W3C组织推荐的处理可扩展标志语言的标准编程接口。在网页上,组织页面(或文档)的对象被组织在一个树形结构中,用来表示文档中对象的标准模型就称为DOM

1. DOM操做

createDocumentFragment()    //建立一个DOM片断
createElement()   //建立一个具体的元素
createTextNode()   //建立一个文本节点
  • 添加:appendChild()

  • 移出:removeChild()

  • 替换:replaceChild()

  • 插入:insertBefore()

  • 复制:cloneNode(true)

节点变化触发的事件

  • DOMSubtreeModified:在DOM结构中发生任何变化时触发; 
  • DOMNodeInserted:在一个节点做为子节点被插入到另外一个节点中时触发; 
  • DOMNodeRemoved:在节点从其父节点中被移除时触发; 
  • DOMNodeInsertedIntoDocument:在一个节点被直接插入文档中或者经过子树间接插入文档后触发。在DOMNodeInserted以后触发; 
  • DOMNodeRemovedFromDocument:在一个节点被直接从文档中删除或经过子树间接从文档中移除以前触发。在DOMNodeRemoved以后触发。 
  • DOMAttrModified:在特性被修改以后触发; 
  • DOMCharacterDataModified:在文本节点的值发生变化的时候触发。 
//查找
getElementsByTagName()    //经过标签名称
getElementsByClassName()    //经过标签名称
getElementsByName()    //经过元素的Name属性的值
getElementById()    //经过元素Id,惟一性

子节点

  • Node.childNodes //获取子节点列表NodeList; 注意换行在浏览器中被算做了text节点,若是用这种方式获取节点列表,须要进行过滤
  • Node.firstChild //返回第一个子节点
  • Node.lastChild //返回最后一个子节点

父节点

  • Node.parentNode // 返回父节点
  • Node.ownerDocument //返回祖先节点(整个document)

同胞节点

  • Node.previousSibling // 返回前一个节点,若是没有则返回null
  • Node.nextSibling // 返回后一个节点

2. DOM事件

DOM事件模型分为捕获和冒泡。一个事件发生后,会在子元素和父元素之间传播(propagation)。这种传播分红三个阶段。

  • 捕获阶段:事件从window对象自上而下目标节点传播的阶段;
  • 目标阶段:真正的目标节点正在处理事件的阶段;
  • 冒泡阶段:事件从目标节点自下而上window对象传播的阶段。

2.1 事件捕获

捕获是从上到下,事件先从window对象,而后再到document(对象),而后是html标签(经过document.documentElement获取html标签),而后是body标签(经过document.body获取body标签),而后按照普通的html结构一层一层往下传,最后到达目标元素。咱们只须要将addEventListener第三个参数改成true就能够实现事件捕获。

//摘自xyyojl的《深刻理解DOM事件机制》
<!-- CSS 代码 -->
<style>
    body{margin: 0;}
    div{border: 1px solid #000;}
    #grandfather1{width: 200px;height: 200px;}
    #parent1{width: 100px;height: 100px;margin: 0 auto;}
    #child1{width: 50px;height: 50px;margin: 0 auto;}
</style>

<!-- HTML 代码 -->
<div id="grandfather1">
    爷爷
    <div id="parent1">
        父亲
        <div id="child1">儿子</div>
    </div>
</div>

<!-- JS 代码 -->
<script>
    var grandfather1 = document.getElementById('grandfather1'),
        parent1 = document.getElementById('parent1'),
        child1 = document.getElementById('child1');
    
    grandfather1.addEventListener('click',function fn1(){
        console.log('爷爷');
    },true)
    parent1.addEventListener('click',function fn1(){
        console.log('爸爸');
    },true)
    child1.addEventListener('click',function fn1(){
        console.log('儿子');
    },true)

    /*
        当我点击儿子的时候,触发顺序是爷爷 ——》父亲——》儿子
    */
    // 请问fn1 fn2 fn3 的执行顺序?
    // fn1 fn2 fn3 or fn3 fn2 fn1  
</script>

2.2 事件冒泡

所谓事件冒泡就是事件像泡泡同样从最开始生成的地方一层一层往上冒。咱们只须要将addEventListener的第三个参数改成false就能够实现事件冒泡。

//html、css代码同上,js代码只是修改一下而已
var grandfather1 = document.getElementById('grandfather1'),
    parent1 = document.getElementById('parent1'),
    child1 = document.getElementById('child1');

grandfather1.addEventListener('click',function fn1(){
    console.log('爷爷');
},false)
parent1.addEventListener('click',function fn1(){
    console.log('爸爸');
},false)
child1.addEventListener('click',function fn1(){
    console.log('儿子');
},false)

/*
   当点击儿子的时候,触发顺序:儿子——》爸爸——》爷爷
*/
// 请问fn1 fn2 fn3 的执行顺序?
// fn1 fn2 fn3 or fn3 fn2 fn1  

3. 事件代理(事件委托)

因为事件会在冒泡阶段向上传播到父节点,所以能够把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件。这种方法叫作事件的代理(delegation)

3.1 优势

  • 减小内存消耗,提升性能

若是给每一个列表项一一都绑定一个函数,那对于内存消耗是很是大的,效率上须要消耗不少性能。借助事件代理,咱们只须要给父容器ul绑定方法便可,这样无论点击的是哪个后代元素,都会根据冒泡传播的传递机制,把容器的click行为触发,而后把对应的方法执行,根据事件源,咱们能够知道点击的是谁,从而完成不一样的事。

  • 动态绑定事件

在不少时候,咱们须要经过用户操做动态的增删列表项元素,若是一开始给每一个子元素绑定事件,那么在列表发生变化时,就须要从新给新增的元素绑定事件,给即将删去的元素解绑事件,若是用事件代理就会省去不少这样麻烦。

3.2 跨浏览器处理事件程序

标准事件对象:

  • (1)type:事件类型
  • (2)target:事件目标
  • (3)stopPropagation()方法:阻止事件冒泡
  • (4)preventDefault()方法:阻止事件的默认行为

IE中的事件对象:

  • (1)type:事件类型

  • (2)srcElement:事件目标

  • (3)cancelBubble属性:阻止事件冒泡 true表示阻止冒泡,false表示不阻止

  • (4)returnValue属性:阻止事件的默认行为

本部分参考博客连接: http://www.javashuo.com/article/p-wvzbwgee-t.html

4、arguments

它是JS的一个内置对象,常被人们所忽略,但实际上确很重要,JS不像JAVA是显示传递参数,JS传的是形参,能够传也能够不传,若方法里没有写参数却传入了参数,那么就要用arguments来拿到这些参数了。每个函数都有一个arguments对象,它包括了函数所要调的参数,一般咱们把它看成数组使用,用它的length获得参数数量,但它却不是数组,若使用push添加数据将报错。

在函数调用的时候,浏览器每次都会传递进两个隐式参数:

1. 函数的上下文对象this

2. 封装实参的对象arguments

arguments还有属性callee,length和迭代器Symbol

1. 咱们发现callee的值是函数fun,而且callee指向函数fun

function fun(){
  // console.log(arguments);
  console.log('arguments.callee === fun的值:',arguments.callee === fun);
}
fun('tom',[1,2,3],{name:'Janny'});

2. 第二个属性length,咱们常常在数组或者类数组中看到,能够看到arguments的原型索引__proto__的值为Object,故此咱们推测arguments不是数组,而是一个类数组对象。

把arguments转换成一个真正的数组: var args = Array.prototype.slice.call(arguments); 

3. 第三个属性是个Symbol类型的键,该类型的值都是独一无二的,该键指向的值是一个values函数,该值是一个生成迭代器的函数。

let arr = ['a', 'b', 'c'];
let iter = arr[Symbol.iterator]();
 
iter.next() // { value: 'a', done: false }
iter.next() // { value: 'b', done: false }
iter.next() // { value: 'c', done: false }
iter.next() // { value: undefined, done: true }

在arguments中有一样的做用

function fun(){
  console.log(arguments[Symbol.iterator]);
  let iterator = arguments[Symbol.iterator]();
  console.log('iterator:',iterator);
  console.log(iterator.next());
  console.log(iterator.next());
  console.log(iterator.next());
  console.log(iterator.next());
}
fun('tom',[1,2,3],{name:'Janny'});
本部分参考博主连接: https://blog.csdn.net/zjy_android_blog/article/details/80934042

5、数据类型判断

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

JS中的不可扩展对象、密封对象、冻结对象

JavaScript 中,能够对对象的权限进行配置,经过配置,可将对象设置为不可扩展对象、密封对象、冻结对象等,以达到保护对象属性的目的。

  • 若是一个对象能够添加新的属性,则这个对象是可扩展的。Object.preventExtensions()将对象标记为再也不可扩展,所以它将永远不会具备超出它被标记为不可扩展的属性。注意,通常来讲,不可扩展对象的属性可能仍然可被删除。尝试将新属性添加到不可扩展对象将静默失败或抛出TypeError(最多见但不排除其余状况,如在strict mode中)。
  • Object.preventExtensions()仅阻止添加自身的属性。但属性仍然能够添加到对象原型。一旦使其不可扩展,就没法再对象进行扩展。
  • 密封对象不可扩展,并且已有的属性成员[[configurable]]特性将被设置成false(意味着不能删除属性和方法,可是可修改已有属性值),使用Object.seal()能够将对象密封
  • 最严格的防止篡改级别是冻结对象,冻结的对象既不能够扩展,又是密封的,并且对象数据属性的[[writable]]特性会被设置为false。 若是定义[[Set]]函数,访问器属性仍然是可写的,使用Object.freeze()方法能够冻结对象

1. null和undefined的差别相同点:

  • 在 if判断语句中,值都默认为 false
  • 大致上二者都是表明无,具体看差别

差别:

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

2. == 和 ===区别

==, 两边值类型不一样的时候,要先进行类型转换,再比较;===,不作类型转换,类型不一样的必定不等 。
===规则: 
一、若是类型不一样,就[不相等] 
二、若是两个都是数值,而且是同一个值,那么[相等];(!例外)的是,若是其中至少一个是NaN,那么[不相等]。(判断一个值是不是NaN,只能用isNaN()来判断) 
三、若是两个都是字符串,每一个位置的字符都同样,那么[相等];不然[不相等]。 
四、若是两个值都是true,或者都是false,那么[相等]。 
五、若是两个值都引用同一个对象或函数,那么[相等];不然[不相等]。 
六、若是两个值都是null,或者都是undefined,那么[相等]。 
==规则: 
一、若是两个值类型相同,进行 === 比较。 
二、若是两个值类型不一样,他们可能相等。根据下面规则进行类型转换再比较: 
   a、若是一个是null、一个是undefined,那么[相等]。 
   b、若是一个是字符串,一个是数值,把字符串转换成数值再进行比较。 
   c、若是任一值是 true,把它转换成 1 再比较;若是任一值是 false,把它转换成 0 再比较。 
   d、若是一个是对象,另外一个是数值或字符串,把对象转换成基础类型的值再比较。对象转换成基础类型,利用它的toString或者valueOf方法。js核心内置类,会尝试valueOf先于toString;例外的是Date,Date利用的是toString转换。非js核心的对象,令说(比较麻烦,我也不大懂) 
   e、任何其余组合,都[不相等]。 
null instanceof Object
null === undefined
null == undefined
NaN == NaN
0 == "0"
true == "20"
//答案是: false false true false true false
//加法运算
 console.dir(16+"5"); //156
 console.dir(5+"a");//5a
 console.dir(5+NaN);//NaN
 console.dir(5+null);//5
 console.dir('5'+null);//5null
 console.dir(5+undefined);//NaN
 console.dir(null+undefined);//NaN
 console.dir(5+5);//10
 console.dir("两个数的和是"+5+5);//两个数的和是55
 console.dir("两个数的和是"+(5+5));//两个数的和是10

补充:隐性转换规则

首先看双等号先后有没有NaN,若是存在NaN,一概返回false。

再看双等号先后有没有布尔,有布尔就将布尔转换为数字。(false是0,true是1)

接着看双等号先后有没有字符串, 有三种状况:

  • 对方是对象,对象使用toString()或者valueOf()进行转换;
  • 对方是数字,字符串转数字;(前面已经举例)
  • 对方是字符串,直接比较;
  • 其余返回false

若是是数字,对方是对象,对象取valueOf()或者toString()进行比较, 其余一概返回false

null, undefined不会进行类型转换, 但它们俩相等。

var undefined;
undefined == null; // true
1 == true; // true
2 == true; // false
0 == false; // true
0 == ' '; // true
NaN == NaN; // false
[] == false; // true
[] == ![]; // true

// alert(!![]) //true
// alert(![]) //false
// alert([] == 0) //true
// alert(false == 0) //true

如今来探讨 [] == ! [] 的结果为何会是true
①、根据运算符优先级 ,! 的优先级是大于 == 的,因此先会执行 ![]

!可将变量转换成boolean类型,null、undefined、NaN以及空字符串('')取反都为true,其他都为false。

因此 ! [] 运算后的结果就是 false

也就是 [] == ! [] 至关于 [] == false

②、根据上面提到的规则(若是有一个操做数是布尔值,则在比较相等性以前先将其转换为数值——false转换为0,而true转换为1),则须要把 false 转成 0

也就是 [] == ! [] 至关于 [] == false 至关于 [] == 0

③、根据上面提到的规则(若是一个操做数是对象,另外一个操做数不是,则调用对象的valueOf()方法,用获得的基本类型值按照前面的规则进行比较,若是对象没有valueOf()方法,则调用 toString())

而对于空数组,[].toString() ->  '' (返回的是空字符串)

也就是  [] == 0 至关于 '' == 0

④、根据上面提到的规则(若是一个操做数是字符串,另外一个操做数是数值,在比较相等性以前先将字符串转换为数值)

Number('') -> 返回的是 0

至关于 0 == 0 天然就返回 true了

总结一下:

[] == ! []   ->   [] == false  ->  [] == 0  ->   '' == 0   ->  0 == 0   ->  true

那么对于 {} == !{} 也是同理的

关键在于  {}.toString() ->  NaN(返回的是NaN)

根据上面的规则(若是有一个操做数是NaN,则相等操做符返回 false)

总结一下:

{} == ! {}   ->   {} == false  ->  {} == 0  ->   NaN == 0    ->  false

关系类型:

 console.dir(16>"5"); //true
 console.dir("16">"5");//false
 console.dir(5<"a");//false
 console.dir(5>=NaN);//false
 console.dir(5<NaN);//false
 console.dir(NaN>=NaN);//false
 console.dir(5>=null);//true
 console.dir(5>=undefined);//false
 console.dir(5>=5);//true
 console.dir(5>=true);//true
 console.dir(5>="true");//false
 console.dir(5>="");//true 
 console.dir("Brick">"alphabet");//false  B的字符串编码值是66 ,而a的字符串编码是97.所以false
 console.dir("brick">"alphabet");//true 小写字母b比a大,因此是true

3. 判断数据类型

typeof:用来判断各类数据类型。

typeof 2 //输出 number 
typeof null //输出 object 
typeof {} //输出 object 
typeof [] //输出 object 
typeof (function(){}) //输出 function
typeof undefined //输出 undefined 
typeof '222' //输出 string 
typeof true //输出 boolean

instanceof:判断已知对象类型的方法.instanceof 后面必定要是对象类型,而且大小写不能错,该方法适合一些条件选择或分支。

var c= [1,2,3]; 
var d = new Date(); 
var e = function(){alert(111);}; 
var f = function(){this.name="22";}; 
console.log(c instanceof Array) //true
console.log(d instanceof Date) //true
console.log(e instanceof Function) //true
// console.log(f instanceof function ) //false

instanceof是一个二元运算符,如:A instanceof B. 其中,A必须是一个合法的JavaScript对象,B必须是一个合法的JavaScript函数 (function)。若是函数B在对象A的原型链 (prototype chain) 中被发现,那么instanceof操做符将返回true,不然返回false.

console.log(Array instanceof Function);//true
console.log(Object instanceof Function);//true
function Foo() {
}
var foo = new Foo();
alert(foo instanceof Foo);// true 
alert(foo instanceof Object);// true
alert(foo instanceof Function);// false
alert(Foo instanceof Function);// true
alert(Foo instanceof Object);// true

为什么Object instanceof Function和Function instanceof Object都返回true?

Object, Function, Array等等这些都被称做是构造“函数”,他们都是函数。而全部的函数都是构造函数Function的实例。从原型链机制的的角度来讲,那就是说全部的函数都能经过原型链找到建立他们的Function构造函数的构造原型Function.protorype对象,因此:

alert(Object instanceof Function);// return true

与此同时,又由于Function.prototype是一个对象,因此他的构造函数是Object. 从原型链机制的的角度来讲,那就是说全部的函数都能经过原型链找到建立他们的Object构造函数的构造原型Object.prototype对象,因此:

alert(Function instanceof Object);// return true

有趣的是根据咱们经过原型链机制对instanceof进行的分析,咱们不可贵出一个结论:Function instanceof Function 依然返回true, 原理是同样的

1. Function是构造函数,因此它是函数对象

2. 函数对象都是由Function构造函数建立而来的,原型链机制解释为:函数对象的原型链中存在Function.prototype

3. instanceof查找原型链中的每个节点,若是Function.prototype的构造函数Function的原型链中被查到,返回true

所以下面代码依然返回true

alert(Function instanceof Function);// still true

instanceof部分摘自:https://www.cnblogs.com/objectorl/archive/2010/01/11/Object-instancof-Function-clarification.html

constructor:据对象的constructor判断,返回对建立此对象的数组函数的引用。

var c= [1,2,3]; 
var d = new Date(); 
var e = function(){alert(111);}; 
alert(c.constructor === Array) //----------> true
alert(d.constructor === Date) //-----------> true
alert(e.constructor === Function) //-------> true
//注意: constructor 在类继承时会出错

prototype:全部数据类型都可判断:Object.prototype.toString.call,这是对象的一个原生原型扩展函数,用来更精确的区分数据类型。

var gettype=Object.prototype.toString
gettype.call('aaaa') //输出 [object String] 
gettype.call(2222) //输出 [object Number] 
gettype.call(true) //输出 [object Boolean] 
gettype.call(undefined) //输出 [object Undefined] 
gettype.call(null) //输出 [object Null] 
gettype.call({}) //输出 [object Object] 
gettype.call([]) //输出 [object Array] 
gettype.call(function(){}) //输出 [object Function]

6、做用域链、闭包、做用域

1. 做用域/链

变量做用域:一个变量能够使用的范围

JS中首先有一个最外层的做用域:称之为全局做用域

JS中还能够经过函数建立出一个独立的做用域,其中函数能够嵌套,因此做用域也能够嵌套

词法做用域就是在你写代码时将变量和块做用域写在哪里来决定,也就是词法做用域是静态的做用域,在你书写代码时就肯定了。

做用域链是由当前做用域与上层一系列父级做用域组成,做用域的头部永远是当前做用域,尾部永远是全局做用域。做用域链保证了当前上下文对其有权访问的变量的有序访问。

做用域链的意义:查找变量(肯定变量来自于哪里,变量是否能够访问)

引擎会在解释javascript代码以前首先对其进行编译。编译阶段中的一部分工做就是找到全部的声明,并用合适的做用域将它们关联起来,引擎查询共分为两种:LHS查询和RHS查询 

从字面意思去理解,当变量出如今赋值操做的左侧时进行LHS查询,出如今右侧时进行RHS查询,更准确地讲,RHS查询与简单地查找某个变量的值没什么区别,而LHS查询则是试图找到变量的容器自己,从而能够对其赋值

function foo(a){
    console.log(a);//2
}
foo( 2 );

这段代码中,总共包括4个查询,分别是:

  一、foo(...)对foo进行了RHS引用

  二、函数传参a = 2对a进行了LHS引用

  三、console.log(...)对console对象进行了RHS引用,并检查其是否有一个log的方法

  四、console.log(a)对a进行了RHS引用,并把获得的值传给了console.log(...)

在当前做用域中没法找到某个变量时,引擎就会在外层嵌套的做用域中继续查找,直到找到该变量,或抵达最外层的做用域(也就是全局做用域)为止。

RHS查询

【1】若是RHS查询失败,引擎会抛出ReferenceError(引用错误)异常

//对b进行RHS查询时,没法找到该变量。也就是说,这是一个“未声明”的变量
function foo(a){
    a = b;  
}
foo();//ReferenceError: b is not defined

【2】若是RHS查询找到了一个变量,但尝试对变量的值进行不合理操做,好比对一个非函数类型值进行函数调用,或者引用null或undefined中的属性,引擎会抛出另一种类型异常:TypeError(类型错误)异常

function foo(){
    var b = 0;
    b();
}
foo();//TypeError: b is not a function

LHS查询

【1】当引擎执行LHS查询时,若是没法找到变量,全局做用域会建立一个具备该名称的变量,并将其返还给引擎

function foo(){
    a = 1;  
}
foo();
console.log(a);//1

【2】若是在严格模式中LHS查询失败时,并不会建立并返回一个全局变量,引擎会抛出同RHS查询失败时相似的ReferenceError异常

function foo(){
    'use strict';
    a = 1;  
}
foo();
console.log(a);//ReferenceError: a is not defined

原理解析:

function foo(a){
    console.log(a);
}
foo(2);

【1】引擎须要为foo(...)函数进行RHS引用,在全局做用域中查找foo。成功找到并执行

【2】引擎须要进行foo函数的传参a=2,为a进行LHS引用,在foo函数做用域中查找a。成功找到,并把2赋值给a

【3】引擎须要执行console.log(...),为console对象进行RHS引用,在foo函数做用域中查找console对象。因为console是个内置对象,被成功找到

【4】引擎在console对象中查找log(...)方法,成功找到

【5】引擎须要执行console.log(a),对a进行RHS引用,在foo函数做用域中查找a,成功找到并执行

【6】因而,引擎把a的值,也就是2传到console.log(...)中

【7】最终,控制台输出2

2. 闭包

闭包就是一个函数,一个能够访问并操做其余函数内部变量的函数。也能够说是一个定义在函数内部的函数。由于JavaScript没有动态做用域,而闭包的本质是静态做用域(静态做用域规则查找一个变量声明时依赖的是源程序中块之间的静态关系),因此函数访问的都是咱们定义时候的做用域,也就是词法做用域。因此闭包才会得以实现。
咱们常见的闭包形式就是a 函数套 b 函数,而后 a 函数返回 b 函数,这样 b 函数在 a 函数之外的地方执行时,依然能访问 a 函数的做用域。其中“b 函数在 a 函数之外的地方执行时”这一点,才体现了闭包的真正的强大之处。
function fn1() {
    var name = 'iceman';
    function fn2() {
        console.log(name);
    }
    return fn2;
}
var fn3 = fn1();
fn3();
  • fn2的词法做用域能访问fn1的做用域

  • fn2当作一个值返回

  • fn1执行后,将fn2的引用赋值给fn3

  • 执行fn3,输出了变量name

正常来讲,当fn1函数执行完毕以后,其做用域是会被销毁的,而后垃圾回收器会释放那段内存空间。而闭包却很神奇的将fn1的做用域存活了下来,fn2依然持有该做用域的引用,这个引用就是闭包。

闭包造成的条件

  • 函数嵌套
  • 内部函数引用外部函数的局部变量

闭包的内存泄漏

栈内存提供一个执行环境,即做用域,包括全局做用域和私有做用域,那他们何时释放内存的?

  • 全局做用域----只有当页面关闭的时候全局做用域才会销毁
  • 私有的做用域----只有函数执行才会产生

通常状况下,函数执行会造成一个新的私有的做用域,当私有做用域中的代码执行完成后,咱们当前做用域都会主动的进行释放和销毁。但当遇到函数执行返回了一个引用数据类型的值,而且在函数的外面被一个其余的东西给接收了,这种状况下通常造成的私有做用域都不会销毁。

所谓内存泄漏指任何对象在您再也不拥有或须要它以后仍然存在。闭包不能滥用,不然会致使内存泄露,影响网页的性能。闭包使用完了后,要当即释放资源,将引用变量指向null。
//经典面试题  
function outer(){
  var num=0;//内部变量
  return function add(){//经过return返回add函数,就能够在outer函数外访问了
  num++;//内部函数有引用,做为add函数的一部分了
  console.log(num);
  };
 }
  var func1=outer();
  func1();//其实是调用add函数, 输出1
  func1();//输出2 由于outer函数内部的私有做用域会一直被占用
  var func2=outer();
  func2();// 输出1  每次从新引用函数的时候,闭包是全新的。
  func2();// 输出2  

闭包的做用

  • 能够读取函数内部的变量。
  • 能够使变量的值长期保存在内存中,生命周期比较长。所以不能滥用闭包,不然会形成网页的性能问题
  • 能够用来实现JS模块。

JS模块:具备特定功能的js文件,将全部的数据和功能都封装在一个函数内部(私有的),只向外暴露一个包信n个方法的对象或函数,模块的使用者,只须要经过模块暴露的对象调用方法来实现对应的功能。

闭包的运用

应用闭包的主要场合是:设计私有的方法和变量。

补充:块级做用域

一般是由于只想在for循环内部的上下文中使用变量i,但实际上i能够在全局做用域中访问,污染了整个做用域:

for (var i= 0; i<10; i++) {
     console.log(i);
}
console.log(i);//10
//当即执行匿名函数(IIFE)
(function(){
  var i = 1;  
})();
console.log(i);//ReferenceError: i is not defined

//for循环的代码中变量i用let声明,将会避免做用域污染问题
for (let i= 0; i<10; i++) {
     console.log(i);
}
console.log(i);////ReferenceError: i is not defined

下面代码中,因为闭包只能取得包含函数中的任何变量的最后一个值,因此控制台输出5,而不是0

var a = [];
for(var i = 0; i < 5; i++){
    a[i] = function(){
        return i;
    }
}
console.log(a[0]());//5

//能够经过函数传参,来保存每次循环的值
var a = [];
for(var i = 0; i < 5; i++){
    a[i] = (function(j){
        return function(){
            return j;
        }
    })(i);
}
console.log(a[0]());//0

//而使用let则更方便,因为let循环有一个从新赋值的过程,至关于保存了每一次循环时的值
var a = [];
for(let i = 0; i < 5; i++){
    a[i] = function(){
        return i;
    }
}
console.log(a[0]());//0

补充:变量/函数提高

 var a = 2 ; 这个代码片断实际上包括两个操做: var a  和  a = 2  ,第一个定义声明是在编译阶段由编译器进行的。第二个赋值操做会被留在原地等待引擎在执行阶段执行。

console.log(a);
var a = 0;
function fn(){
    console.log(b);
    var b = 1;
    function test(){
        console.log(c);
        var c = 2;
    }
    test();
}
fn();
//变量声明提高后,变成下面这样
var a ;
console.log(a);
a = 0;
function fn(){
    var b;
    console.log(b);
    b = 1;
    function test(){
        var c ;
        console.log(c);
        c = 2;
    }
    test();
}
fn();

函数声明会提高,但函数表达式却不会提高

foo();
function foo(){
    console.log(1);//1
}

//提高后
function foo(){
    console.log(1);
}
foo();

//函数表达式不会提高
foo();
var foo = function(){
    console.log(1);//TypeError: foo is not a function
}

//变量提高后,代码以下所示,依然会报错:
var foo;
foo();
foo = function(){
    console.log(1);
}

函数声明和变量声明都会被提高。可是,函数声明会覆盖变量声明

var a;
function a(){}
console.log(a);//'function a(){}'

可是,若是变量存在赋值操做,则最终的值为变量的值

var a=1;
function a(){}
console.log(a);//1

var a;
function a(){};
console.log(a);//'function a(){}'
a = 1;
console.log(a);//1

注意:变量的重复声明是无用的,但函数的重复声明会覆盖前面的声明(不管是变量仍是函数声明)

【1】变量的重复声明无用

var a = 1;
var a;
console.log(a);//1

【2】因为函数声明提高优先于变量声明提高,因此变量的声明无做用

var a;
function a(){
    console.log(1);
}
a();//1

【3】后面的函数声明会覆盖前面的函数声明

复制代码
a();//2
function a(){
    console.log(1);
}
function a(){
    console.log(2);
}

 本部分参考博主连接:http://www.javashuo.com/article/p-fcyvnpug-cv.html

7、Ajax的原生写法

1. Ajax介绍

  • 全称Asynchronous JavaScript and XML
  • 异步的 JavaScript 和 XML;
  • 能够在不从新加载整个页面的状况下,与服务器交换数据并更新部分网页内容;
  • 可以实现局部刷新,大大下降了资源的浪费;
  • 不须要任何浏览器插件,但须要用户容许JavaScript在浏览器上执行;
  • 是一门用于建立快速动态网页的技术;
  • 传统的网页(不使用 AJAX)若是须要更新内容,必须重载整个网页;
Ajax的工做原理至关于在用户和服务器之间加了—个中间层(AJAX引擎),使用户操做与服务器响应异步化。并非全部的用户请求都提交给服务器。像—些数据验证和数据处理等都交给Ajax引擎本身来作,,只有肯定须要从服务器读取新数据时再由Ajax引擎代为向服务器提交请求。

2. XMLHttpRequest 对象的三个经常使用的属性

  • onreadystatechange 属性存有处理服务器响应的函数;
  • readyState 属性存有服务器响应的状态信息。每当 readyState 改变时,onreadystatechange 函数就会被执行;
  • 能够经过 responseText 属性来取回由服务器返回的数据。
状态 描述
0 请求未初始化(在调用 open() 以前)
1 请求已提出(调用 send() 以前)
2 请求已发送(这里一般能够从响应获得内容头部)
3 请求处理中(响应中一般有部分数据可用,可是服务器尚未完成响应)
4 请求已完成(能够访问服务器响应并使用它)

3. xmlhttprequst的方法

  • open() 有三个参数。第一个参数定义发送请求所使用的方法,第二个参数规定服务器端脚本的URL,第三个参数规定应当对请求进行异步地处理。 xmlHttp.open("GET","test.php",true); 
  • send() 方法将请求送往服务器。若是咱们假设 HTML 文件和 PHP 文件位于相同的目录,那么代码是这样的: xmlHttp.send(null); 

4. 实现Ajax

  • 建立XMLHttpRequest对象。
  • 设置请求方式。
  • 调用回调函数。
  • 发送请求。
var Ajax = {
    get: function(url, fn) {
        //建立XMLHttpRequest对象
        var xhr = new XMLHttpRequest();
        //true表示异步
        xhr.open('GET', url, true);
        xhr.onreadystatechange = function() {
            // readyState == 4说明请求已完成
            if(xhr.readyState == 4 && xhr.status == 200 || xhr.status == 304) {
                //responseText:从服务器得到数据
                fn.call(this, xhr.responseText);
             }
         };
          xhr.send();
      },
     post: function(url, data, fn) { //datat应为'a=a1&b=b1'这种字符串格式
         var xhr = new XMLHttpRequest();
         xhr.open("POST", url, true);
         // 添加http头,发送信息至服务器时内容编码类型
         xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
         xhr.onreadystatechange = function() {
             if(xhr.readyState == 4 && (xhr.status == 200 || xhr.status == 304)) {
                fn.call(this, xhr.responseText);
             }
         };
             xhr.send(data);
     }
}

8、对象深拷贝、浅拷贝

浅拷贝就是把属于源对象的值都复制一遍到新的对象,不会开辟二者独立的内存区域;深度拷贝则是完彻底全两个独立的内存区域,互不干扰

//js的深拷贝
function deepCopy(obj){
    //判断是不是简单数据类型,
    if(typeof obj == "object"){ //复杂数据类型 var result = obj.constructor == Array ? [] : {}; for(let i in obj){ result[i] = typeof obj[i] == "object" ? deepCopy(obj[i]) : obj[i]; } }else { //简单数据类型 直接 == 赋值 var result = obj; } return result; }

 

/**
* deep clone
* @param  {[type]} parent object 须要进行克隆的对象
* @return {[type]}        深克隆后的对象
*/
const clone = parent => {
  // 维护两个储存循环引用的数组
  const parents = [];
  const children = [];

  const _clone = parent => {
    if (parent === null) return null;
    if (typeof parent !== 'object') return parent;

    let child, proto;

    if (isType(parent, 'Array')) {
      // 对数组作特殊处理
      child = [];
    } else if (isType(parent, 'RegExp')) {
      // 对正则对象作特殊处理
      child = new RegExp(parent.source, getRegExp(parent));
      if (parent.lastIndex) child.lastIndex = parent.lastIndex;
    } else if (isType(parent, 'Date')) {
      // 对Date对象作特殊处理
      child = new Date(parent.getTime());
    } else {
      // 处理对象原型
      proto = Object.getPrototypeOf(parent);
      // 利用Object.create切断原型链
      child = Object.create(proto);
    }

    // 处理循环引用
    const index = parents.indexOf(parent);

    if (index != -1) {
      // 若是父数组存在本对象,说明以前已经被引用过,直接返回此对象
      return children[index];
    }
    parents.push(parent);
    children.push(child);

    for (let i in parent) {
      // 递归
      child[i] = _clone(parent[i]);
    }

    return child;
  };
  return _clone(parent);
};

9、图片懒加载、预加载

懒加载即延迟,对于图片过多的页面,为了加快页面加载速度,咱们须要将页面内未出如今可视区域内的图片先不作加载, 等到滚动到可视区域后再去加载。这样一来页面加载性能大幅提高,提升了用户体验。

第一种是纯粹的延迟加载,使用setTimeOut或setInterval进行加载延迟,若是用户在加载前就离开了页面,那么就不会加载。 
第二种是条件加载,符合某些条件,或触发了某些事件才开始异步下载。
第三种是可视区加载,即仅加载用户能够看到的区域,这个主要由监控滚动条来实现,通常会在距用户看到某图片前必定距离遍开始加载,这样能保证用户拉下时正好能看到图片。
原理:在页面载入时将img标签內的src指向一个小图片,即占位图或loading图,将真实地址存放于一个自定义属性data-src中。当页面滚动时,遍历有data-src的img标签,判断每一个img是否进入可视区,当某个img进入了可视区域,就将真实地址赋值给该img的src并将该img的data-src删除以免重复判断。
<img src="https://i.loli.net/2017/08/08/5989307b6c87b.gif" data-xxx="${data.content[i].url}">
let images = document.querySelectorAll('img[data-xxx]')
  for(let i = 0; i <images.length; i++){
    if(出如今屏幕里(images[i])){
      images[i].src = images[i].getAttribute('data-xxx')
      images[i].removeAttribute('data-xxx')
    }

预加载:图片预加载就是在网页所有加载以前,提早加载图片。当用户须要查看时可直接从本地缓存中渲染,以提供给用户更好的体验,减小等待的时间。不然,若是一个页面的内容过于庞大,没有使用预加载技术的页面就会长时间的展示为一片空白,这样浏览者可能觉得图片预览慢而没兴趣浏览,把网页关掉,这时,就须要图片预加载。固然这种作法实际上牺牲了服务器的性能换取了更好的用户体验。

实现预载的方法很是多,能够用CSS(background)、JS(Image)、HTML(<img />)均可以。经常使用的是new Image();,设置其src来实现预载,再使用onload方法回调预载完成事件。只要浏览器把图片下载到本地,一样的src就会使用缓存,这是最基本也是最实用的预载方法。当Image下载完图片头后,会获得宽和高,所以能够在预载前获得图片的大小(我所知的方法是用记时器轮循宽高变化)。通常实现预载的工具类,都实现一个Array来存须要预载的URL,而后实现Finish、Error、SizeChange等经常使用事件,能够由用户选择是顺序预载或假并发预载。Jquery的PreLoad能够用于预载。

JS获取宽高的方式

获取屏幕的高度和宽度(屏幕分辨率): window.screen.height/width 
获取屏幕工做区域的高度和宽度(去掉状态栏): window.screen.availHeight/availWidth 
网页全文的高度和宽度: document.body.scrollHeight/Width 
滚动条卷上去的高度和向右卷的宽度: document.body.scrollTop/scrollLeft 
网页可见区域的高度和宽度(不加边线): document.body.clientHeight/clientWidth 
网页可见区域的高度和宽度(加边线): document.body.offsetHeight/offsetWidth 

10、实现页面加载进度条

document.onreadystatechange页面加载状态改变时的事件;
document.readyState返回当前文档的状态(uninitialized--还未开始载入;loading--载入中;interactive--已加载,文档与用户能够开始交互;complete--载入完成)

<script type="text/javascript">
//页面加载状态改变时的事件
    document.onreadystatechange = function () {
       if(document.readyState == 'complete'){   //判断页面加载完成,加载的图标就隐藏
           $(".loading").fadeOut();
       }
    }
</script>

11、this关键字

在Javascript中,当一个函数被调用时,会建立一个活动记录(也称为执行上下文)。它包含函数在哪里调用、函数的调用方式、传入的参数等信息。this就是这个记录的一个属性,会在函数执行的过程当中用到。this关键字是在运行时进行绑定的,与函数声明的位置没有任何关系,它指向什么彻底取决于函数在哪里被调用

1. this四大绑定规则

函数绑定(默认绑定) 

当直接调用函数时就是函数绑定模式。

function fn() {
    console.log( this.a );
}
var a = 2;
fn(); 
// 2 -- fn单独调用,this引用window

注意:在非严格模式下,this将绑定到全局对象window。然而,在严格模式下,this将绑定到undefined

隐式绑定(方法调用)

当函数做为一个对象的属性被调用的时候就属于隐式绑定模式,此时,this指向是调用这个函数的对象。

function test(){
 alert(this.x);
}
var obj = {};
obj.x = 1;
obj.m = test;
obj.m(); // 1

注意:被隐式绑定的函数会丢失绑定对象,此时,将会应用默认绑定,从而把this绑定到全局对象或undefined上

显式绑定(硬绑定)

在Javascript中,一般使用call/apply/bind方法来进行显示绑定。

var x = 0;
function test(){
 alert(this.x);
}

var obj={}; obj.x = 1; obj.m = test; obj.m.apply(); //0 //apply()的参数为空时,默认调用全局对象。所以,这时的运行结果为0,证实this指的是全局对象。若是把最后一行代码修改成 obj.m.apply(o); //1

new绑定(构造器绑定) 

经过new关键字调用的函数,属于new绑定模式。这时this关键字指向这个新建立的对象。

function test(){
  this.x = 1;
}
 var obj = new test();
 alert(obj.x); // 1
 //运行结果为1。为了代表这时this不是全局对象,我对代码作一些改变:
 var x = 2;
 function test(){
   this.x = 1;
 }
 var obj = new test();
 alert(x); //2

this关键字绑定规则的断定顺序

  • 函数是不是new绑定?若是是,则this指向新建立的对象;
  • 函数是否经过call/apply/bind显式绑定或硬绑定?若是是,则this指向指定的对象;
  • 函数是否在某个上下文对象中隐式调用?若是是,this绑定的是那个上下文对象;
  • 上述全不是,则使用默认绑定。若是在严格模式下,就绑定到undefined,不然绑定到全局window对象。

2. this绑定指向改变

call:

function.call(obj,[param1[,param2[,…[,paramN]]]])

obj:将代替function类里的this对象

parms:这是一个参数列表

当即执行

apply:

function.apply(obj,args)

obj:将代替function类里this对象

args:数组,它将做为参数传给function

当即执行

bind:

function.bind(obj,arg1,arg2,...)

不会当即执行,而是返回一个新的函数

3. 被忽略的this

若是将null或者undefined做为this的绑定对象传入call、apply或者bind,这些值在调用时会被忽略,实际应用的是默认绑定规则。

很是常见的作法是使用apply来“展开”一个数组,并看成参数传入一个函数如:求数组最大最小值,合并数组等,具体用法以下:

var min = Math.min.apply(null, arr);
var max = Math.max.apply(null, arr);
Array.prototype.push.apply(arrA, arrB);

箭头函数不使用this的四种绑定规则,而是根据外层(函数或者全局)做用域来决定this的指向。

箭头函数中的this只和定义它的做用域的this有关,而与在哪里以及如何调用它无关,同时它的this指向是不能够改变的。

本部分摘自:https://mp.weixin.qq.com/s/31HlZRug9RjcKBXCtfbBXA

12、函数式编程

函数式编程的历史已经很悠久了,可是最近几年却频繁的出如今大众的视野,不少不支持函数式编程的语言也在积极加入闭包,匿名函数等很是典型的函数式编程特性。大量的前端框架也标榜本身使用了函数式编程的特性,好像一旦跟函数式编程沾边,就很高大上同样,并且还有一些专门针对函数式编程的框架和库,好比:RxJS、cycleJS、ramdaJS、lodashJS、underscoreJS等。函数式编程变得愈来愈流行,掌握这种编程范式对书写高质量和易于维护的代码都大有好处。

函数式编程经常使用核心概念

•纯函数

•函数的柯里化(柯里化是一种“预加载”函数的方法,经过传递较少的参数,获得一个已经记住了这些参数的新函数,某种意义上讲,这是一种对参数的“缓存”,是一种很是高效的编写函数的方法。)

•函数组合

•Point Free

•声明式与命令式代码

//命令式
let CEOs = [];
for (var i = 0; i < companies.length; i++) {
    CEOs.push(companies[i].CEO)
}
//声明式
let CEOs = companies.map(c => c.CEO);

简单来讲,也就是当一个函数的输出不受外部环境影响,同时也不影响外部环境时,该函数就是纯函数,也就是它只关注逻辑运算和数学运算,同一个输入总获得同一个输出。

javascript内置函数有很多纯函数,也有很多非纯函数。

纯函数:

Array.prototype.slice

Array.prototype.map

String.prototype.toUpperCase

非纯函数:

Math.random

Date.now

Array.ptototype.splice

调用数组的slice方法每次返回的结果彻底相同,同时数组不会被改变,而调用splice方法每次返回值都不同,同时会改变原数组。

这就是咱们强调使用纯函数的缘由,由于纯函数相对于非纯函数来讲,在可缓存性、可移植性、可测试性以及并行计算方面都有着巨大的优点。

把一个函数变纯的基本手段是不要依赖系统状态。

本部分参考连接:https://www.cnblogs.com/fengyuqing/p/functional_programming_1.html    http://www.javashuo.com/article/p-axsmkixw-ke.html

十3、手动实现parseInt

parseInt是ECMAScript核心的一个全局函数,能够在实现了ECMAScript的宿主环境全局调用。

console.log(parseInt('12'));
console.log(parseInt('08'));
console.log(parseInt('0x16'));
console.log(parseInt('-12'));
console.log(parseInt('   -12'));
console.log(parseInt('   -  12'));
console.log(parseInt('124ref'));
console.log(parseInt('ref'));

parseInt(string, [int radix])第二个形参是能够忽略的,忽略时默认赋值为10也就是十进制。

radix就是指定第一个形参的进制类型,而后根据这个进制类型再转换为十进制整数

radix形参没指定的时候是10,有效范围:[2, 36]和特殊值0
1. 将第一个形参转换为字符串
2. 识别string转换是否有code unit,若是有 -, -标记为负数,0x或0X则把radix赋值为16
3. radix形参(int类型)是否存在,存在则从新赋值(会对实参进行Int32转化,没法转换成int类型则不会从新赋值radix)
4. radix为0,则设置radix为默认值10
5. 若是radix为1,或者大于等于37,parseInt直接返回NaN
6. 若是radix为[2, 36]时则表明,string参数分别是二进制,三进制(若是有得话~)…三十六进制类型
7. 而后对string进行的radix进制进行十进制转换,例如,按二进制对string来进行十进制转换

['1', '2', '3'].map(parseInt) //[1, NaN, NaN]

//内部执行的剖析
(function (){
        var ret = ['1', '2', '3'].map((value, index)=>{
            console.log(value, index);
            return parseInt(value, index);
        });
        console.log(ret);
    })();

//所以,其实是
parseInt('1', 0);
parseInt('2', 1);
parseInt('3', 2);

parseInt('13', 2),这个结果是……1,由于string参数若是最开始的code符合radix进制的话是能够进行解析转换的,正如这里’1’是符合二进制的,’3’是不符合二进制的,但1处于优先位置,因此能够进行转换解析,而3被无情地忽略~

function l(obj) {
   return console.log(obj)
}
function _parseInt(str,radix){
        var res = 0;
        if(typeof str !="string" && typeof str !="number"){
            return NaN;
        }
        str =String(str).trim().split(".")[0];
        // l(str)
        let len = str.length;
        if(!len){
            return NaN;
        }
        if(!radix){
            return radix = 10;
        }
        if(typeof radix !=="number" || radix < 2 || radix >36){
            return NaN;
        }
        for(let i = 0; i < len; i++){
            let arr = str.split("");
           l(arr instanceof Array)
            l(typeof arr)
            res += Math.floor(arr[i])*Math.pow(radix,i)
        }
        l(res);
}
_parseInt("654646",10)

十4、为何会有同源策略

同源策略是浏览器的一个安全功能,不一样源的客户端脚本在没有明确受权的状况下,不能读写对方资源。只有同一个源的脚本赋予dom、读写cookie、session、ajax等操做的权限。url由协议、域名、端口和路径组成、若是两个url的协议、域名和端口相同,则这两个url是同源的。限制来源不用源的“document”,对当前的“document”读取或设置某些属性。在不受同源策略限制,带有“src”属性的标签加载是,其实是由游览器发起一次GET请求,不一样于XMLHTTPRequest,它们经过src属性加载的资源。但游览器限制了JavaScript的权限,使其不能读,写其中返回的内容。

若是没有同源策略,不一样源的数据和资源(如HTTP头、Cookie、DOM、localStorage等)就能相互随意访问,根本没有隐私和安全可言。为了安全起见和资源的有效管理,浏览器固然要采用这种策略。

同源策略是一种约定,它是浏览器最核心和最基本的安全功能,能够用于隔离潜在恶意文件,若是没有了同源策略,浏览器的正常使用将受到影响。

浏览器采用同源策略,禁止页面加载或执行与自身不一样源的任何脚本。若是没有同源策略,那么恶意网页能够读取银行网站、网上商城等里面的用户信息,甚至篡改帐号密码等。因此全部支持JavaScript的浏览器都采用了同源策略。

十5、怎么判断两个对象是否相等

ES6有一个方法来判断两个对象是否相等 console.log(Object.is(a,b)) ,可是这个相等,和咱们平时要的相等可能不同,这个方法判断的是a和b是否是同一个指针的对象。

var a = {
  id:1
};
var b = a;
console.log(Object.is(a,b));   //true

//当咱们只须要两个对象的内容相同的时候,他就没效果了
var a = {
  id:1
};
var b = {
  id:1
}
console.log(Object.is(a,b));   //false

思路:只要两个对象的名和键值都相同。那么两个对象的内容就相同了(考虑若是键值也是对象的状况——用递归,递归的时候要判断prop是否是Object)

1.用Object.getOwnPropertyNames拿到对象的因此键名数组
2.比对键名数组的长度是否相等。否=>false。真=>3
3.比对键名对应的键值是否相等

function isObjectValueEqual(a, b) {
   var aProps = Object.getOwnPropertyNames(a);
   var bProps = Object.getOwnPropertyNames(b);
   if (aProps.length != bProps.length) {
      return false;
   }
   for (var i = 0; i < aProps.length; i++) {
      var propName = aProps[i]
      var propA = a[propName]
      var propB = b[propName]
      if (propA !== propB) {
        if ((typeof (propA) === 'object')) {
          if (this.isObjectValueEqual(propA, propB)) {
            return true
          } else {
            return false
          }
        } else {
          return false
        }
      } else {
        return false
      }
    }
        return true;
    }
    var a = {
        id:1,
        name:2,
        c:{
            age:3
        }
    };
    var b = {
        id:1,
        name:2,
        c:{
            age:3
        }
    }
    console.log(isObjectValueEqual(a,b));//true

本部分摘自:https://www.jianshu.com/p/7407bd65b15d

十6、事件模型

1.原始事件模型(DOM0级)

这是一种被全部浏览器都支持的事件模型,对于原始事件而言,没有事件流,事件一旦发生将立刻进行处理,有两种方式能够实现原始事件:

(1)在html代码中直接指定属性值:<button id="demo" type="button" onclick="doSomeTing()" />  

(2)在js代码中为 document.getElementsById("demo").onclick = doSomeTing()

优势:全部浏览器都兼容

缺点:1)逻辑与显示没有分离;2)相同事件的监听函数只能绑定一个,后绑定的会覆盖掉前面的,如:a.onclick = func1; a.onclick = func2;将只会执行func2中的内容。3)没法经过事件的冒泡、委托等机制完成更多事情。

由于这些缺点,虽然原始事件类型兼容全部浏览器,但仍不推荐使用。

2.DOM2事件模型

此模型是W3C制定的标准模型,现代浏览器(IE6~8除外)都已经遵循这个规范。W3C制定的事件模型中,一次事件的发生包含三个过程:(1).事件捕获阶段,(2).事件目标阶段,(3).事件冒泡阶段

事件捕获:当某个元素触发某个事件(如onclick),顶层对象document就会发出一个事件流,随着DOM树的节点向目标元素节点流去,直到到达事件真正发生的目标元素。在这个过程当中,事件相应的监听函数是不会被触发的。

事件目标:当到达目标元素以后,执行目标元素该事件相应的处理函数。若是没有绑定监听函数,那就不执行。

事件冒泡:从目标元素开始,往顶层元素传播。途中若是有节点绑定了相应的事件处理函数,这些函数都会被一次触发。

全部的事件类型都会经历事件捕获可是只有部分事件会经历事件冒泡阶段,例如submit事件就不会被冒泡。 

事件的传播是能够阻止的:
  • 在W3c中,使用stopPropagation()方法
  • 在IE下设置cancelBubble = true;
  在捕获的过程当中stopPropagation();后,后面的冒泡过程就不会发生了。

标准的事件监听器该如何绑定:

addEventListener("eventType","handler","true|false");其中eventType指事件类型,注意不要加‘on’前缀,与IE下不一样。第二个参数是处理函数,第三个即用来指定是否在捕获阶段进行处理,通常设为false来与IE保持一致(默认设置),除非你有特殊的逻辑需求。监听器的解除也相似:removeEventListner("eventType","handler","true!false");

3.IE事件模型

IE不把该对象传入事件处理函数,因为在任意时刻只会存在一个事件,因此IE把它做为全局对象window的一个属性,为求证其真伪,使用IE8执行代码alert(window.event),结果弹出是null,说明该属性已经定义,只是值为null(与undefined不一样)。难道这个全局对象的属性是在监听函数里才加的?因而执行下面代码:

    window.onload = function (){alert(window.event);}

    setTimeout(function(){alert(window.event);},2000);

结果第一次弹出【object event】,两秒后弹出依然是null。因而可知IE是将event对象在处理函数中设为window的属性,一旦函数执行结束,便被置为null了。IE的事件模型只有两步,先执行元素的监听函数,而后事件沿着父节点一直冒泡到document。冒泡已经讲解过了,这里不重复。IE模型下的事件监听方式也挺独特,绑定监听函数的方法是:attachEvent( "eventType","handler"),其中evetType为事件的类型,如onclick,注意要加’on’。解除事件监听器的方法是 detachEvent("eventType","handler" )

IE的事件模型已经能够解决原始模型的三个缺点,但其本身的缺点就是兼容性,只有IE系列浏览器才能够这样写。

以上就是3种事件模型,在咱们写代码的时候,为了兼容ie,一般使用如下写法:

var demo = document.getElementById('demo');
  if(demo.attachEvent){
     demo.attachEvent('onclick',func);
  }else{
      demo.addEventListener('click',func,false);
}

事件被封装成一个event对象,包含了该事件发生时的全部相关信息(event的属性)以及能够对事件进行的操做(event的方法)。

1. 事件定位相关属性

x/y与clientX/clientY值同样,表示距浏览器可视区域(工具栏除外区域)左/上的距离;

pageX/pageY,距页面左/上的距离,它与clientX/clientY的区别是不随滚动条的位置变化;

screenX/screenY,距计算机显示器左/上的距离,拖动你的浏览器窗口位置能够看到变化;

layerX/layerY与offsetX/offsetY值同样,表示距有定位属性的父元素左/上的距离。

2.其余经常使用属性

target:发生事件的节点;

currentTarget:当前正在处理的事件的节点,在事件捕获或冒泡阶段;

timeStamp:事件发生的时间,时间戳。

bubbles:事件是否冒泡。

cancelable:事件是否能够用preventDefault()方法来取消默认的动做;

keyCode:按下的键的值;

3. event对象的方法

event. preventDefault()//阻止元素默认的行为,如连接的跳转、表单的提交;

event. stopPropagation()//阻止事件冒泡

event.initEvent()//初始化新事件对象的属性,自定义事件会用,不经常使用

event. stopImmediatePropagation()//能够阻止掉同一事件的其余优先级较低的侦听器的处理(这货表示没用过,优先级就不说明了,谷歌或者问度娘吧。)

event.target与event.currentTarget他们有什么不一样?

target在事件流的目标阶段;currentTarget在事件流的捕获,目标及冒泡阶段。只有当事件流处在目标阶段的时候,两个的指向才是同样的, 而当处于捕获和冒泡阶段的时候,target指向被单击的对象而currentTarget指向当前事件活动的对象(通常为父级)。

本部分摘自:http://www.javashuo.com/article/p-ghctezii-k.html

十7、window的onload事件和DOMContentLoaded

一、当 onload 事件触发时,页面上全部的DOM,样式表,脚本,图片,flash都已经加载完成了。

二、当 DOMContentLoaded 事件触发时,仅当DOM加载完成,不包括样式表,图片,flash。

onload事件是DOM事件,onDOMContentLoaded是HTML5事件。

onload事件会被样式表、图像和子框架阻塞,而onDOMContentLoaded不会。

当加载的脚本内容并不包含当即执行DOM操做时,使用onDOMContentLoaded事件是个更好的选择,会比onload事件执行时间更早。

十8、for...in迭代和for...of有什么区别

1. for…in 语句以原始插入顺序迭代对象的可枚举属性。

2. for…of 语句遍历可迭代对象定义要迭代的数据。

Object.prototype.objCustom = function() {}; 
Array.prototype.arrCustom = function() {};
let iterable = [3, 5, 7];
iterable.foo = 'hello';
//for in 会继承
for (let i in iterable) {
  console.log(i); // logs 0, 1, 2, "foo", "arrCustom", "objCustom"
}
for (let i in iterable) {
  if (iterable.hasOwnProperty(i)) {
    console.log(i); // logs 0, 1, 2, "foo"
  }
}
// for of
for (let i of iterable) {
  console.log(i); // logs 3, 5, 7
}

for...of循环是ES6引入的新的语法。

for...in遍历拿到的x是键(下标)。而for...of遍历拿到的x是值,但在对象中会提示不是一个迭代器报错。

let x;
let a = ['A','B','C'];
let b = {name: '刘德华',age: '18'};

console.log(a.length);
for(x of a){
  console.log(x); //A,B,C
}
for(x in a){
  console.log(x+':'+a[x]); //0:A,1:B,2:C
}
/*for(x of b){
  console.log(x); //报错
}*/
for(x in b){
  console.log(x); //name,age
}

a.name = "Hello";
for(x in a){
  console.log(x); //0,1,2,name
}
console.log(a.length); //3

for...in因为历史遗留问题,它遍历的其实是对象的属性名称,一个Array数据也是一个对象,数组中的每一个元素的索引被视为属性名称。

因此咱们能够看到使用for...in循环Array数组时,拿到的实际上是每一个元素的索引。以下,把name包括在内,可是Array的length属性却不包括在内,for...of循环则彻底修复了这些问题,它只循环集合自己的元素。

十9、函数柯里化

将一个低阶函数转换为高阶函数的过程就叫柯里化。好比对于加法操做: var add = (x, y) => x + y ,咱们能够这样柯里化

function curryingAdd(x) {
    return function (y) {
        return x + y
    }
}

只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数

 
 

// 经典面试题,实现一个add方法,使计算结果可以知足以下预期:

add(1)(2)(3) = 6;

add(1, 2, 3)(4) = 10;

add(1)(2)(3)(4)(5) = 15;

function add() {
    // 第一次执行时,定义一个数组专门用来存储全部的参数
    var _args = Array.prototype.slice.call(arguments);
    // 在内部声明一个函数,利用闭包的特性保存_args并收集全部的参数值
    var _adder = function() {
        _args.push(...arguments);
        return _adder;
    };

    // 利用toString隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
    _adder.toString = function () {
        return _args.reduce(function (a, b) {
            return a + b;
        });
    }
    return _adder;
}

二10、call apply区别,原生实现bind

call,apply,bind 三者用法和区别:角度可为参数、绑定规则(显示绑定和强绑定),运行效率、运行状况。

//bind的实现就是柯里化
Function.prototype.bind = function (context) {
    var _this = this
    var args = Array.prototype.slice.call(arguments, 1)
    return function() {
        return _this.apply(context, args)
    }
}

 

Function.prototype.myCall = function(context = window, ...rest) {
    context.fn = this; //此处this是指调用myCall的function
    let result = context.fn(...rest);
    //将this指向销毁
    delete context.fn;
    return result;
};
 
 
Function.prototype.myApply = function(context = window, params = []) {
    context.fn = this; //此处this是指调用myCall的function
    let result
    if (params.length) {
        result = context.fn(...params)
    }else {
        result = context.fn()
    }
    //将this指向销毁
    delete context.fn;
    return result;
};

 

Function.prototype.myBind = function(thisArg) {
  if (typeof this !== 'function') {
    return;
  }
  var _self = this;
  var args = Array.prototype.slice.call(arguments, 1)
  var fnBound = function () {
    // 检测 New
    // 若是当前函数的this指向的是构造函数中的this 则断定为new 操做
    var _this = this instanceof _self ? this : thisArg;
    return _self.apply(_this, args.concat(Array.prototype.slice.call(arguments)));
  }
  // 为了完成 new操做
  // 还须要作一件事情 执行原型 连接 (思考题,为何?
  fnBound.prototype = this.prototype;
  return fnBound;
} 

二11、async/await

async/await特色

  1. async/await更加语义化,async 是“异步”的简写,async function 用于申明一个 function 是异步的; await,能够认为是async wait的简写, 用于等待一个异步方法执行完成;

  2. async/await是一个用同步思惟解决异步问题的方案(等结果出来以后,代码才会继续往下执行)

  3. 能够经过多层 async function 的同步写法代替传统的callback嵌套

async function语法

  • 自动将常规函数转换成Promise,返回值也是一个Promise对象

  • 只有async函数内部的异步操做执行完,才会执行then方法指定的回调函数

  • 异步函数内部能够使用await

await语法

  • await 放置在Promise调用以前,await 强制后面点代码等待,直到Promise对象resolve,获得resolve的值做为await表达式的运算结果

  • await只能在async函数内部使用,用在普通函数里就会报错

const timeoutFn = function(timeout){ 
    return new Promise(function(resolve){
        return setTimeout(resolve, timeout);
               });
}

async function fn(){
    await timeoutFn(1000);
    await timeoutFn(2000);
    return '完成';
}

fn().then(success => console.log(success));
本部分参考连接: https://www.jianshu.com/p/1e75bd387aa0

二12、当即执行函数和使用场景

你的代码在页面加载完成以后,不得不执行一些设置工做,好比时间处理器,建立对象等等。全部的这些工做只须要执行一次,好比只须要显示一个时间。可是这些代码也须要一些临时的变量,可是初始化过程结束以后,就不再会被用到,若是将这些变量做为全局变量,不是一个好的注意,咱们能够用当即执行函数——去将咱们全部的代码包裹在它的局部做用域中,不会让任何变量泄露成全局变量。

二十3、设计模式(要求说出如何实现,应用,优缺点)/单例模式实现

1. 单例模式

单例模式的定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。实现的方法为先判断实例存在与否,若是存在则直接返回,若是不存在就建立了再返回,这就确保了一个类只有一个实例对象。

适用场景:一个单一对象。好比:弹窗,不管点击多少次,弹窗只应该被建立一次。

// 单例模式
var Singleton = function(name){
    this.name = name;
};
Singleton.prototype.getName = function(){
    return this.name;
}
// 获取实例对象,代理实现单例模式
var getInstance = (function() {
    var instance = null;
    return function(name) {
        if(!instance) {
            instance = new Singleton(name);
        }
        return instance;
    }
})();
// 测试单例模式的实例
var a = getInstance("aa");
var b = getInstance("bb");
// 实现单例模式弹窗
var createWindow = (function(){
    var div;
    return function(){
        if(!div) {
            div = document.createElement("div");
            div.innerHTML = "我是弹窗内容";
            div.style.display = 'none';
            document.body.appendChild(div);
        }
        return div;
    }
})();
document.getElementById("Id").onclick = function(){
    // 点击后先建立一个div元素
    var win = createWindow();
    win.style.display = "block";
}

2. 策略模式

策略模式的定义:定义一系列的算法,把他们一个个封装起来,而且使他们能够相互替换。

一个基于策略模式的程序至少由两部分组成。第一个部分是一组策略类(可变),策略类封装了具体的算法,并负责具体的计算过程。第二个部分是环境类Context(不变),Context接受客户的请求,随后将请求委托给某一个策略类。要作到这一点,说明Context中要维持对某个策略对象的引用。
/*策略类*/
var levelOBJ = {
    "A": function(money) {
        return money * 4;
    },
    "B" : function(money) {
        return money * 3;
    },
    "C" : function(money) {
        return money * 2;
    } 
};
/*环境类*/
var calculateBouns =function(level,money) {
    return levelOBJ[level](money);
};
console.log(calculateBouns('A',10000)); // 40000

3. 代理模式

代理模式的定义:为一个对象提供一个代用品或占位符,以便控制对它的访问。

经常使用的虚拟代理形式:某一个花销很大的操做,能够经过虚拟代理的方式延迟到这种须要它的时候才去建立(例:使用虚拟代理实现图片懒加载)

图片懒加载的方式:先经过一张loading图占位,而后经过异步的方式加载图片,等图片加载好了再把完成的图片加载到img标签里面。

var imgFunc = (function() {
    var imgNode = document.createElement('img');
    document.body.appendChild(imgNode);
    return {
        setSrc: function(src) {
            imgNode.src = src;
        }
    }
})();
var proxyImage = (function() {
    var img = new Image();
    img.onload = function() {
        imgFunc.setSrc(this.src);
    }
    return {
        setSrc: function(src) {
            imgFunc.setSrc('./loading,gif');
            img.src = src;
        }
    }
})();
proxyImage.setSrc('./pic.png');

4. 装饰者模式

装饰者模式的定义:在不改变对象自身的基础上,在程序运行期间给对象动态地添加方法。

例如:现有4种型号的自行车分别被定义成一个单独的类,若是给每辆自行车都加上前灯、尾灯、铃铛这3个配件,若是用类继承的方式,须要建立4*3=12个子类。但若是经过装饰者模式,只须要建立3个类。

装饰者模式适用的场景:原有方法维持不变,在原有方法上再挂载其余方法来知足现有需求;函数的解耦,将函数拆分红多个可复用的函数,再将拆分出来的函数挂载到某个函数上,实现相同的效果但加强了复用性。

//用AOP装饰函数实现装饰者模式
Function.prototype.before = function(beforefn) {
    var self = this;    //保存原函数引用
    return function(){  //返回包含了原函数和新函数的 '代理函数'
        beforefn.apply(this, arguments);    //执行新函数,修正this
        return self.apply(this,arguments);  //执行原函数
    }
}
Function.prototype.after = function(afterfn) {
    var self = this;
    return function(){
        var ret = self.apply(this,arguments);
        afterfn.apply(this, arguments);
        return ret;
    }
}
var func = function() {
    console.log('2');
}
//func1和func3为挂载函数
var func1 = function() {
    console.log('1');
}
var func3 = function() {
    console.log('3');
}
func = func.before(func1).after(func3);
func();

本部分摘自:http://www.javashuo.com/article/p-wzevjtae-dm.html    http://www.javashuo.com/article/p-ovztdblg-gm.html

二十4、iframe的缺点有哪些

iframe的优势:
1.iframe可以原封不动的把嵌入的网页展示出来。
2.若是有多个网页引用iframe,那么你只须要修改iframe的内容,就能够实现调用的每个页面内容的更改,方便快捷。
3.网页若是为了统一风格,头部和版本都是同样的,就能够写成一个页面,用iframe来嵌套,能够增长代码的可重用。
4.若是遇到加载缓慢的第三方内容如图标和广告,这些问题能够由iframe来解决。
iframe的缺点:
1.会产生不少页面, 不容易管理
2.iframe框架结构有时会让人感到迷惑,若是框架个数多的话,可能会出现上下、左右滚动条,会分散访问者的注意力, 用户体验度差
3.代码复杂,没法被一些搜索引擎索引到,这一点很关键,如今的搜索引擎爬虫还不能很好的处理iframe中的内容,因此使用iframe会 不利于搜索引擎优化
4.不少的移动设备(PDA手机)没法彻底显示框架, 设备兼容性差。
5.iframe框架页面会 增长服务器的http请求,对于大型网站是不可取的。
分析了这么多, 如今基本上都是用Ajax来代替iframe,因此iframe已经渐渐的退出了前端开发

二十5、数组问题

1. 数组操做

shift:删除原数组第一项,并返回删除元素的值;若是数组为空则返回undefined

unshift:将参数添加到原数组开头,并返回数组的长度

pop:删除原数组最后一项,并返回删除元素的值;若是数组为空则返回undefined

push:将参数添加到原数组末尾,并返回数组的长度 

concat:返回一个新数组,是将参数添加到原数组中构成的

splice(start,deleteCount,val1,val2,...):从start位置开始删除deleteCount项,并从该位置起插入val1,val2,..会改变原数组

reverse:将数组反序

sort(orderfunction):按指定的参数对数组进行排序

slice(start,end):返回从原数组中指定开始下标到结束下标之间的项组成的新数组

join(separator):将数组的元素组起一个字符串,以separator为分隔符,省略的话则用默认用逗号为分隔符

var a = [1,2,3,4,5];   
var b = a.shift(); //a:[2,3,4,5] b:1  

var a = [1,2,3,4,5];   
var b = a.unshift(-2,-1); //a:[-2,-1,1,2,3,4,5] b:7 

var a = [1,2,3,4,5];   
var b = a.pop(); //a:[1,2,3,4] b:5

var a = [1,2,3,4,5];   
var b = a.push(6,7); //a:[1,2,3,4,5,6,7] b:7

var a = [1,2,3,4,5];   
var b = a.concat(6,7); //a:[1,2,3,4,5] b:[1,2,3,4,5,6,7]

var a = [1,2,3,4,5];   
var b = a.splice(2,2,7,8,9); //a:[1,2,7,8,9,5] b:[3,4]   
var b = a.splice(0,1); //同shift   
a.splice(0,0,-2,-1); var b = a.length; //同unshift   
var b = a.splice(a.length-1,1); //同pop   
a.splice(a.length,0,6,7); var b = a.length; //同push 

var a = [1,2,3,4,5];   
var b = a.reverse(); //a:[5,4,3,2,1] b:[5,4,3,2,1]

var a = [1,2,3,4,5];   
var b = a.sort(); //a:[1,2,3,4,5] b:[1,2,3,4,5] 

var a = [1,2,3,4,5];   
var b = a.slice(2,5); //a:[1,2,3,4,5] b:[3,4,5]

var a = [1,2,3,4,5];   
var b = a.join("|"); //a:[1,2,3,4,5] b:"1|2|3|4|5" 

2. 数组遍历

for循环

使用临时变量,将长度缓存起来,避免重复获取数组长度,当数组较大时优化效果才会比较明显。

foreach循环

遍历数组中的每一项,没有返回值,对原数组没有影响,不支持IE

//1 没有返回值
arr.forEach((item,index,array)=>{
    //执行代码
})
//参数:value数组中的当前项, index当前项的索引, array原始数组;
//数组中有几项,那么传递进去的匿名回调函数就须要执行几回;
//基本用法
var ary = ["JavaScript", "Java", "CoffeeScript", "TypeScript"];
ary.forEach(function(value, index, _ary) {
    console.log(index + ": " + value);
    return false;
});

使用some或every能够中断循环

//使用some函数
var ary = ["JavaScript", "Java", "CoffeeScript", "TypeScript"];
ary.some(function (value, index, _ary) {
    console.log(index + ": " + value);
    return value === "CoffeeScript";
});
// logs:
//0: JavaScript 
//1: Java 
//2: CoffeeScript
//使用every
var ary = ["JavaScript", "Java", "CoffeeScript", "TypeScript"];
ary.every(function(value, index, _ary) {
    console.log(index + ": " + value);
    return value.indexOf("Script") > -1;
});
// logs:
//0: JavaScript 
//1: Java

补充:如何中断foreach循环呢?

//循环外使用try.. catch,当须要中断时throw 一个异常,而后catch进行捕获;
var BreakException = {};
try {
  [1, 2, 3].forEach(function(el) {
    console.log(el);
    if (el === 2) throw BreakException;
  });
} catch (e) {
  if (e !== BreakException) throw e;
}

map循环

有返回值,能够return出来

map的回调函数中支持return返回值;return的是啥,至关于把数组中的这一项变为啥(并不影响原来的数组,只是至关于把原数组克隆一份,把克隆的这一份的数组中的对应项改变了);

var ary = [12,23,24,42,1]; 
var res = ary.map(function (item,index,ary ) { 
    return item*10; 
}) 
console.log(res);//-->[120,230,240,420,10];  原数组拷贝了一份,并进行了修改
console.log(ary);//-->[12,23,24,42,1];  原数组并未发生变化

for of遍历

能够正确响应break、continue和return语句

for (var value of myArray) {
  console.log(value);
}
//基本用法
let arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
for (let el of arr) {
  console.log(el);
  if (el === 5) {
    break;
  }
}

filter遍历

不会改变原始数组,返回新数组

var arr = [
  { id: 1, text: 'aa', done: true },
  { id: 2, text: 'bb', done: false }
]
console.log(arr.filter(item => item.done))
//es5写法
arr.filter(function (item) {
  return item.done;
});
var arr = [73,84,56, 22,100]
var newArr = arr.filter(item => item>80)   //获得新数组 [84, 100]
console.log(newArr,arr)

reduce

reduce() 方法接收一个函数做为累加器(accumulator),数组中的每一个值(从左到右)开始缩减,最终为一个值。

var total = [0,1,2,3,4].reduce((a, b)=>a + b); //10
//reduce接受一个函数,函数有四个参数,分别是:上一次的值,当前值,当前值的索引,数组
[0, 1, 2, 3, 4].reduce(function(previousValue, currentValue, index, array){
 return previousValue + currentValue;
});
//reduce还有第二个参数,咱们能够把这个参数做为第一次调用callback时的第一个参数,上面这个例子由于没有第二个参数,因此直接从数组的第二项开始,若是咱们给了第二个参数为5,那么结果就是这样的:
[0, 1, 2, 3, 4].reduce(function(previousValue, currentValue, index, array){
 return previousValue + currentValue;
},5);

find

find()方法返回数组中符合测试函数条件的第一个元素。不然返回undefined 

stu.find((element) => (element.name == '李四'))
//es5
function getStu(element){
   return element.name == '李四'
}
stu.find(getStu)

keys,values,entries

ES6 提供三个新的方法 —— entries(),keys()和values() —— 用于遍历数组。它们都返回一个遍历器对象,能够用for...of循环进行遍历,惟一的区别是keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历

for (let index of ['a', 'b'].keys()) {
console.log(index);
}
// 0
// 1
for (let elem of ['a', 'b'].values()) {
console.log(elem);
}
// 'a'
// 'b'
for (let [index, elem] of ['a', 'b'].entries()) {
console.log(index, elem);
}
// 0 "a"
// 1 "b"

本部分参考连接:http://www.javashuo.com/article/p-qezqemuj-ek.html   http://www.javashuo.com/article/p-erqmascr-ee.html

对象遍历方法

for...in...

Object.keys(obj)

Object.values(obj)

Object.getOwnPropertyNames(obj)

const obj = {
      id:1,
      name:'zhangsan',
      age:18
}

for(let key  in obj){
      console.log(key + '---' + obj[key])
}

console.log(Object.keys(obj))
console.log(Object.values(obj))

Object.getOwnPropertyNames(obj).forEach(function(key){
      console.log(key+ '---'+obj[key])
})

二十6、BOM属性对象方法

 

二十7、服务端渲染

 

二十8、垃圾回收机制

什么是垃圾:通常来讲没有被引用的对象就是垃圾,就是要被清除, 有个例外若是几个对象引用造成一个环,互相引用,但根访问不到它们,这几个对象也是垃圾,也要被清除。

JavaScript 中的内存管理是自动执行的,并且是不可见的。咱们建立基本类型、对象、函数……全部这些都须要内存。

JavaScript 中内存管理的主要概念是可达性。

简单地说,“可达性” 值就是那些以某种方式可访问或可用的值,它们被保证存储在内存中。

1. 有一组基本的固有可达值,因为显而易见的缘由没法删除。例如:

  • 本地函数的局部变量和参数
  • 当前嵌套调用链上的其余函数的变量和参数
  • 全局变量
  • 还有一些其余的,内部的

这些值称为根。

2. 若是引用或引用链能够从根访问任何其余值,则认为该值是可访问的。

例如,若是局部变量中有对象,而且该对象具备引用另外一个对象的属性,则该对象被视为可达性, 它引用的那些也是能够访问的,详细的例子以下。

JavaScript 引擎中有一个后台进程称为垃圾回收器,它监视全部对象,并删除那些不可访问的对象。

// user 具备对象的引用
let user = {
  name: "John"
};
user = null; //此时,user 的值被覆盖,则引用丢失
//如今 John 变成不可达的状态,没有办法访问它,没有对它的引用。垃圾回收器将丢弃 John 数据并释放内存。

内存生命周期

JS环境中分配的内存通常有以下生命周期:

  • 内存分配:当咱们申明变量、函数、对象,并执行的时候,系统会自动为他们分配内存
  • 内存使用:即读写内存,也就是使用变量、函数等
  • 内存回收:使用完毕,由垃圾回收机制自动回收再也不使用的内存

基本的垃圾回收算法称为“标记-清除”,按期执行如下“垃圾回收”步骤:

  • 垃圾回收器获取根并“标记”(记住)它们。
  • 而后它访问并“标记”全部来自它们的引用。
  • 而后它访问标记的对象并标记它们的引用。全部被访问的对象都被记住,以便之后再也不访问同一个对象两次。
  • 以此类推,直到有未访问的引用(能够从根访问)为止。
  • 除标记的对象外,全部对象都被删除。

一些优化:

  • 分代回收——对象分为两组:“新对象”和“旧对象”。许多对象出现,完成它们的工做并迅速结 ,它们很快就会被清理干净。那些活得足够久的对象,会变“老”,而且不多接受检查。
  • 增量回收——若是有不少对象,而且咱们试图一次遍历并标记整个对象集,那么可能会花费一些时间,并在执行中会有必定的延迟。所以,引擎试图将垃圾回收分解为多个部分。而后,各个部分分别执行。这须要额外的标记来跟踪变化,这样有不少微小的延迟,而不是很大的延迟。
  • 空闲时间收集——垃圾回收器只在 CPU 空闲时运行,以减小对执行的可能影响。

本部分摘自:https://segmentfault.com/a/1190000018605776?utm_source=tag-newest

二十9、eventloop

 Javascript的事件分为同步任务和异步任务.

 遇到同步任务就放在执行栈中执行.

 遇到异步任务就放到任务队列之中,等到执行栈执行完毕以后再去执行任务队列之中的事件。

JS调用栈

Javascript 有一个 主线程(main thread)和 调用栈(call-stack),全部的代码都要经过函数,放到调用栈(也被称为执行栈)中的任务等待主线程执行。

JS调用栈采用的是后进先出的规则,当函数执行的时候,会被添加到栈的顶部,当执行栈执行完成后,就会从栈顶移出,直到栈内被清空。

WebAPIs

MDN的解释: Web 提供了各类各样的 API 来完成各类的任务。这些 API 能够用 JavaScript 来访问,令你能够作不少事儿,小到对任意 window 或者 element作小幅调整,大到使用诸如 WebGL 和 Web Audio 的 API 来生成复杂的图形和音效。

Web API 接口参考

总结: 就是浏览器提供一些接口,让JavaScript能够调用,这样就能够把任务甩给浏览器了,这样就能够实现异步了!

任务队列(Task Queue)

"任务队列"是一个先进先出的数据结构,排在前面的事件,优先被主线程读取。主线程的读取过程基本上是自动的,只要执行栈一清空,"任务队列"上第一位的事件就自动进入主线程。可是,若是存在"定时器",主线程首先要检查一下执行时间,某些事件只有到了规定的时间,才能返回主线程。

同步任务和异步任务

Javascript单线程任务被分为同步任务和异步任务.

  • 同步任务会在调用栈 中按照顺序等待主线程依次执行.
  • 异步任务会甩给在WebAPIs处理,处理完后有告终果后,将注册的回调函数放入任务队列中等待主线程空闲的时候(调用栈被清空),被读取到栈内等待主线程的执行。

宏任务(MacroTask)和 微任务(MicroTask):在JavaScript中,任务被分为两种,一种宏任务(MacroTask)也叫Task,一种叫微任务(MicroTask)。

宏任务(MacroTask):script(总体代码)setTimeoutsetIntervalsetImmediate(浏览器暂时不支持,只有IE10支持,具体可见MDN)、I/OUI Rendering

微任务(MicroTask):Process.nextTick(Node独有)PromiseObject.observe(废弃)MutationObserver(具体使用方式查看这里

注意:只要主线程空了,就会去读取"任务队列",这就是JavaScript的运行机制。这个过程会不断重复。

console.log('script start')

async function async1() {
  await async2()
  console.log('async1 end')
}
async function async2() {
  console.log('async2 end') 
}
async1()

setTimeout(function() {
  console.log('setTimeout')
}, 0)

new Promise(resolve => {
  console.log('Promise')
  resolve()
})
  .then(function() {
    console.log('promise1')
  })
  .then(function() {
    console.log('promise2')
  })

console.log('script end')
//script start
//async2 end
//Promise
//script end
//async1 end
//promise1
//promise2
//undefined
//setTimeout
  • 首先,打印script start,调用async1()时,返回一个Promise,因此打印出来async2 end
  • 每一个 await,会新产生一个promise,但这个过程自己是异步的,因此该await后面不会当即调用。
  • 继续执行同步代码,打印Promisescript end,将then函数放入微任务队列中等待执行。
  • 同步执行完成以后,检查微任务队列是否为null,而后按照先入先出规则,依次执行。
  • 而后先执行打印promise1,此时then的回调函数返回undefinde,此时又有then的链式调用,又放入微任务队列中,再次打印promise2
  • 再回到await的位置执行返回的 Promiseresolve 函数,这又会把 resolve 丢到微任务队列中,打印async1 end
  • 微任务队列为空时,执行宏任务,打印setTimeout

浏览器执行过程

执行完主执行线程中的任务。
取出Microtask Queue中任务执行直到清空。
取出Macrotask Queue中一个任务执行。
取出Microtask Queue中任务执行直到清空。
重复3和4。

即为同步完成,一个宏任务,全部微任务,一个宏任务,全部微任务......

console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
Promise.resolve().then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
});
console.log('script end');
//结果
//script start
//script end
//promise1
//promise2
//setTimeout

解析:一开始task队列中只有script,则script中全部函数放入函数执行栈执行,代码按顺序执行。
接着遇到了setTimeout,它的做用是0ms后将回调函数放入task队列中,也就是说这个函数将在下一个事件循环中执行(注意这时候setTimeout执行完毕就返回了)。
接着遇到了Promise,按照前面所述Promise属于microtask,因此第一个.then()会放入microtask队列。
当全部script代码执行完毕后,此时函数执行栈为空。开始检查microtask队列,此时队列不为空,执行.then()的回调函数输出'promise1',因为.then()返回的依然是promise,因此第二个.then()会放入microtask队列继续执行,输出'promise2'。
此时microtask队列为空了,进入下一个事件循环,检查task队列发现了setTimeout的回调函数,当即执行回调函数输出'setTimeout',代码执行完毕。

以上即是浏览器事件循环的过程

Node.js执行过程

事件循环能让 Node.js 执行非阻塞 I/O 操做,尽管JavaScript事实上是单线程的,经过在可能的状况下把操做交给操做系统内核来实现。

因为大多数现代系统内核是多线程的,内核能够处理后台执行的多个操做。当其中一个操做完成的时候,内核告诉 Node.js,相应的回调就被添加到轮询队列(poll queue)并最终获得执行。

在node中事件每一轮循环按照顺序分为6个阶段,来自libuv的实现:

  • timers 阶段: 这个阶段执行setTimeout(callback) and setInterval(callback)预约的callback;
  • I/O callbacks 阶段:  是否有已完成的I/O操做的回调函数,来自上一轮的poll残留;
  • idle, prepare 阶段: 仅node内部使用;
  • poll 阶段: 获取新的I/O事件, 适当的条件下node将阻塞在这里;
  • check 阶段: 执行setImmediate() 设定的callbacks;
  • close callbacks 阶段: 好比socket.on(‘close’, callback)的callback会在这个阶段执行.

每个阶段都有一个装有callbacks的fifo queue(队列),当event loop运行到一个指定阶段时,node将执行该阶段的fifo queue(队列),当队列callback执行完或者执行callbacks数量超过该阶段的上限时,event loop会转入下一下阶段.

上面六个阶段都不包括 process.nextTick(),process.nextTick不是基于libuv事件机制的,而timers一系列的api所有是基于libuv开放出来的api实现的。

定时器(timers)

定时器的用途是让指定的回调函数在某个阈值后会被执行,具体的执行时间并不必定是那个精确的阈值。定时器的回调会在制定的时间事后尽快获得执行,然而,操做系统的计划或者其余回调的执行可能会延迟该回调的执行。

轮询(poll)

轮询阶段有两个主要功能:
1,执行已经到时的定时器脚本
2,处理轮询队列中的事件

当事件循环进入到轮询阶段却没有发现定时器时:
若是轮询队列非空,事件循环会迭代回调队列并同步执行回调,直到队列空了或者达到了上限(前文说过的根据操做系统的不一样而设定的上限)。
若是轮询队列是空的:
若是有setImmediate()定义了回调,那么事件循环会终止轮询阶段并进入检查阶段去执行定时器回调;
若是没有setImmediate(),事件回调会等待回调被加入队列并当即执行。
一旦轮询队列空了,事件循环会查找已经到时的定时器。若是找到了,事件循环就回到定时器阶段去执行回调。

I/O callbacks

这个阶段执行一些诸如TCP错误之类的系统操做的回调。例如,若是一个TCP socket 在尝试链接时收到了 ECONNREFUSED错误,某些 *nix 系统会等着报告这个错误。这个就会被排到本阶段的队列中。

检查(check)

这个阶段容许回调函数在轮询阶段完成后当即执行。若是轮询阶段空闲了,而且有回调已经被 setImmediate() 加入队列,事件循环会进入检查阶段而不是在轮询阶段等待。
setImmediate() 是个特殊的定时器,在事件循环中一个单独的阶段运行。它使用libuv的API 来使得回调函数在轮询阶段完成后执行。

关闭事件的回调(close callbacks)

若是一个 socket 或句柄(handle)被忽然关闭(is closed abruptly),例如 socket.destroy(), 'close' 事件会被发出到这个阶段。不然这种事件会经过 process.nextTick() 被发出。

setTimeout VS setImmediate

两者很是类似,可是两者区别取决于他们何时被调用.

setImmediate 设计在poll阶段完成时执行,即check阶段;
setTimeout 设计在poll阶段为空闲时,且设定时间到达后执行;但其在timer阶段执行
其两者的调用顺序取决于当前event loop的上下文,若是他们在异步i/o callback以外调用,其执行前后顺序是不肯定的

setTimeout(function timeout () {
  console.log('timeout');
},0);
setImmediate(function immediate () {
  console.log('immediate');
});
$ node timeout_vs_immediate.js
timeout
immediate
$ node timeout_vs_immediate.js
immediate
timeout

为何结果不肯定呢?

解释:setTimeout/setInterval 的第二个参数取值范围是:[1, 2^31 - 1],若是超过这个范围则会初始化为 1,即 setTimeout(fn, 0) === setTimeout(fn, 1)。咱们知道 setTimeout 的回调函数在 timer 阶段执行,setImmediate 的回调函数在 check 阶段执行,event loop 的开始会先检查 timer 阶段,可是在开始以前到 timer 阶段会消耗必定时间,因此就会出现两种状况:

timer 前的准备时间超过 1ms,知足 loop->time >= 1,则执行 timer 阶段(setTimeout)的回调函数
timer 前的准备时间小于 1ms,则先执行 check 阶段(setImmediate)的回调函数,下一次 event loop 执行 timer 阶段(setTimeout)的回调函数
再看个例子:

setTimeout(() => {
  console.log('setTimeout')
}, 0)
setImmediate(() => {
  console.log('setImmediate')
})
const start = Date.now()
while (Date.now() - start < 10);
//结果
//setTimeout
//setImmediate

本部分摘自:http://www.javashuo.com/article/p-xacejgab-kn.html          浅析Nodejs Event Loop

三10、如何快速让字符串变成以千为精度的数字

toLocalString()

三11、正则表达式

 \ 作为转意,即一般在"\"后面的字符不按原来意义解释,如/b/匹配字符"b",当b前面加了反斜杆后/\b/,转意为匹配一个单词的边界。 
-或- 
对正则表达式功能字符的还原,如"*"匹配它前面元字符0次或屡次,/a*/将匹配a,aa,aaa,加了"\"后,/a\*/将只匹配"a*"。 
^ 匹配一个输入或一行的开头,/^a/匹配"an A",而不匹配"An a" 
$ 匹配一个输入或一行的结尾,/a$/匹配"An a",而不匹配"an A" 
* 匹配前面元字符0次或屡次,/ba*/将匹配b,ba,baa,baaa 
+ 匹配前面元字符1次或屡次,/ba*/将匹配ba,baa,baaa 
? 匹配前面元字符0次或1次,/ba*/将匹配b,ba 
(x) 匹配x保存x在名为$1...$9的变量中 
x|y 匹配x或y 
{n} 精确匹配n次 
{n,} 匹配n次以上 
{n,m} 匹配n-m次 
[xyz] 字符集(character set),匹配这个集合中的任一一个字符(或元字符) 
[^xyz] 不匹配这个集合中的任何一个字符 
[\b] 匹配一个退格符 
\b 匹配一个单词的边界 
\B 匹配一个单词的非边界 
\cX 这儿,X是一个控制符,/\cM/匹配Ctrl-M 
\d 匹配一个字数字符,/\d/ = /[0-9]/ 
\D 匹配一个非字数字符,/\D/ = /[^0-9]/ 
\n 匹配一个换行符 
\r 匹配一个回车符 
\s 匹配一个空白字符,包括\n,\r,\f,\t,\v等 
\S 匹配一个非空白字符,等于/[^\n\f\r\t\v]/ 
\t 匹配一个制表符 
\v 匹配一个重直制表符 
\w 匹配一个能够组成单词的字符(alphanumeric,这是个人意译,含数字),包括下划线,如[\w]匹配"$5.98"中的5,等于[a-zA-Z0-9] 
\W 匹配一个不能够组成单词的字符,如[\W]匹配"$5.98"中的$,等于[^a-zA-Z0-9]。

var pattern = /s$/; //正则表达式直接量
var pattern = new RegExp("s$"); //构造函数RegExp()

匹配Email地址的正则表达式:w+([-+.]w+)*@w+([-.]w+)*.w+([-.]w+)*

评注:表单验证时很实用

匹配网址URL的正则表达式:[a-zA-z]+://[^s]*

/^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\*\+,;=.]+$/

评注:网上流传的版本功能颇有限,上面这个基本能够知足需求

匹配账号是否合法(字母开头,容许5-16字节,容许字母数字下划线):^[a-zA-Z][a-zA-Z0-9_]{4,15}$

评注:表单验证时很实用

匹配国内电话号码:d{3}-d{8}|d{4}-d{7}

评注:匹配形式如 0511-4405222 或 021-87888822

匹配腾讯QQ号:[1-9][0-9]{4,}

评注:腾讯QQ号从10000开始

匹配中国邮政编码:[1-9]d{5}(?!d)

评注:中国邮政编码为6位数字

匹配身份证:d{15}|d{18}

评注:中国的身份证为15位或18位

手机号:/^1[3|4|5|8][0-9]\d{4,8}$/

^1表明以1开头,如今中国的手机号没有是其它开头的,之后不必定啊 
[3|4|5|8] 紧跟上面的1后面,能够是3或4或5或8的一个数字,若是之后出现190开始的手机号码了,就须要以下[3|4|5|8|9] 
[0-9]表示0-9中间的任何数字,能够是0或9 
\d{4,8} 这个\d跟[0-9]意思同样,都是0-9中间的数字。{4,8}表示匹配前面的最低4位数字最高8位数字。这里为何不是直接的8呢,由于手机号码归属地查询的时候,根据前7位就能够知道具体的地址了,后面的4位没有影响的。

三12、toString()、toLocaleString()、valueOf()

Array、Boolean、Date、Number等对象都具备toString()、toLocaleString()、valueOf()三个方法

1. JS Array

var array = new Array("niu","li","na");
console.log(array.valueOf());//Array【3】
console.log(array.toString());//niu,li,na
console.log(array.toLocaleString());//niu,li,na
  • valueOf:返回数组自己

  • toString():把数组转换为字符串,并返回结果,每一项以逗号分割。

  • toLocalString():把数组转换为本地数组,并返回结果。

2. JS Boolean

var boolean = new Boolean();
console.log(boolean.valueOf());//false
console.log(boolean.toString());//false
  • valueOf:返回 Boolean 对象的原始值。

  • toString():根据原始布尔值或者 booleanObject 对象的值返回字符串 "true" 或 "false"。默认为"false"。

  • toLocalString():Boolean对象没有toLocalString()方法。可是在Boolean对象上使用这个方法也不会报错。

3. JS Date

var date = new Date();
console.log(date.valueOf());
console.log(date.toString());
console.log(date.toLocaleString());

  • valueOf:返回 Date 对象的原始值,以毫秒表示。

  • toString():把 Date 对象转换为字符串,并返回结果。使用本地时间表示。

  • toLocalString():可根据本地时间把 Date 对象转换为字符串,并返回结果,返回的字符串根据本地规则格式化。

4. JS Math

console.log(Math.PI.valueOf());//3.141592653589793
  • valueOf:返回 Math 对象的原始值。 

5. JS Number

var num = new Number(1337);
console.log(num.valueOf());//1337
console.log(num.toString());//1337
console.log(num.toLocaleString());//1,337
  • valueOf:返回一个 Number 对象的基本数字值。

  • toString():把数字转换为字符串,使用指定的基数。

  • toLocalString():把数字转换为字符串,使用本地数字格式顺序。

6. JS String

var string = new String("abc");
console.log(string.valueOf());//abc
console.log(string.toString());//abc
  • valueOf:返回某个字符串对象的原始值。

  • toString():返回字符串。

toString()方法与toLocalString()方法区别:

  • toLocalString()是调用每一个数组元素的 toLocaleString() 方法,而后使用地区特定的分隔符把生成的字符串链接起来,造成一个字符串。

  • toString()方法获取的是String(传统字符串),而toLocaleString()方法获取的是LocaleString(本地环境字符串)。

  • 若是你开发的脚本在世界范围都有人使用,那么将对象转换成字符串时请使用toString()方法来完成。

  • LocaleString()会根据你机器的本地环境来返回字符串,它和toString()返回的值在不一样的本地环境下使用的符号会有微妙的变化。

  • 因此使用toString()是保险的,返回惟一值的方法,它不会由于本地环境的改变而发生变化。若是是为了返回时间类型的数据,推荐使用LocaleString()。如果在后台处理字符串,请务必使用toString()。

本部分摘自:http://www.javashuo.com/article/p-fthvgxvs-cs.html

未完待续·······

相关文章
相关标签/搜索