前端(二)JS篇

JS篇

js数据类型

一、js有几种数据类型,其中基本数据类型有哪些

七种数据类型
复制代码
  • Boolean
  • Null
  • Undefined
  • Number
  • String
  • Symbol (ECMAScript 6 新定义)
  • Object

(ES6以前)其中5种为基本类型:string,number,boolean,null,undefined
javascript

ES6出来的Symbol也是原始数据类型 ,表示独一无二的值
css

Object 为引用类型(范围挺大),也包括数组、函数html

二、null和undefined的差别

大致说一下,想要知其因此然请引擎搜索前端

相同点:java

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

差别:webpack

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

三、== 和 ===区别,什么状况用 ==

JS对象

一、原生对象及方法

1.一、改变原数组的方法

  • 改变原数组的方法:
pop、push、reverse、shift、sort、splice、unshift,以及两个ES6新增的方法copyWithin 和 fill;

复制代码
  • 不改变原数组(复制):
concat、join、slice、toString、toLocaleString、indexOf、lastIndexOf、
未标准的toSource以及ES7新增的方法includes;
复制代码
  • 循环遍历:
forEach、every、some、filter、map、reduce、reduceRight
以及ES6新增的方法entries、find、findIndex、keys、values。
复制代码

1.二、splice和slice、map和forEach、 filter()、reduce()的区别

  • 1.slice(start,end):方法能够从已有数组中返回选定的元素,返回一个新数组,包含从startend(不包含该元素)的数组方法 注意:该方法不会更新原数组,而是返回一个子数组
  • 2.splice():该方法想或者从数组中添加或删除项目,返回被删除的项目。(该方法会改变原数组) splice(index, howmany,item1,...itemx) ·index参数:必须,整数规定添加或删除的位置,使用负数,从数组尾部规定位置 ·howmany参数:必须,要删除的数量, ·item1..itemx:可选,向数组添加新项目
  • 3.map():会返回一个全新的数组。使用于改变数据值的时候。会分配内存存储空间数组并返回,forEach()不会返回数据
  • 4.forEach(): 不会返回任何有价值的东西,而且不打算改变数据,单纯的只是想用数据作一些事情,他容许callback更改原始数组的元素
  • 5.reduce(): 方法接收一个函数做为累加器,数组中的每个值(从左到右)开始缩减,最终计算一个值,不会改变原数组的值
  • 6.filter(): 方法建立一个新数组,新数组中的元素是经过检查指定数组中符合条件的全部元素。它里面经过function去作处理
  • String
方法 描述
charAt() 返回在指定位置的字符。
charCodeAt() 返回在指定的位置的字符的 Unicode 编码。
concat() 链接字符串。
indexOf() 检索字符串。
match() 找到一个或多个正则表达式的匹配。
replace() 替换与正则表达式匹配的子串。
search() 检索与正则表达式相匹配的值。
slice() 提取字符串的片段,并在新的字符串中返回被提取的部分。
split() 把字符串分割为字符串数组。
toLocaleLowerCase() 把字符串转换为小写。
toLocaleUpperCase() 把字符串转换为大写。
toLowerCase() 把字符串转换为小写。
toUpperCase() 把字符串转换为大写。
substr() 从起始索引号提取字符串中指定数目的字符。
substring() 提取字符串中两个指定的索引号之间的字符。

-** Array**es6

方法 描述
slice[start,end) 返回从原数组中指定开始下标到结束下标之间的项组成的新数组(不影响原数组)
: . 1个参数:n.即:n到末尾的全部
: . 2个参数:[start,end]
splice(): . 删除:2个参数,起始位置,删除的项数
. 插入:3个参数,起始位置,删除的项数,插入的项
. 替换:任意参数,起始位置,删除的项数,插入任意数量的项
pop() 删除数组的最后一个元素,减小数组的长度,返回删除的值。(无参)
push() 将参数加载到数组的最后,返回新数组的长度。 (参数不限)
shift() 删除数组的第一个元素,数组长度减1,返回删除的值。 (无参)
unshift() 向数组的开头添加一个或更多元素,并返回新的长度。(参数不限)
sort() 按指定的参数对数组进行排序 ,返回的值是通过排序以后的数组(无参/函数)
concat(3,4) 把两个数组拼接起来。 返回的值是一个副本 (参数不限)
join() 将数组的元素组起一个字符串,以separator为分隔符,省略的话则用默认用逗号为分隔符
indexOf() 从数组开头向后查找,接受两个参数,要查找的项(可选)和查找起点位置的索引
lastIndexOf() 从数组末尾开始向前查找,接受两个参数,要查找的项(可选)和查找起点位置的索引
every() 对数组中的每一项运行给定函数,若是该函数对每一项都返回true,则返回true。
filter() 对数组中的每一项运行给定函数,返回该函数会返回true的项组成数组。
forEach() 对数组的每一项运行给定函数,这个方法没有返回值。
map() 对数组的每一项运行给定函数,返回每次函数调用的结果组成的数组。
some() 对数组的每一项运行给定参数,若是该函数对任一项返回true,则返回true。以上方法都不会修改数组中的包含的值。
reduce()和reduceRight() 缩小数组的方法,这两个方法都会迭代数组的全部项,而后构建一个最终返回的值。
  • 正则
方法 描述
compile 编译正则表达式。
exec 检索字符串中指定的值。返回找到的值,并肯定其位置。
test 检索字符串中指定的值。返回 true 或 false。
search 检索与正则表达式相匹配的值。
match 找到一个或多个正则表达式的匹配。
replace 替换与正则表达式匹配的子串。
split 把字符串分割为字符串数组。

推荐:知道这20个正则表达式,能让你少写1,000行代码web

面试的一些 JavaScript 算法面试

二、建立对象的几种方法以及优缺点

