关于前端:如果才能做好准备好前端面试

56次阅读

共计 19492 个字符,预计需要花费 49 分钟才能阅读完成。

闭包产生的实质

以后环境中存在指向父级作用域的援用

Promise.allSettled

形容 :等到所有promise 都返回后果,就返回一个 promise 实例。

实现

Promise.allSettled = function(promises) {return new Promise((resolve, reject) => {if(Array.isArray(promises)) {if(promises.length === 0) return resolve(promises);
            let result = [];
            let count = 0;
            promises.forEach((item, index) => {Promise.resolve(item).then(
                    value => {
                        count++;
                        result[index] = {
                            status: 'fulfilled',
                            value: value
                        };
                        if(count === promises.length) resolve(result);
                    }, 
                    reason => {
                        count++;
                        result[index] = {
                            status: 'rejected'.
                            reason: reason
                        };
                        if(count === promises.length) resolve(result);
                    }
                );
            });
        }
        else return reject(new TypeError("Argument is not iterable"));
    });
}

说一下怎么把类数组转换为数组?

// 通过 call 调用数组的 slice 办法来实现转换
Array.prototype.slice.call(arrayLike)

// 通过 call 调用数组的 splice 办法来实现转换
Array.prototype.splice.call(arrayLike,0)

// 通过 apply 调用数组的 concat 办法来实现转换
Array.prototype.concat.apply([],arrayLike)

// 通过 Array.from 办法来实现转换
Array.from(arrayLike)

说一下常见的检测数据类型的几种形式?

typeof  其中数组、对象、null 都会被判断为 Object,其余判断都正确

instanceof 只能判断援用数据类型, 不能判断根本数据类型

constructor 它有 2 个作用 一是判断数据的类型,二是对象实例通过 constructor 对象拜访它的构造函数。须要留神的事件是如果创立一个对象来扭转它的原型,constructor 就不能来判断数据类型了

Object.prototype.toString.call()

Vue 的生命周期是什么 每个钩子外面具体做了什么事件

Vue 实例有⼀个残缺的⽣命周期,也就是从开始创立、初始化数据、编译模版、挂载 Dom -> 渲染、更新 -> 渲染、卸载 等⼀系列过程,称这是 Vue 的⽣命周期。1、beforeCreate(创立前):数据观测和初始化事件还未开始,此时 data 的响应式追踪、event/watcher 都还没有被设置,也就是说不能拜访到 data、computed、watch、methods 上的办法和数据。2、created(创立后):实例创立实现,实例上配置的 options 包含 data、computed、watch、methods 等都配置实现,然而此时渲染得节点还未挂载到 DOM,所以不能拜访到 `$el` 属性。3、beforeMount(挂载前):在挂载开始之前被调用,相干的 render 函数首次被调用。实例已实现以下的配置:编译模板,把 data 外面的数据和模板生成 html。此时还没有挂载 html 到页面上。4、mounted(挂载后):在 el 被新创建的 vm.$el 替换,并挂载到实例下来之后调用。实例已实现以下的配置:用下面编译好的 html 内容替换 el 属性指向的 DOM 对象。实现模板中的 html 渲染到 html 页面中。此过程中进行 ajax 交互。5、beforeUpdate(更新前):响应式数据更新时调用,此时尽管响应式数据更新了,然而对应的实在 DOM 还没有被渲染。6、updated(更新后):在因为数据更改导致的虚构 DOM 从新渲染和打补丁之后调用。此时 DOM 曾经依据响应式数据的变动更新了。调用时,组件 DOM 曾经更新,所以能够执行依赖于 DOM 的操作。然而在大多数状况下,应该防止在此期间更改状态,因为这可能会导致更新有限循环。该钩子在服务器端渲染期间不被调用。7、beforeDestroy(销毁前):实例销毁之前调用。这一步,实例依然齐全可用,`this` 仍能获取到实例。8、destroyed(销毁后):实例销毁后调用,调用后,Vue 实例批示的所有货色都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务端渲染期间不被调用。另外还有 `keep-alive` 独有的生命周期,别离为 `activated` 和 `deactivated`。用 `keep-alive` 包裹的组件在切换时不会进行销毁,而是缓存到内存中并执行 `deactivated` 钩子函数,命中缓存渲染后会执行 `activated` 钩子函数。

实现 call、apply 及 bind 函数

(1)call 函数的实现步骤:

  • 判断调用对象是否为函数,即便是定义在函数的原型上的,然而可能呈现应用 call 等形式调用的状况。
  • 判断传入上下文对象是否存在,如果不存在,则设置为 window。
  • 解决传入的参数,截取第一个参数后的所有参数。
  • 将函数作为上下文对象的一个属性。
  • 应用上下文对象来调用这个办法,并保留返回后果。
  • 删除方才新增的属性。
  • 返回后果。