原型 / 构造函数 / 实例

  • 原型(prototype): 一个简单的对象,用于实现对象的 属性继承。能够简单的理解成对象的爹。在 Firefox 和 Chrome 中,每一个JavaScript对象中都包含一个__proto__ (非标准)的属性指向它爹(该对象的原型),可obj.__proto__进行访问。正则表达式

  • 构造函数: 能够经过new来 新建一个对象 的函数。

  • 实例: 经过构造函数和new建立出来的对象,即是实例。 实例经过__proto__指向原型,经过constructor指向构造函数。

2.一、 工厂模式

工厂模式是软件工程领域一种广为人知的设计模式,这种模式抽象了建立具体对象的过程

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)

2.三、原型模式

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

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

复制代码

原型中全部属性是被实例共享的, 引用类型的属性会出问题

function Person(){
}

Person.prototype = {
    constructor: Person, // 重写原型必定要将constructor属性赋值为原构造函数,不然原型丢失
    name : "Nicholas",
    age : 29,
    job : "Software Engineer",
    friends : ["Shelby", "Court"],
    sayName : function () {
        alert(this.name);
    }
};

var person1 = new Person();
var person2 = new Person();

person1.friends.push("Van");

console.log(person1.friends);    //"Shelby,Court,Van"
console.log(person2.friends);    //"Shelby,Court,Van"
console.log(person1.friends === person2.friends);  //true

复制代码

2.四、组合使用构造函数模式和原型模式

建立自定义类型的最多见方式,就是组合使用构造函数模式与原型模式。构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。结果,每一个实例都会有本身的一份实例属性的副本,但同时又共享着对方法的引用,最大限度地节省了内存。

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中使用最普遍、认同度最高的一种建立自定义类型的方法。能够说,这是用来定义引用类型的一种默认模式。

2.五、动态原型模式

把全部信息都封装在了构造函数中,而经过在构造函数中初始化原型(仅在必要的状况下),又保持了同时使用构造函数和原型的优势。换句话说,能够经过检查某个应该存在的方法是否有效,来决定是否须要初始化原型。

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操做符肯定它的类型。

2.六、寄生构造函数模式

这种模式的基本思想是建立一个函数,该函数的做用仅仅是封装建立对象的代码,而后再返回新建立的对象;但从表面上看,这个函数又很像是典型的构造函数

function Person(name, age, job){
    var o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function(){
        alert(this.name);
    };    
    return o;
}

var p1 = new Person("Nicholas", 29, "Software Engineer");
p1.sayName();  //"Nicholas"

复制代码

Person函数建立了一个新对象,并以相应的属性和方法初始化该对象,而后又返回了这个对象。除了使用new操做符并把使用的包装函数叫作构造函数以外,这个模式跟工厂模式实际上是如出一辙的,构造函数在不返回值的状况下,默认会返回新对象实例。而经过在构造函数的末尾添加一个return语句,能够重写调用构造函数时返回的值。

这个模式能够在特殊的状况下用来为对象建立构造函数。假设咱们想建立一个具备额外方法的特殊数组。因为不能直接修改Array构造函数,所以可使用这个模式。

function SpecialArray(){

    //建立数组
    var values = new Array();

    //添加值
    values.push.apply(values, arguments);

    //添加方法
    values.toPipedString = function(){
        return this.join("|");
    };

    //返回数组
    return values;
}

var colors = new SpecialArray("red", "blue", "green");
alert(colors.toPipedString()); //"red|blue|green"

复制代码

关于寄生构造函数模式,有一点须要说明:首先,返回的对象与构造函数或者与构造函数的原型属性之间没有关系;也就是说,构造函数返回的对象与在构造函数外部建立的对象没有什么不一样。为此,不能依赖instanceof操做符来肯定对象类型。因为存在上述问题,咱们建议在可使用其余模式的状况下,不要使用这种模式

2.七、稳妥构造函数模式

所谓稳妥对象,指的是没有公共属性,并且其方法也不引用this的对象。稳妥对象最适合在一些安全的环境中(这些环境中会禁止使用this和new),或者在防止数据被其余应用程序改动时使用。稳妥构造函数遵循与寄生构造函数相似的模式,但有两点不一样:一是新建立对象的实例方法不引用this;二是不使用new操做符调用构造函数。按照稳妥构造函数的要求,能够将前面的Person构造函数重写以下。

function Person(name, age, job){

    //建立要返回的对象
    var o = new Object();

    //能够在这里定义私有变量和函数

    //添加方法
    o.sayName = function(){
        alert(name);
    };    

    //返回对象
    return o;
}

复制代码

注意,在以这种模式建立的对象中,除了使用sayName()方法以外,没有其余办法访问name的值。能够像下面使用稳妥的Person构造函数。

var person = Person("Nicholas", 29, "Software Engineer");
person.sayName();  //"Nicholas"

复制代码

这样,变量person中保存的是一个稳妥对象,而除了调用sayName()方法外,没有别的方式能够访问其数据成员。即便有其余代码会给这个对象添加方法或数据成员,但也不可能有别的办法访问传入到构造函数中的原始数据。稳妥构造函数模式提供的这种安全性,使得它很是适合在某些安全执行环境下使用。

三、this对象及指向

改变函数内部this指针的指向函数(bind,apply,call的区别)

  • 经过applycall改变函数的this指向,他们两个函数的第一个参数都是同样的表示要改变指向的那个对象,第二个参数,apply是数组,而call则是arg1,arg2...这种形式。

  • 经过bind改变this做用域会返回一个新的函数,这个函数不会立刻执行。

手动实现 call 方法:

Function.prototype.myCall = function(context = window, ...rest) {
    context.fn = this; //此处this是指调用myCall的function
    let result = context.fn(...rest);
    //将this指向销毁
    delete context.fn;
    return result;
};
复制代码

手动实现 apply 方法:

Function.prototype.myCall = 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;
};
复制代码

手动实现 bind 方法:

Function.prototype.myBind = function(oThis, ...rest) {
    let _this = this;
    let F = function() {}
    // 根据 bind 规定,若是使用 new 运算符构造 bind 的返回函数时,第一个参数绑定的 this 失效
    let resFn = function(...parmas) {
        return _this.apply(this instanceof resFn ? this : oThis, [...rest,...parmas]);
    };
    // 继承原型
    if (this.prototype) {
        F.prototype = this.prototype;
        resFn.prototype = new F;
    }
    return resFn;
};

复制代码

京东小姐姐的文章 嗨,你真的懂this吗?

四、浅拷贝和深拷贝

浅拷贝就是把属于源对象的值都复制一遍到新的对象,不会开辟二者独立的内存区域;

深度拷贝则是完彻底全两个独立的内存区域,互不干扰

  • 浅拷贝
// 这个 ES5的

function shallowClone(sourceObj) {
  // 先判断传入的是否为对象类型
  if (!sourceObj || typeof sourceObj !== 'object') {
    console.log('您传入的不是对象!!')
  }
  // 判断传入的 Obj是类型,而后给予对应的赋值
  var targetObj = sourceObj.constructor === Array ? [] : {};
  
  // 遍历全部 key
  for (var keys in sourceObj) {
    // 判断全部属于自身原型链上的 key,而非继承(上游 )那些
    if (sourceObj.hasOwnProperty(keys)) {
      // 一一复制过来
      targetObj[keys] = sourceObj[keys];
    }
  }
  return targetObj;
}

 // ES6 能够用 Object.assign(targeObj, source1,source2,source3) 来实现对象浅拷贝
 


复制代码
  • 深度拷贝
// 就是把须要赋值的类型转为基本类型(字符串这些)而非引用类型来实现
// JOSN对象中的stringify能够把一个js对象序列化为一个JSON字符串,parse能够把JSON字符串反序列化为一个js对象

var deepClone = function(sourceObj) {
  if (!sourceObj || typeof sourceObj !== 'object') {
    console.log('您传入的不是对象!!');
    return;
  }
  // 转->解析->返回一步到位
  return window.JSON
    ? JSON.parse(JSON.stringify(sourceObj))
    : console.log('您的浏览器不支持 JSON API');
};




复制代码

原型链和继承

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

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

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

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

  • 原型链继承: function Cat(){ } Cat.prototype = new Animal(); Cat.prototype.name = 'cat'; 没法实现多继承
  • 构造继承:使用父类的构造函数来加强子类实例。function Cat(name){Animal.call(this);this.name = name || 'Tom';}没法继承父类原型链上的属性跟方法 installof去检验
  • 实例继承:为父类实例添加新特性,做为子类实例的返回
  • 拷贝继承:拷贝父类元素上的属性跟方法
  • 组合继承:构造继承 和原型继承的组合体
  • 寄生组合继承:经过寄生方式,在构造继承上加一个·Super·函数(没有实例和方法)让他的原型链指向父类的原型链砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性

一、原型链继承

  • 特色:基于原型链,既是父类的实例,也是子类的实例
  • 缺点:没法实现多继承
// 定义一个动物类
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);
复制代码

二、构造继承

使用父类的构造函数来加强子类实例,等因而复制父类的实例属性给子类(没用到原型)

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

三、组合继承

至关于构造继承和原型链继承的组合体。经过调用父类构造,继承父类的属性并保留传参的优势,而后经过将父类实例做为子类原型,实现函数复用

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);
复制代码
  • 特色:能够继承实例属性/方法,也能够继承原型属性/方法
  • 缺点:调用了两次父类构造函数,生成了两份实例

四、寄生组合继承

经过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性

function Cat(name){
  Animal.call(this);
  this.name = name || 'Tom';
}
(function(){
  // 建立一个没有实例方法的类
  var Super = function(){};
  Super.prototype = Animal.prototype;
  //将实例做为子类的原型
  Cat.prototype = new Super();
})();
// Test Code
var cat = new Cat();
console.log(cat.name);
复制代码

详细请看 路易斯的文章 JS原型链与继承别再被问倒了

函数做用域

一、JS 的做用域是什么?有什么特别之处么?

变量做用域的概念:就是一个变量可使用的范围

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

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

二、做用域链

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

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

简单来讲,做用域链能够用如下几句话来归纳:(或者说:肯定一个变量来自于哪一个做用域)

查看当前做用域,若是当前做用域声明了这个变量,就肯定结果

查找当前做用域的上级做用域,也就是当前函数的上级函数,看看上级函数中有没有声明

再查找上级函数的上级函数,直到全局做用域为止

若是全局做用域中也没有,咱们就认为这个变量未声明(xxx is not defined)

推荐文章 深刻到不能再深刻之JS大法系列 -- 做用域链

三、函数声明 VS 函数表达式

函数声明和函数表达式判别的依据是:函数的声明是否以function关键词开始。 以关键词function 开始的声明是函数声明,其他的函数声明所有是函数表达式。

//函数声明
function foo() {

}

//函数表达式
var foo = function () {

};

(function() {

})();

复制代码

四、具名函数 VS 匿名函数

  • 具名函数 拥有名字的函数
function foo() {

}

var foo = function bar() {

}

setTimeout( function foo() {

} )

+function foo() {

}();

复制代码

须要注意:函数声明必定要是具名函数。

  • 匿名函数 没有名字的函数
var foo = function () {

}

setTimeout( function foo() {

} )

-function foo() {

}();

复制代码
  • 当即执行函数(IIFE)
vara=2;

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

console.log( a ); // 2

复制代码
  • 另外一种表达形式
(function() {

}())

复制代码

五、闭包

5.一、定义

当函数能够记住并访问所在的词法做用域,即便函数是在当前词法做用域以外执行,这就产生了闭包。 ----《你不知道的Javascript上卷》
复制代码

闭包就是一个函数,一个能够访问并操做其余函数内部变量的函数。也能够说是一个定义在函数内部的函数。由于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

咱们知道经过引用的关系,fn3就是fn2函数自己。执行fn3能正常输出name,这不就是fn2能记住并访问它所在的词法做用域,并且fn2函数的运行仍是在当前词法做用域以外了。

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