Function.prototype.myCall = function(context) {
  // 判断调用对象
  if (typeof this !== "function") {console.error("type error");
  }
  // 获取参数
  let args = [...arguments].slice(1),
    result = null;
  // 判断 context 是否传入,如果未传入则设置为 window
  context = context || window;
  // 将调用函数设为对象的办法
  context.fn = this;
  // 调用函数
  result = context.fn(...args);
  // 将属性删除
  delete context.fn;
  return result;
};

(2)apply 函数的实现步骤:

  • 判断调用对象是否为函数,即便是定义在函数的原型上的,然而可能呈现应用 call 等形式调用的状况。
  • 判断传入上下文对象是否存在,如果不存在,则设置为 window。
  • 将函数作为上下文对象的一个属性。
  • 判断参数值是否传入
  • 应用上下文对象来调用这个办法,并保留返回后果。
  • 删除方才新增的属性
  • 返回后果
Function.prototype.myApply = function(context) {
  // 判断调用对象是否为函数
  if (typeof this !== "function") {throw new TypeError("Error");
  }
  let result = null;
  // 判断 context 是否存在,如果未传入则为 window
  context = context || window;
  // 将函数设为对象的办法
  context.fn = this;
  // 调用办法
  if (arguments[1]) {result = context.fn(...arguments[1]);
  } else {result = context.fn();
  }
  // 将属性删除
  delete context.fn;
  return result;
};

(3)bind 函数的实现步骤:

  • 判断调用对象是否为函数,即便是定义在函数的原型上的,然而可能呈现应用 call 等形式调用的状况。
  • 保留以后函数的援用,获取其余传入参数值。
  • 创立一个函数返回
  • 函数外部应用 apply 来绑定函数调用,须要判断函数作为构造函数的状况,这个时候须要传入以后函数的 this 给 apply 调用,其余状况都传入指定的上下文对象。
Function.prototype.myBind = function(context) {
  // 判断调用对象是否为函数
  if (typeof this !== "function") {throw new TypeError("Error");
  }
  // 获取参数
  var args = [...arguments].slice(1),
    fn = this;
  return function Fn() {
    // 依据调用形式,传入不同绑定值
    return fn.apply(
      this instanceof Fn ? this : context,
      args.concat(...arguments)
    );
  };
};

继承

原型继承

核心思想:子类的原型成为父类的实例

实现

function SuperType() {this.colors = ['red', 'green'];
}
function SubType() {}
// 原型继承要害: 子类的原型成为父类的实例
SubType.prototype = new SuperType();

// 测试
let instance1 = new SubType();
instance1.colors.push('blue');

let instance2 = new SubType();
console.log(instance2.colors);  // ['red', 'green', 'blue']

原型继承存在的问题

  1. 原型中蕴含的援用类型属性将被所有实例对象共享
  2. 子类在实例化时不能给父类构造函数传参

构造函数继承

核心思想:在子类构造函数中调用父类构造函数

实现

function SuperType(name) {
    this.name = name;
    this.colors = ['red', 'green'];
    this.getName = function() {return this.name;}
}
function SubType(name) {
    // 继承 SuperType 并传参
    SuperType.call(this, name);
}

// 测试
let instance1 = new SubType('instance1');
instance1.colors.push('blue');
console.log(instance1.colors); // ['red','green','blue']

let instance2 = new SubType('instance2');
console.log(instance2.colors);  // ['red', 'green']

构造函数继承 的呈现是为了解决了原型继承的援用值共享问题。长处是能够在子类构造函数中向父类构造函数传参。它存在的问题是:1)因为办法必须在构造函数中定义,因而办法不能重用。2)子类也不能拜访父类原型上定义的办法。

组合继承

核心思想:综合了原型链和构造函数,即,应用原型链继承原型上的办法,而通过构造函数继承实例属性。

实现

function SuperType(name) {
    this.name = name;
    this.colors = ['red', 'green'];
}
Super.prototype.sayName = function() {console.log(this.name);
}
function SubType(name, age) {
    // 继承属性
    SuperType.call(this, name);
    // 实例属性
    this.age = age;
}
// 继承办法
SubType.prototype = new SuperType();

// 测试
let instance1 = new SubType('instance1', 1);
instance1.sayName();  // "instance1"
instance1.colors.push("blue");
console.log(instance1.colors); // ['red','green','blue']