总结:某个函数在定义时的词法做用域以外的地方被调用,闭包可使该函数极限访问定义时的词法做用域。
复制代码

5.二、闭包造成的条件

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

5.三、闭包的特性

每一个函数都是闭包,每一个函数天生都可以记忆本身定义时所处的做用域环境。把一个函数从它定义的那个做用域,挪走,运行。这个函数竟然可以记忆住定义时的那个做用域。无论函数走到哪里,定义时的做用域就带到了哪里。接下来咱们用两个例子来讲明这个问题:

//例题1
var inner;
function outer(){
var a=250;
inner=function(){
alert(a);//这个函数虽然在外面执行,但可以记忆住定义时的那个做用域,a是250
  }
}
outer();
var a=300;
inner();//一个函数在执行的时候,找闭包里面的变量,不会理会当前做用域。

复制代码
//例题2
function outer(x){
  function inner(y){
  console.log(x+y);
  }
return inner;
}
var inn=outer(3);//数字3传入outer函数后,inner函数中x便会记住这个值
inn(5);//当inner函数再传入5的时候,只会对y赋值,因此最后弹出8

复制代码

5.四、闭包的内存泄漏

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

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

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

以下面这种状况:

function fn(){
var num=100;
return function(){
  }
}
var f=fn();//fn执行造成的这个私有的做用域就不能再销毁了

复制代码

也就是像上面这段代码,fn函数内部的私有做用域会被一直占用的,发生了内存泄漏。

所谓内存泄漏指任何对象在您再也不拥有或须要它以后仍然存在。闭包不能滥用,不然会致使内存泄露,影响网页的性能。闭包使用完了后,要当即释放资源,将引用变量指向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  

复制代码

5.五、闭包的做用

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

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

5.六、闭包的运用

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

推荐文章:

JavaScript 闭包

高效使用 JavaScript 闭包

六、new操做符

推荐 yck 的文章 重学 JS 系列:聊聊 new 操做符

七、 执行上下文(EC)

执行上下文能够简单理解为一个对象:

  • 它包含三个部分:

    • 变量对象(VO)
    • 做用域链(词法做用域)
    • this指向
  • 它的类型:

    • 全局执行上下文
    • 函数执行上下文
    • eval执行上下文
  • 代码执行过程:

    • 建立 全局上下文 (global EC)
    • 全局执行上下文(caller)逐行 自上而下 执行。遇到函数时,函数执行上下文 (callee)push到执行栈顶层
    • 函数执行上下文被激活,成为 active EC, 开始执行函数中的代码,caller 被挂起
    • 函数执行完后,calleepop移除出执行栈,控制权交还全局上下文(caller),继续执行

DOM

一、dom 操做

建立:

createDocumentFragment()    //建立一个DOM片断

createElement()   //建立一个具体的元素

createTextNode()   //建立一个文本节点
复制代码
  • 添加:appendChild()

  • 移出:removeChild()

  • 替换:replaceChild()

  • 插入:insertBefore()

  • 复制:cloneNode(true)

查找:

getElementsByTagName()    //经过标签名称

getElementsByClassName()    //经过标签名称

getElementsByName()    //经过元素的Name属性的值

getElementById()    //经过元素Id,惟一性
复制代码

子节点

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

父节点

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

同胞节点

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

二、dom事件

三、dom事件模型

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

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

上文中讲到了addEventListener的第三个参数为指定事件是否在捕获或冒泡阶段执行,设置为true表示事件在捕获阶段执行,而设置为false表示事件在冒泡阶段执行。那么什么是事件冒泡和事件捕获呢?能够用下图来解释:

3.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>

复制代码

3.二、事件冒泡

所谓事件冒泡就是事件像泡泡同样从最开始生成的地方一层一层往上冒。咱们只须要将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  

复制代码

四、事件代理(事件委托)

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

4.1.优势

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

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

  • 动态绑定事件

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

4.二、跨浏览器处理事件程序

标准事件对象:

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

IE中的事件对象:

  • (1)type:事件类型

  • (2)srcElement:事件目标

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

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

/*
* @Author: 陈陈陈
*/
var  eventUtil{

    /*添加事件处理程序    */
    addHandler:function(element,type,handler){//参数表元素、事件类型、函数
        if(element.addEventListener){//DOM 2级事件处理程序
            element.addEventListener(type,handler,false);//false表冒泡事件调用;true表捕获事件调用
        }else if(element.attachEvent){//IE事件处理程序
            element.attachEvent('on'+type,handler);//没有第三个参数的缘由是:IE8及更早的浏览器版本只支持事件冒泡
        }else{//DOM 0级事件处理程序
            element['on'+type]=handler;
        }
    },

    /*删除事件处理程序    */
    removeHandler:function(element,type,handler){//删除事件处理程序,参数表元素、事件类型、函数
        if(element.removeEventListener){//DOM 2级事件处理程序
            element.removeEventListener(type,handler,false);//false表冒泡事件调用;true表捕获事件调用
        }else if(element.detachEvent){//IE事件处理程序
            element.detachEvent('on'+type,handler);//没有第三个参数的缘由是:IE8及更早的浏览器版本只支持事件冒泡
        }else{//DOM 0级事件处理程序
            element['on'+type]=null;
        }
    },

 
    
    getEvent:function(event){
        return event?event:window.event;
    },

    getTarget:function(event){
        return target || srcElement;
    },

    stopPro:function(event){
        if(event.stopPropagation){
            event.stopPropagation();
        }else{
            event.cancelBubble=true;
        }
    },

    preventDef:function(event){
        if(event.preventDefault){
            event.preventDefault();
        }else{
            event.returnValue=false;
        }
    },
}
复制代码

JS中级篇

一、图片的懒加载和预加载

  • 预加载:提早加载图片,当用户须要查看时可直接从本地缓存中渲染。 为何要使用预加载:在网页加载以前,对一些主要内容进行加载,以提供用户更好的体验,减小等待时间。不然,若是一个页面的内容过于庞大,会出现留白。

解决页面留白的方案:

  • 1).预加载

  • 2).使用svg站位图片,将一些结构快速搭建起来,等待请求的数据来了以后,替换当前的占位符

实现预加载的方法:
		1.使用html标签
		2.使用Image对象
		3.使用XMLHTTPRequest对像,但会精细控制预加载过程

复制代码
  • 懒加载:懒加载的主要目的是做为服务器前端的优化,减小请求数或延迟请求数。

两种技术的本质:二者的行为是相反的,一个是提早加载,一个是迟缓甚至不加载。 懒加载对服务器前端有必定的缓解压力做用,预加载则会增长服务器前端压力。

二、懒加载怎么实现

  • 场景:一个页面中不少图片,可是首屏只出现几张,这时若是一次性把图片都加载出来会影响性能。这时可使用懒加载,页面滚动到可视区在加载。优化首屏加载。
  • 实现:img标签src属性为空,给一个data-xx属性,里面存放图片真实地址,当页面滚动直至此图片出如今可视区域时,用js取到该图片的data-xx的值赋给src
  • 优势:页面加载速度快,减轻服务器压力、节约流量,用户体验好。

三、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
复制代码

四、 js拖拽功能的实现

首先是三个事件,分别是mousedown,mousemove,mouseup 当鼠标点击按下的时候,须要一个tag标识此时已经按下,能够执行mousemove里面的具体方法。

clientX,clientY标识的是鼠标的坐标,分别标识横坐标和纵坐标,而且咱们用offsetX和offsetY来表示元素的元素的初始坐标,移动的举例应该是:鼠标移动时候的坐标-鼠标按下去时候的坐标。

也就是说定位信息为:

  • 鼠标移动时候的坐标-鼠标按下去时候的坐标+元素初始状况下的offetLeft.

  • 还有一点也是原理性的东西,也就是拖拽的同时是绝对定位,咱们改变的是绝对定位条件下的left 以及top等等值。

五、异步加载js的方法

  • defer:只支持IE若是您的脚本不会改变文档的内容,可将 defer属性加入到script标签中, 以便加快处理文档的速度。由于浏览器知道它将可以安全地读取文档的剩余部分而不用执行脚本,它将推迟对脚本的解释,直到文档已经显示给用户为止。

  • async,HTML5属性仅适用于外部脚本,而且若是在IE中,同时存在defer和async,那么defer的优先级比较高,脚本将在页面完成时执行。

  • 建立script标签,插入到DOM

六、js中的垃圾回收机制

  • JS具备自动垃圾收集的机制

  • JS的内存生命周期(变量的生命)

    • 1).分配你所须要的空间 var a = 20
    • 2).使用分配带的内存(读写) alert(a + 10)
    • 3).不适用的时候,释放内存空间 a = null
  • JS的垃圾收集器每隔固定的时间就执行一次释放操做,通用的是经过标记清除的算法

标记清除算法:js最多见的垃圾回收方式,当变量进入执行环境的时候,好比函数中声明一个变量,垃圾回收器将他标
记为'进入环境',当变量离开(函数执行完后),就其标记为'离开环境'。垃圾回收器会在运行的时候给存储在内存中
的全部变量加上标记,而后去掉环境中的变量以及被环境中该变量所引用的变量(闭包)。在这些完成以后仍存在标记
的就是要删除的变量了
复制代码

七、内存泄露

  • 意外的全局变量: 没法被回收
  • 定时器: 未被正确关闭,致使所引用的外部变量没法被释放
  • 事件监听: 没有正确销毁 (低版本浏览器可能出现)
  • 闭包: 会致使父级中的变量没法被释放
  • dom引用: dom 元素被删除时,内存中的引用未被正确清空

八、防抖与节流

防抖与节流函数是一种最经常使用的 高频触发优化方式,能对性能有较大的帮助。

8.一、防抖

  • 定义: 合并事件且不会去触发事件,当必定时间内没有触发这个事件时,才真正去触发事件。

  • 原理:对处理函数进行延时操做,若设定的延时到来以前,再次触发事件,则清除上一次的延时操做定时器,从新定时。

  • 场景: keydown事件上验证用户名,用户输入,只需再输入完成后作一次输入校验便可。

function debounce(fn, wait, immediate) {
    let timer = null

    return function() {
        let args = arguments
        let context = this

        if (immediate && !timer) {
            fn.apply(context, args)
        }

        if (timer) clearTimeout(timer)
        timer = setTimeout(() => {
            fn.apply(context, args)
        }, wait)
    }
}
复制代码

8.二、节流

  • 定义: 持续触发事件时,合并必定时间内的事件,在间隔必定时间以后再真正触发事件。每间隔一段时间触发一次。

  • 原理:对处理函数进行延时操做,若设定的延时到来以前,再次触发事件,则清除上一次的延时操做定时器,从新定时。

  • 场景: resize改变布局时,onscroll滚动加载下面的图片时。

  • 实现:

方法一:使用时间戳。

当触发事件的时候,咱们取出当前的时间戳,而后减去以前的时间戳(最一开始值设为0),若是大于设置的时间周期,就执行函数,而后更新时间戳为当前的时间戳,若是小于,就不执行。

缺陷:第一次事件会当即执行,中止触发后没办法再激活事件。

function throttle(fn, interval) {
var previousTime = +new Date()

    return function () {
        var that = this
        var args = arguments
        var now = +new Date()
        if (now - previousTime >= interval) {
            previousTime = now
            fn.apply(that, args)
        }
   }
}
复制代码

方法二:使用定时器

当触发事件的时候,咱们设置一个定时器,再触发事件的时候,若是定时器存在,就不执行,直到定时器执行,而后执行函数,清空定时器,这样就能够设置下个定时器。

缺陷:第一次事件会在n秒后执行,中止触发后依然会再执行一次事件。