let instance2 = new SubType('instance2', 2);
instance2.sayName();  // "instance2"
console.log(instance2.colors); // ['red','green']

组合继承 存在的问题是:父类构造函数始终会被调用两次:一次是在创立子类原型时 new SuperType() 调用,另一次是在子类构造函数中 SuperType.call() 调用。

寄生式组合继承(最佳)

核心思想:通过构造函数继承属性,但应用混合式原型继承办法,即,不通过调用父类构造函数给子类原型赋值,而是获得父类原型的一个正本。

实现

function SuperType(name) {
    this.name = name;
    this.colors = ['red', 'green'];
}
Super.prototype.sayName = function() {console.log(this.name);
}
function SubType(name, age) {
    // 继承属性
    SuperType.call(this, name);
    this.age = age;
}
// 继承办法
SubType.prototype = Object.create(SuperType.prototype);
// 重写原型导致默认 constructor 失落,手动将 constructor 指回 SubType
SubType.prototype.constructor = SubType;

class 实现继承(ES6)

核心思想:通过 extends 来实现类的继承(相当于 ES5 的原型继承)。通过 super 调用父类的构造方法 (相当于 ES5 的构造函数继承)。

实现

class SuperType {constructor(name) {this.name = name;}
    sayName() {console.log(this.name);
    }
}
class SubType extends SuperType {constructor(name, age) {super(name);  // 继承属性
        this.age = age;
    }
}

// 测试
let instance = new SubType('instance', 0);
instance.sayName();  // "instance"

尽管类继承应用的是新语法,但背地仍旧应用的是原型链。

介绍一下 Connection:keep-alive

什么是 keep-alive

咱们晓得 HTTP 协定采纳“申请 - 应答”模式,当应用一般模式,即非 KeepAlive 模式时,每个申请 / 应答客户和服务器都要新建一个连贯,实现 之后立刻断开连接(HTTP 协定为无连贯的协定);

当应用 Keep-Alive 模式(又称长久连贯、连贯重用)时,Keep-Alive 性能使客户端到服 务器端的连贯继续无效,当呈现对服务器的后继申请时,Keep-Alive 性能防止了建设或者从新建设连贯。

为什么要应用 keep-alive

keep-alive 技术的创立目标,能在屡次 HTTP 之前重用同一个 TCP 连贯,从而缩小创立 / 敞开多个 TCP 连贯的开销(包含响应工夫、CPU 资源、缩小拥挤等),参考如下示意图

客户端如何开启

在 HTTP/1.0 协定中,默认是敞开的,须要在 http 头退出 ”Connection: Keep-Alive”,能力启用 Keep-Alive;

Connection: keep-alive

http 1.1 中默认启用 Keep-Alive,如果退出 ”Connection: close“,才敞开。

Connection: close

目前大部分浏览器都是用 http1.1 协定,也就是说默认都会发动 Keep-Alive 的连贯申请了,所以是否能实现一个残缺的 Keep- Alive 连贯就看服务器设置状况。

面向对象

编程思维

  • 根本思维是应用对象,类,继承,封装等基本概念来进行程序设计
  • 长处

    • 易保护

      • 采纳面向对象思维设计的构造,可读性高,因为继承的存在,即便扭转需要,那么保护也只是在部分模块,所以保护起来是十分不便和较低成本的
    • 易扩大
    • 开发工作的重用性、继承性高,升高反复工作量。
    • 缩短了开发周期

个别面向对象蕴含:继承,封装,多态,形象

1. 对象模式的继承

浅拷贝

var Person = {
    name: 'poetry',
    age: 18,
    address: {
        home: 'home',
        office: 'office',
    }
    sclools: ['x','z'],
};

var programer = {language: 'js',};

function extend(p, c){var c = c || {};
    for(var prop in p){c[prop] = p[prop];
    }
}
extend(Person, programer);
programer.name;  // poetry
programer.address.home;  // home
programer.address.home = 'house';  //house
Person.address.home;  // house

从下面的后果看出,浅拷贝的缺点在于批改了子对象中援用类型的值,会影响到父对象中的值,因为在浅拷贝中对援用类型的拷贝只是拷贝了地址,指向了内存中同一个正本

深拷贝

function extendDeeply(p, c){var c = c || {};
    for (var prop in p){if(typeof p[prop] === "object"){c[prop] = (p[prop].constructor === Array)?[]:{};
            extendDeeply(p[prop], c[prop]);
        }else{c[prop] = p[prop];
        }
    }
}

利用递归进行深拷贝,这样子对象的批改就不会影响到父对象