function throttle(fn, interval) {
    var timer
    return function (){
        var that = this
        var args = arguments

   if(!timer){
        timer = setTimeout(function () {
            fn.apply(that, args)
            timer = null
         }, interval)
        }
    }
}
复制代码

方法三:优化

鼠标移入能马上执行,中止触发的时候还能再执行一次。

var throttle = function(func,delay){
    var timer = null;
    var startTime = Date.now();

    return function(){
        var curTime = Date.now();
        var remaining = delay-(curTime-startTime);
        var context = this;
        var args = arguments;

        clearTimeout(timer);
        if(remaining<=0){
            func.apply(context,args);
            startTime = Date.now();
        }else{
            timer = setTimeout(func,remaining);
        }
    }
}

复制代码

九、函数柯里化

在一个函数中,首先填充几个参数,而后再返回一个新的函数的技术,称为函数的柯里化

一般可用于在不侵入函数的前提下,为函数 预置通用参数,供屡次重复调用。

const add = function add(x) {
	return function (y) {
		return x + y
	}
}

const add1 = add(1)

add1(2) === 3
add1(20) === 21

复制代码

十、高阶函数是什么,怎么去写一个高阶函数

高阶函数英文叫 Higher-order function,它的定义很简单,就是至少知足下列一个条件的函数:

  • 接受一个或多个函数做为输入
  • 输出一个函数

也就是说高阶函数是对其余函数进行操做的函数,能够将它们做为参数传递,或者是返回它们。

简单来讲,高阶函数是一个接收函数做为参数传递或者将函数做为返回值输出的函数。

高阶函数:参数值为函数或者返回值为函数。例如mapreducefiltersort方法就是高阶函数。

编写高阶函数,就是让函数的参数可以接收别的函数。

10.1 map()方法

map() 方法建立一个新数组,其结果是该数组中的每一个元素都调用一个提供的函数后返回的结果,原始数组不会改变。传递给 map 的回调函数(callback)接受三个参数,分别是

currentValue、index(可选)、array(可选),除了 callback 以外还能够接受 this 值(可选),用于执行 callback 函数时使用的this 值。
复制代码

来个简单的例子方便理解,如今有一个数组 [1, 2, 3, 4],咱们想要生成一个新数组,其每一个元素皆是以前数组的两倍,那么咱们有下面两种使用高阶和不使用高阶函数的方式来实现。

// 木易杨
const arr1 = [1, 2, 3, 4];
const arr2 = arr1.map(item => item * 2);

console.log( arr2 );
// [2, 4, 6, 8]
console.log( arr1 );
// [1, 2, 3, 4]

复制代码

10.二、filter() 方法

filter() 方法建立一个新数组, 其包含经过提供函数实现的测试的全部元素,原始数组不会改变。接收的参数和 map 是同样的,其返回值是一个新数组、由经过测试的全部元素组成,若是没有任何数组元素经过测试,则返回空数组。

来个例子介绍下,如今有一个数组 [1, 2, 1, 2, 3, 5, 4, 5, 3, 4, 4, 4, 4],咱们想要生成一个新数组,这个数组要求没有重复的内容,即为去重。

const arr1 = [1, 2, 1, 2, 3, 5, 4, 5, 3, 4, 4, 4, 4];
const arr2 = arr1.filter( (element, index, self) => {
    return self.indexOf( element ) === index;
});

console.log( arr2 );
// [1, 2, 3, 5, 4]
console.log( arr1 );
// [1, 2, 1, 2, 3, 5, 4, 5, 3, 4, 4, 4, 4]

复制代码

10.三、reduce() 方法

reduce() 方法对数组中的每一个元素执行一个提供的 reducer 函数(升序执行),将其结果汇总为单个返回值。

reduce 的回调函数(callback)接受四个参数,分别是累加器 accumulator、currentValue、currentIndex(可选)、array(可选),除了 callback 以外还能够接受初始值 initialValue 值(可选)。
复制代码
  • 若是没有提供 initialValue,那么第一次调用callback函数时,accumulator 使用原数组中的第一个元素,currentValue 便是数组中的第二个元素。 在没有初始值的空数组上调用 reduce 将报错。

  • 若是提供了 initialValue,那么将做为第一次调用 callback 函数时的第一个参数的值,即 accumulatorcurrentValue使用原数组中的第一个元素。

来个简单的例子介绍下,如今有一个数组 [0, 1, 2, 3, 4],须要计算数组元素的和,需求比较简单,来看下代码实现。

无 initialValue 值

const arr = [0, 1, 2, 3, 4];
let sum = arr.reduce((accumulator, currentValue, currentIndex, array) => {
  return accumulator + currentValue;
});

console.log( sum );
// 10
console.log( arr );
// [0, 1, 2, 3, 4]

上面是没有 initialValue 的状况,代码的执行过程以下,callback 总共调用四次。
复制代码

有 initialValue 值

咱们再来看下有 initialValue 的状况,假设 initialValue 值为 10,咱们看下代码。

const arr = [0, 1, 2, 3, 4];
let sum = arr.reduce((accumulator, currentValue, currentIndex, array) => {
  return accumulator + currentValue;
}, 10);

console.log( sum );
// 20
console.log( arr );
// [0, 1, 2, 3, 4]
复制代码代码的执行过程以下所示,callback 总共调用五次。
复制代码

例题

已知以下数组:var arr = [ [1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14] ] ] ], 10]; 编写一个程序将数组扁平化去并除其中重复部分数据,最终获得一个升序且不重复的数组

答案:

var arr = [ [1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14] ] ] ], 10]
// 扁平化
let flatArr = arr.flat(4)
// 去重
let disArr = Array.from(new Set(flatArr))
// 排序
let result = disArr.sort(function(a, b) {
    return a-b
})
console.log(result)
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]

复制代码

十一、Web Worker

现代浏览器为JavaScript创造的多线程环境。能够新建并将部分任务分配到worker线程并行运行,两个线程可 独立运行,互不干扰,可经过自带的 消息机制 相互通讯。

基本用法:
// 建立 worker
const worker = new Worker('work.js');

// 向主进程推送消息
worker.postMessage('Hello World');

// 监听主进程来的消息
worker.onmessage = function (event) {
  console.log('Received message ' + event.data);
}
复制代码
  • 限制:

    • 同源限制
    • 没法使用 document / window / alert / confirm
    • 没法加载本地资源

十二、怎么把es6转成es5,babel怎么工做的

  • 解析:将代码字符串解析成抽象语法树
  • 变换:对抽象语法树进行变换操做
  • 再建:根据变换后的抽象语法树再生成代码字符串

1三、用过哪些设计模式

(1)单例模式

  • 定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

  • 实现方法:先判断实例存在与否,若是存在则直接返回,若是不存在就建立了再返回,这就确保了一个类只有一个实例对象。

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

(2)发布/订阅模式

  • 定义:又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,全部依赖于它的对象都将获得通知。

  • 场景:订阅感兴趣的专栏和公众号。

(3)策略模式

  • 定义:将一个个算法(解决方案)封装在一个个策略类中。

  • 优势:

    • 策略模式能够避免代码中的多重判断条件。
    • 策略模式很好的体现了开放-封闭原则,将一个个算法(解决方案)封装在一个个策略类中。便于切换,理解,扩展。
    • 策略中的各类算法能够重复利用在系统的各个地方,避免复制粘贴。
    • 策略模式在程序中或多或少的增长了策略类。但比堆砌在业务逻辑中要清晰明了。
    • 违反最少知识原则,必需要了解各类策略类,才能更好的在业务中应用。
  • 应用场景:根据不一样的员工绩效计算不一样的奖金;表单验证中的多种校验规则。

(4)代理模式

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

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

(5)中介者模式

  • 定义:经过一个中介者对象,其余全部相关对象都经过该中介者对象来通讯,而不是互相引用,当其中的一个对象发生改变时,只要通知中介者对象就能够。能够解除对象与对象之间的紧耦合关系。

  • 应用场景: 例如购物车需求,存在商品选择表单、颜色选择表单、购买数量表单等等,都会触发change事件,那么能够经过中介者来转发处理这些事件,实现各个事件间的解耦,仅仅维护中介者对象便可。

(6)装饰者模式

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

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

1四、事件队列(宏任务、微任务)

参考::这一次,完全弄懂 JavaScript 执行机制

1五、怎么用原生js实现一个轮播图,以及滚动滑动

思路,用定时器去实现,以及如何实现平滑的滚动效果。详情请看: 原生js实现轮播图

前端模块化