extendDeeply(Person, programer);
programer.address.home = 'poetry';
Person.address.home; // home

利用 call 和 apply 继承

function Parent(){
    this.name = "abc";
    this.address = {home: "home"};
}
function Child(){Parent.call(this);
    this.language = "js"; 
}

ES5 中的 Object.create()

var p = {name : 'poetry'};
var obj = Object.create(p);
obj.name; // poetry

Object.create()作为 new 操作符的代替计划是 ES5 之后才进去的。咱们也能够本人模仿该办法:

// 模仿 Object.create()办法
function myCreate(o){function F(){};
    F.prototype = o;
    o = new F();
    return o;
}
var p = {name : 'poetry'};
var obj = myCreate(p);
obj.name; // poetry

目前,各大浏览器的最新版本(包含 IE9)都部署了这个办法。如果遇到老式浏览器,能够用上面的代码自行部署

if (!Object.create) {Object.create = function (o) {function F() {}
      F.prototype = o;
      return new F();};
  }

2. 类的继承

Object.create()

function Person(name, age){}
Person.prototype.headCount = 1;
Person.prototype.eat = function(){console.log('eating...');
}
function Programmer(name, age, title){}

Programmer.prototype = Object.create(Person.prototype); // 建设继承关系
Programmer.prototype.constructor = Programmer;  // 批改 constructor 的指向

调用父类办法

function Person(name, age){
    this.name = name;
    this.age = age;
}
Person.prototype.headCount = 1;
Person.prototype.eat = function(){console.log('eating...');
}

function Programmer(name, age, title){Person.apply(this, arguments); // 调用父类的结构器
}


Programmer.prototype = Object.create(Person.prototype);
Programmer.prototype.constructor = Programmer;

Programmer.prototype.language = "js";
Programmer.prototype.work = function(){console.log('i am working code in'+ this.language);
    Person.prototype.eat.apply(this, arguments); // 调用父类上的办法
}

3. 封装

  • 命名空间

    • js 是没有命名空间的,因而能够用对象模仿
var app = {};  // 命名空间 app
// 模块 1
app.module1 = {
    name: 'poetry',
    f: function(){console.log('hi robot');
    }
};
app.module1.name; // "poetry"
app.module1.f();  // hi robot

对象的属性外界是可读可写 如何来达到封装的额目标?答:可通过 闭包 + 局部变量 来实现

  • 在构造函数外部申明局部变量 和一般办法
  • 因为作用域的关系 只有构造函数内的办法
  • 能力拜访局部变量 而办法对于外界是凋谢的
  • 因而能够通过办法来拜访 本来外界拜访不到的局部变量 达到函数封装的目标