前端模块化就是复杂的文件编程一个一个独立的模块,好比js文件等等,分红独立的模块有利于重用(复用性)和维护(版本迭代),这样会引来模块之间相互依赖的问题,因此有了commonJS规范AMDCMD规范等等,以及用于js打包(编译等处理)的工具webpack`。

一、什么是模块?

将一个复杂的程序依据必定的规则(规范)封装成几个块(文件), 并进行组合在一块儿 块的内部数据与实现是私有的, 只是向外部暴露一些接口(方法)与外部其它模块通讯

二、模块化的进化过程

1)全局function模式: 将不一样的功能封装成不一样的全局函数

  • 编码: 将不一样的功能封装成不一样的全局函数

  • 问题: 污染全局命名空间, 容易引发命名冲突或数据不安全,并且模块成员之间看不出直接关系

2)namespace模式 : 简单对象封装

  • 做用: 减小了全局变量,解决命名冲突

  • 问题: 数据不安全(外部能够直接修改模块内部的数据)

3)IIFE模式:匿名函数自调用(闭包)

  • 做用: 数据是私有的, 外部只能经过暴露的方法操做

  • 编码: 将数据和行为封装到一个函数内部, 经过给window添加属性来向外暴露接口

  • 问题: 若是当前这个模块依赖另外一个模块怎么办?

  • 解决:IIFE模式加强 : 引入依赖

三、模块化的好处

  • 避免命名冲突(减小命名空间污染)
  • 更好的分离, 按需加载
  • 更高复用性
  • 高可维护性

四、引入多个script标签后出现出现问题

请求过多

首先咱们要依赖多个模块,那样就会发送多个请求,致使请求过多
复制代码

依赖模糊

咱们不知道他们的具体依赖关系是什么,也就是说很容易由于不了解他们之间的依赖关系致使加载前后顺序出错。
复制代码

难以维护

以上两种缘由就致使了很难维护,极可能出现牵一发而动全身的状况致使项目出现严重的问题。
复制代码

模块化当然有多个好处,然而一个页面须要引入多个js文件,就会出现以上这些问题。而这些问题能够经过模块化规范来解决,下面介绍开发中最流行的commonjs, AMD, ES6, CMD规范。

五、模块化规范

  • CommonJS规范主要用于服务端编程,加载模块是同步的,这并不适合在浏览器环境,由于同步意味着阻塞加载,浏览器资源是异步加载的,所以有了AMD CMD解决方案。
  • AMD规范在浏览器环境中异步加载模块,并且能够并行加载多个模块。不过,AMD规范开发成本高,代码的阅读和书写比较困难,模块定义方式的语义不畅。
  • CMD规范与AMD规范很类似,都用于浏览器编程,依赖就近,延迟执行,能够很容易在Node.js中运行。不过,依赖SPM 打包,模块的加载逻辑偏重
  • ES6 在语言标准的层面上,实现了模块功能,并且实现得至关简单,彻底能够取代 CommonJSAMD 规范,成为浏览器和服务器通用的模块解决方案。
  • require与import的区别

    • require支持 动态导入,import不支持,正在提案 (babel 下可支持)
    • require是 同步 导入,import属于 异步 导入
    • require是 值拷贝,导出值变化不会影响导入值;import指向 内存地址,导入值会随导出值而变化

六、MVC和MVVM的区别

  • Model用于封装和应用程序的业务逻辑相关的数据以及对数据的处理方法;
  • View做为视图层,主要负责数据的展现;
  • Controller定义用户界面对用户输入的响应方式,它链接模型和视图,用于控制应用程序的流程,处理用户的行为和数据上的改变。

MVC将响应机制封装在controller对象中,当用户和你的应用产生交互时,控制器中的事件触发器就开始工做了。

MVVMViewModel的同步逻辑自动化了。之前Controller负责的View和Model同步再也不手动地进行操做,而是交给框架所提供的数据绑定功能进行负责,只须要告诉它View显示的数据对应的是Model哪一部分便可。也就是双向数据绑定,就是View的变化能实时让Model发生变化,而Model的变化也能实时更新到View

参考: 浅析前端开发中的 MVC/MVP/MVVM 模式

七、async、await 优缺点

asyncawait相比直接使用 Promise来讲,优点在于处理 then 的调用链,可以更清晰准确的写出代码。

缺点在于滥用await 可能会致使性能问题,由于 await 会阻塞代码,也许以后的异步代码并不依赖于前者,但仍然须要等待前者完成,致使代码失去了并发性。

下面来看一个使用 await 的代码。

var a = 0
var b = async () => {
  a = a + await 10
  console.log('2', a) // -> '2' 10
  a = (await 10) + a
  console.log('3', a) // -> '3' 20
}
b()
a++
console.log('1', a) // -> '1' 1

复制代码

首先函数b先执行,在执行到await 10以前变量 a 仍是0,由于在 await内部实现了 generatorsgenerators 会保留堆栈中东西,因此这时候 a = 0被保存了下来

由于 await是异步操做,遇到await就会当即返回一个pending状态的Promise对象,暂时返回执行代码的控制权,使得函数外的代码得以继续执行,因此会先执行 console.log('1', a)

这时候同步代码执行完毕,开始执行异步代码,将保存下来的值拿出来使用,这时候a = 10 而后后面就是常规执行代码了

推荐:

浪里行舟前端模块化详解(完整版)

subwaydown前端模块化:CommonJS,AMD,CMD,ES6

六、前端打包工具

1)基于任务运行的工具:

Grunt、Gulp
复制代码

它们会自动执行指定的任务,就像流水线,把资源放上去而后经过不一样插件进行加工,它们包含活跃的社区,丰富的插件,能方便的打造各类工做流。

2)基于模块化打包的工具:

Browserify、Webpack、rollup.js
复制代码

有过 Node.js 开发经历的应该对模块很熟悉,须要引用组件直接一个 require 就 OK,这类工具就是这个模式,还能够实现按需加载、异步加载模块。

3)整合型工具:

Yeoman、FIS、jdf、Athena、cooking、weflow
复制代码

使用了多种技术栈实现的脚手架工具,好处是即开即用,缺点就是它们约束了技术选型,而且学习成本相对较高。

七、webpack

WebPack 是一个模块打包工具,你可使用WebPack管理你的模块依赖,并编绎输出模块们所需的静态文件。它可以很好地管理、打包Web开发中所用到的HTMLJavaScriptCSS以及各类静态文件(图片、字体等),让开发过程更加高效。对于不一样类型的资源,webpack有对应的模块加载器。webpack模块打包器会分析模块间的依赖关系,最后 生成了优化且合并后的静态资源。

webpack的两大特点:

  • 1.code splitting(能够自动完成)

  • 2.loader 能够处理各类类型的静态文件,而且支持串联操做

webpack 是以commonJS的形式来书写脚本滴,但对AMD/CMD 的支持也很全面,方便旧项目进行代码迁移。

webpack具备requireJsbrowserify的功能,但仍有不少本身的新特性

    1. CommonJSAMDES6的语法作了兼容
    1. jscss、图片等资源文件都支持打包
    1. 串联式模块加载器以及插件机制,让其具备更好的灵活性和扩展性,例如提供对CoffeeScriptES6的支持
    1. 有独立的配置文件webpack.config.js
    1. 能够将代码切割成不一样的chunk,实现按需加载,下降了初始化时间
    1. 支持 SourceUrlsSourceMaps,易于调试
    1. 具备强大的Plugin接口,大可能是内部插件,使用起来比较灵活
    1. webpack 使用异步 IO 并具备多级缓存。这使得 webpack 很快且在增量编译上更加快

八、webpack 如何进行打包优化

从提取公共模块,区分开发环境,移除重复没必要要的 css 和 js 文件等方面说。

推荐arlendp2012的文章 Webpack打包优化

九、 webpack 打包原理

  • 初始化:启动构建,读取与合并配置参数,加载 Plugin,实例化 Compiler

  • 编译:从 Entry 发出,针对每一个Module串行调用对应的Loader去翻译文件内容,再找到该Module 依赖的 Module,递归地进行编译处理。

  • 输出:对编译后的Module组合成 Chunk,把 Chunk转换成文件,输出到文件系统。

推荐whjin的文章 webpack原理

十、webpack的做用

  • 依赖管理:方便引用第三方模块、让模块更容易复用,避免全局注入致使的冲突、避免重复加载或者加载不须要的模块。会一层一层的读取依赖的模块,添加不一样的入口;同时,不会重复打包依赖的模块。
  • 合并代码:把各个分散的模块集中打包成大文件,减小HTTP的请求连接数,配合UglifyJS(压缩代码)能够减小、优化代码的体积。
  • 各路插件:统一处理引入的插件,babel编译ES6文件,TypeScript,eslint能够检查编译期的错误。

一句话总结:webpack的做用就是处理依赖,模块化,打包压缩文件,管理插件。

参考文章:

刷前端面试题的话,收藏这一篇就够了!

2017下半年掘金日报优质文章合集:前端篇

2018春招前端面试: 闯关记(精排精校) | 掘金技术征文

「中高级前端面试」JavaScript手写代码无敌秘籍

相关文章
相关标签/搜索