function Girl(name,age){
    var love = '小明';//love 是局部变量 精确说不属于对象 属于这个函数的额激活对象 函数调用时必将产生一个激活对象 love 在激活对象身上   激活对象有作用域的关系 有方法拜访  加一个函数提供外界拜访
    this.name = name;
    this.age = age;
    this.say = function () {return love;};

    this.movelove = function (){love = '小轩'; //35}

} 

var g = new Girl('yinghong',22);

console.log(g);
console.log(g.say());// 小明
console.log(g.movelove());//undefined  因为 35 行没有返回
console.log(g.say());// 小轩



function fn(){function t(){
        //var age = 22;// 申明 age 变量 在 t 的激活对象上
        age = 22;// 赋值操作 t 的激活对象上找 age 属性,找不到 找 fn 的激活对象.... 再找到 最终找到 window.age = 22;
                // 不加 var 就是操作 window 全局属性

    }
    t();}
console.log(fn());//undefined

4. 动态成员

面向对象中的静态方法 - 动态属性:没有 new 对象 也能援用静态方法属性

function Person(name){
    var age = 100;
    this.name = name;
}
// 动态成员
Person.walk = function(){console.log('static');
};
Person.walk();  // static

5. 公有与私有

function Person(id){
    // 公有属性与办法
    var name = 'poetry';
    var work = function(){console.log(this.id);
    };
    // 私有属性与办法
    this.id = id;
    this.say = function(){console.log('say hello');
        work.call(this);
    };
};
var p1 = new Person(123);
p1.name; // undefined
p1.id;  // 123
p1.say();  // say hello 123

6. 模块化

var moduleA;
moduleA = function() {
    var prop = 1;

    function func() {}

    return {
        func: func,
        prop: prop
    };
}(); // 立刻执行匿名函数

7. 多态

多态: 同一个父类继承进去的子类各有各的状态

function Cat(){this.eat = '肉';}

function Tiger(){this.color = '黑黄相间';}

function Cheetah(){this.color = '报文';}

function Lion(){this.color = '土黄色';}

Tiger.prototype =  Cheetah.prototype = Lion.prototype = new Cat();// 共享一个先人 Cat

var T = new Tiger();
var C = new Cheetah();
var L = new Lion();

console.log(T.color);
console.log(C.color);
console.log(L.color);


console.log(T.eat);
console.log(C.eat);
console.log(L.eat);

8. 抽象类

在结构器中 throw new Error(''); 抛异样。这样避免这个类被间接调用

function DetectorBase() {throw new Error('Abstract class can not be invoked directly!');
}

DetectorBase.prototype.detect = function() {console.log('Detection starting...');
};
DetectorBase.prototype.stop = function() {console.log('Detection stopped.');
};
DetectorBase.prototype.init = function() {throw new Error('Error');
};

// var d = new DetectorBase();
// Uncaught Error: Abstract class can not be invoked directly!

function LinkDetector() {}
LinkDetector.prototype = Object.create(DetectorBase.prototype);
LinkDetector.prototype.constructor = LinkDetector;

var l = new LinkDetector();
console.log(l); //LinkDetector {}__proto__: LinkDetector
l.detect(); //Detection starting...
l.init(); //Uncaught Error: Error

原函数形参定长(此时 fn.length 是个不变的常数)

// 写法 1 - 不保留参数, 递归部分函数
function curry(fn) {let judge = (...args) => {
        // 递归完结条件
        if(args.length === fn.length) return fn(...args);
        return (...arg) => judge(...args, ...arg);
    }
    return judge;
}

// 写法 2 - 保留参数, 递归整体函数
function curry(fn) {
    // 保留参数,除去第一个函数参数
    let presentArgs = [].slice.call(arguments, 1);
    // 返回一个新函数
    return function(){
        // 新函数调用时会持续传参
        let allArgs = [...presentArgs, ...arguments];
        // 递归完结条件
        if(allArgs.length === fn.length) {
            // 如果参数够了,就执行原函数
            return fn(,,,allArgs);
        }
        // 否则持续柯里化
        else return curry(fn, ...allArgs);
    }
}

// 测试
function add(a, b, c, d) {return a + b + c + d;}
console.log(add(1, 2, 3, 4));
let addCurry = curry(add);
// 以下后果都返回 10
console.log(addCurry(1)(2)(3)(4));  
console.log(addCurry(1)(2, 3, 4));
console.log(addCurry(1, 2)(3)(4));
console.log(addCurry(1, 2)(3, 4));
console.log(addCurry(1, 2, 3)(4));
console.log(addCurry(1, 2, 3, 4));

革除浮动

  1. 在浮动元素前面增加 clear:both的空 div 元素
<div class="container">
    <div class="left"></div>
    <div class="right"></div>
    <div style="clear:both"></div>
</div>
  1. 给父元素增加 overflow:hidden 或者 auto 款式,触发BFC
<div class="container">
    <div class="left"></div>
    <div class="right"></div>
</div>
.container{
    width: 300px;
    background-color: #aaa;
    overflow:hidden;
    zoom:1;   /*IE6*/
}
  1. 应用伪元素,也是在元素开端增加一个点并带有 clear: both 属性的元素实现的。
<div class="container clearfix">
    <div class="left"></div>
    <div class="right"></div>
</div>
.clearfix{zoom: 1; /*IE6*/}
.clearfix:after{
    content: ".";
    height: 0;
    clear: both;
    display: block;
    visibility: hidden;
}

举荐应用第三种办法,不会在页面新增 div,文档构造更加清晰

函数柯里化

什么叫函数柯里化?其实就是将应用多个参数的函数转换成一系列应用一个参数的函数的技术。还不懂?来举个例子。

function add(a, b, c) {return a + b + c}
add(1, 2, 3)
let addCurry = curry(add)
addCurry(1)(2)(3)

当初就是要实现 curry 这个函数,使函数从一次调用传入多个参数变成屡次调用每次传一个参数。

function curry(fn) {let judge = (...args) => {if (args.length == fn.length) return fn(...args)
        return (...arg) => judge(...args, ...arg)
    }
    return judge
}

说一下 slice splice split 的区别?

// slice(start,[end])
// slice(start,[end])办法:该办法是对数组进行局部截取,该办法返回一个新数组
// 参数 start 是截取的开始数组索引,end 参数等于你要取的最初一个字符的地位值加上 1(可选)。// 蕴含了源函数从 start 到 end 所指定的元素,然而不包含 end 元素,比方 a.slice(0,3);// 如果呈现正数就把正数与长度相加后再划分。// slice 中的正数的绝对值若大于数组长度就会显示所有数组
// 若参数只有一个,并且参数大于 length,则为空。// 如果完结地位小于起始地位,则返回空数组
// 返回的个数是 end-start 的个数
// 不会扭转原数组
var arr = [1,2,3,4,5,6]
/*console.log(arr.slice(3))//[4,5,6] 从下标为 0 的到 3,截取 3 之后的数 console.log(arr.slice(0,3))//[1,2,3] 从下标为 0 的中央截取到下标为 3 之前的数 console.log(arr.slice(0,-2))//[1,2,3,4]console.log(arr.slice(-4,4))//[3,4]console.log(arr.slice(-7))//[1,2,3,4,5,6]console.log(arr.slice(-3,-3))// []console.log(arr.slice(8))//[]*/
// 集体总结:slice 的参数如果是负数就从左往右数,如果是正数的话就从右往左边数,// 截取的数组与数的方向统一,如果是 2 个参数则截取的是数的交加,没有交加则返回空数组 
// ps:slice 也能够切割字符串,用法和数组一样,但要留神空格也算字符

// splice(start,deletecount,item)
// start:起始地位
// deletecount:删除位数
// item:替换的 item
// 返回值为被删除的字符串
// 如果有额定的参数,那么 item 会插入到被移除元素的地位上。// splice: 移除,splice 办法从 array 中移除一个或多个数组,并用新的 item 替换它们。// 举一个简略的例子 
var a=['a','b','c']; 
var b=a.splice(1,1,'e','f'); 
 console.log(a) //['a', 'e', 'f', 'c']
 console.log(b) //['b']

 var a = [1, 2, 3, 4, 5, 6];
//console.log("被删除的为:",a.splice(1, 1, 8, 9)); // 被删除的为:2
// console.log("a 数组元素:",a); //1,8,9,3,4,5,6

// console.log("被删除的为:", a.splice(0, 2)); // 被删除的为:1,2
// console.log("a 数组元素:", a) //3,4,5,6
console.log("被删除的为:", a.splice(1, 0, 2, 2)) // 插入 第二个数为 0,示意删除 0 个  
console.log("a 数组元素:", a) //1,2,2,2,3,4,5,6

// split(字符串)
// string.split(separator,limit):split 办法把这个 string 宰割成片段来创立一个字符串数组。// 可选参数 limit 能够限度被宰割的片段数量。// separator 参数能够是一个字符串或一个正则表达式。// 如果 separator 是一个空字符,会返回一个单字符的数组,不会扭转原数组。var a="0123456";  
var b=a.split("",3);  
console.log(b);//b=["0","1","2"]
// 留神:String.split() 执行的操作与 Array.join 执行的操作是相同的。

字符串模板

function render(template, data) {const reg = /\{\{(\w+)\}\}/; // 模板字符串正则
    if (reg.test(template)) { // 判断模板里是否有模板字符串
        const name = reg.exec(template)[1]; // 查找以后模板里第一个模板字符串的字段
        template = template.replace(reg, data[name]); // 将第一个模板字符串渲染
        return render(template, data); // 递归的渲染并返回渲染后的构造
    }
    return template; // 如果模板没有模板字符串间接返回
}

测试:

let template = '我是{{name}},年龄{{age}},性别{{sex}}';
let person = {
    name: '布兰',
    age: 12
}
render(template, person); // 我是布兰,年龄 12,性别 undefined

Promise.race

形容 :只有promises 中有一个率先扭转状态,就返回这个率先扭转的 Promise 实例的返回值。

实现

Promise.race = function(promises){return new Promise((resolve, reject) => {if(Array.isArray(promises)) {if(promises.length === 0) return resolve(promises);
            promises.forEach((item) => {Promise.resolve(item).then(value => resolve(value), 
                    reason => reject(reason)
                );
            })
        }
        else return reject(new TypeError("Argument is not iterable"));
    });
}

Promise.all

形容:所有 promise 的状态都变成 fulfilled,就会返回一个状态为 fulfilled 的数组(所有promisevalue)。只有有一个失败,就返回第一个状态为 rejectedpromise 实例的 reason

实现

Promise.all = function(promises) {return new Promise((resolve, reject) => {if(Array.isArray(promises)) {if(promises.length === 0) return resolve(promises);
            let result = [];
            let count = 0;
            promises.forEach((item, index) => {Promise.resolve(item).then(
                    value => {
                        count++;
                        result[index] = value;
                        if(count === promises.length) resolve(result);
                    }, 
                    reason => reject(reason)
                );
            })
        }
        else return reject(new TypeError("Argument is not iterable"));
    });
}

Iterator 迭代器

Iterator(迭代器)是一种接口,也能够说是一种标准。为各种不同的数据结构提供对立的拜访机制。任何数据结构只有部署 Iterator 接口,就能够实现遍历操作(即顺次解决该数据结构的所有成员)。

Iterator 语法:

const obj = {[Symbol.iterator]:function(){}
}

[Symbol.iterator] 属性名是固定的写法,只有领有了该属性的对象,就可能用迭代器的形式进行遍历。

  • 迭代器的遍历办法是首先取得一个迭代器的指针,初始时该指针指向第一条数据之前,接着通过调用 next 办法,扭转指针的指向,让其指向下一条数据
  • 每一次的 next 都会返回一个对象,该对象有两个属性

    • value 代表想要获取的数据
    • done 布尔值,false 示意以后指针指向的数据有值,true 示意遍历曾经完结

Iterator 的作用有三个:

  • 创立一个指针对象,指向以后数据结构的起始地位。也就是说,遍历器对象实质上,就是一个指针对象。
  • 第一次调用指针对象的 next 办法,能够将指针指向数据结构的第一个成员。
  • 第二次调用指针对象的 next 办法,指针就指向数据结构的第二个成员。
  • 一直调用指针对象的 next 办法,直到它指向数据结构的完结地位。

每一次调用 next 办法,都会返回数据结构的以后成员的信息。具体来说,就是返回一个蕴含 value 和 done 两个属性的对象。其中,value 属性是以后成员的值,done 属性是一个布尔值,示意遍历是否完结。

let arr = [{num:1},2,3]
let it = arr[Symbol.iterator]() // 获取数组中的迭代器
console.log(it.next())  // {value: Object { num: 1}, done: false }
console.log(it.next())  // {value: 2, done: false}
console.log(it.next())  // {value: 3, done: false}
console.log(it.next())  // {value: undefined, done: true}

对象没有布局 Iterator 接口,无奈应用for of 遍历。上面使得对象具备 Iterator 接口

  • 一个数据结构只有有 Symbol.iterator 属性,就能够认为是“可遍历的”
  • 原型部署了 Iterator 接口的数据结构有三种,具体蕴含四种,别离是数组,相似数组的对象,Set 和 Map 构造

为什么对象(Object)没有部署 Iterator 接口呢?

  • 一是因为对象的哪个属性先遍历,哪个属性后遍历是不确定的,须要开发者手动指定。然而遍历遍历器是一种线性解决,对于非线性的数据结构,部署遍历器接口,就等于要部署一种线性转换
  • 对对象部署 Iterator 接口并不是很必要,因为 Map 补救了它的缺点,又正好有 Iteraotr 接口
let obj = {
    id: '123',
    name: '张三',
    age: 18,
    gender: '男',
    hobbie: '睡觉'
}

obj[Symbol.iterator] = function () {let keyArr = Object.keys(obj)
    let index = 0
    return {next() {
            return index < keyArr.length ? {
                value: {key: keyArr[index],
                    val: obj[keyArr[index++]]
                }
            } : {done: true}
        }
    }
}

for (let key of obj) {console.log(key)
}

箭头函数和一般函数有啥区别?箭头函数能当构造函数吗?

  • 一般函数通过 function 关键字定义,this 无奈联合词法作用域应用,在运行时绑定,只取决于函数的调用形式,在哪里被调用,调用地位。(取决于调用者,和是否独立运行)
  • 箭头函数应用被称为“胖箭头”的操作 => 定义,箭头函数不利用一般函数 this 绑定的四种规定,而是依据外层(函数或全局)的作用域来决定 this,且箭头函数的绑定无奈被批改(new 也不行)。

    • 箭头函数罕用于回调函数中,包含事件处理器或定时器
    • 箭头函数和 var self = this,都试图取代传统的 this 运行机制,将 this 的绑定拉回到词法作用域
    • 没有原型、没有 this、没有 super,没有 arguments,没有 new.target
    • 不能通过 new 关键字调用

      • 一个函数外部有两个办法:[[Call]] 和 [[Construct]],在通过 new 进行函数调用时,会执行 [[construct]] 办法,创立一个实例对象,而后再执行这个函数体,将函数的 this 绑定在这个实例对象上
      • 当间接调用时,执行 [[Call]] 办法,间接执行函数体
      • 箭头函数没有 [[Construct]] 办法,不能被用作结构函数调用,当应用 new 进行函数调用时会报错。
function foo() {return (a) => {console.log(this.a);
  }
}

var obj1 = {a: 2}

var obj2 = {a: 3}

var bar = foo.call(obj1);
bar.call(obj2);

性能优化

性能优化是前端开发中避不开的问题,性能问题无外乎两方面起因:渲染速度慢、申请工夫长。性能优化尽管波及很多简单的起因和解决方案,但其实只有通过正当地应用标签,就能够在肯定水平上晋升渲染速度以及缩小申请工夫

1. script 标签:调整加载程序晋升渲染速度

  • 因为浏览器的底层运行机制,渲染引擎在解析 HTML 时,若遇到 script 标签援用文件,则会暂停解析过程 ,同时 告诉网络线程加载文件,文件加载后会切换至 JavaScript 引擎来执行对应代码 代码执行实现之后切换至渲染引擎持续渲染页面
  • 在这一过程中能够看到,页面渲染过程中蕴含了申请文件以及执行文件的工夫,但页面的首次渲染可能并不依赖这些文件,这些申请和执行文件的动作反而缩短了用户看到页面的工夫,从而升高了用户体验。

为了缩小这些工夫损耗,能够借助 script 标签的 3 个属性来实现。

  • async 属性 。立刻申请文件,但不阻塞渲染引擎,而是 文件加载结束后阻塞渲染引擎并立刻执行文件内容
  • defer 属性 。立刻申请文件,但不阻塞渲染引擎, 等到解析完 HTML 之后再执行 文件内容
  • HTML5 规范 type 属性,对应值为“module”。让浏览器依照 ECMA Script 6 规范将文件当作模块进行解析,默认阻塞成果同 defer,也能够配合 async 在申请实现后立刻执行。

绿色的线示意执行解析 HTML,蓝色的线示意申请文件,红色的线示意执行文件

当渲染引擎解析 HTML 遇到 script 标签引入文件时,会立刻进行一次渲染。所以这也就是为什么构建工具会把编译好的援用 JavaScript 代码的 script 标签放入到 body 标签底部,因为当渲染引擎执行到 body 底部时会先将已解析的内容渲染进去,而后再去申请相应的 JavaScript 文件

2. link 标签:通过预处理晋升渲染速度

在咱们对大型单页利用进行性能优化时,兴许会用到按需懒加载的形式,来加载对应的模块,但如果能正当利用 link 标签的 rel 属性值来进行预加载,就能进一步晋升渲染速度。

  • dns-prefetch。当 link 标签的 rel 属性值为“dns-prefetch”时,浏览器会对某个域名事后进行 DNS 解析并缓存 。这样,当浏览器在申请同域名资源的时候,能省去从域名查问 IP 的过程,从而 缩小工夫损耗。下图是淘宝网设置的 DNS 预解析
  • preconnect。让浏览器在一个 HTTP 申请正式发给服务器前事后执行一些操作,这包含DNS 解析、TLS 协商、TCP 握手,通过打消往返提早来为用户节省时间
  • prefetch/preload。两个值都是 让浏览器事后下载并缓存某个资源,但不同的是,prefetch 可能会在浏览器忙时被疏忽,而 preload 则是肯定会被事后下载
  • prerender。浏览器不仅会加载资源,还会解析执行页面,进行预渲染

这几个属性值恰好反映了浏览器获取资源文件的过程,在这里我绘制了一个流程简图,不便你记忆。

3. 搜寻优化

  • meta 标签:提取要害信息

    • 通过 meta 标签能够设置页面的形容信息,从而让搜索引擎更好地展现搜寻后果。
    • 示例 <meta name="description" content="寰球最大的中文搜索引擎、致力于让网民更便捷地获取信息,找到所求。百度超过千亿的中文网页数据库,能够霎时找到相干的搜寻后果。">

柯里化

题目形容: 柯里化(Currying),又称局部求值(Partial Evaluation),是把承受多个参数的函数变换成承受一个繁多参数(最后函数的第一个参数)的函数,并且返回承受余下的参数而且返回后果的新函数的技术。核心思想是把多参数传入的函数拆成单参数(或局部)函数,外部再返回调用下一个单参数(或局部)函数,顺次解决残余的参数。

实现代码如下:

function currying(fn, ...args) {
  const length = fn.length;
  let allArgs = [...args];
  const res = (...newArgs) => {allArgs = [...allArgs, ...newArgs];
    if (allArgs.length === length) {return fn(...allArgs);
    } else {return res;}
  };
  return res;
}

// 用法如下:// const add = (a, b, c) => a + b + c;
// const a = currying(add, 1);
// console.log(a(2,3))

正文完
 0