msbd

235 阅读7分钟

1、几种垂直居中方式

vertical-align:middle

.parent{display:table-cell;vertical-align:middle;text-align: center;}
.child{display: inline-block;}

display:flex

.parent{display: flex;justify-content: center;align-items: center;}
.child{}

transform

.child{position:relative;top:50%;left:50%;transform:translate(-50%,-50%);}

position

.parent{position: relative;}
.child{position: absolute;left:50%;margin-left:-100px;}

2、闭包

闭包是指有权访问另一个函数作用域中的变量的函数

var dom=document.getElementsByTagName("li");
for(var i=0;i<items.length;i++){
     items[i].onclick=(function(num){
                return function(){
                     console.log(num);
                }
     })(i);
}

3、async函数返回一个 Promise 对象,可以使用then方法添加回调函数。当执行到await的时候会先执行await,等完成后再继续。

4、双向绑定实现原理

通过Object.defineProperty的方法定义 get和set方法对数据的操作拥有代理权,并且每个属性都会有一个对应的依赖

5、v-model实现原理

v-model 本质是一个语法糖监听input事件每次输入时候监听data值变化之后把值绑定到value上

6、webpack打包原理

webpack只是一个打包模块的机制,只是把依赖的模块转化成可以代表这些包的静态文件。webpack做的就是分析代码。转换代码,编译代码,输出代码。webpack本身是一个node的模块

7、loader原理

loader 本质上是一个函数,输入参数是一个字符串,输出参数也是一个字符串。输出的参数会被当成是 JS 代码,从而被 esprima 解析成 AST,触发进一步的依赖解析。webpack会按照从右到左的顺序执行loader。

8、函数防抖和节流

防抖(debounce) 就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。

/**
 * @desc 函数防抖
 * @param func 函数
 * @param wait 延迟执行毫秒数
 * @param immediate true 表立即执行,false 表非立即执行
 */
function debounce(func,wait,immediate) {
    let timeout;

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

        if (timeout) clearTimeout(timeout);
        if (immediate) {
            var callNow = !timeout;
            timeout = setTimeout(() => {
                timeout = null;
            }, wait)
            if (callNow) func.apply(context, args)
        }
        else {
            timeout = setTimeout(function(){
                func.apply(context, args)
            }, wait);
        }
    }
}

节流(throttle) 所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数。节流会稀释函数的执行频率。

/**
 * @desc 函数节流
 * @param func 函数
 * @param wait 延迟执行毫秒数
 * @param type 1 表时间戳版,2 表定时器版
 */
function throttle(func, wait ,type) {
    if(type===1){
        let previous = 0;
    }else if(type===2){
        let timeout;
    }
    return function() {
        let context = this;
        let args = arguments;
        if(type===1){
            let now = Date.now();

            if (now - previous > wait) {
                func.apply(context, args);
                previous = now;
            }
        }else if(type===2){
            if (!timeout) {
                timeout = setTimeout(() => {
                    timeout = null;
                    func.apply(context, args)
                }, wait)
            }
        }
    }
}

9、JavaScript 事件代理和委托

为父节点添加一个click事件,当子节点被点击的时候,click事件会从子节点开始向上冒泡。父节点捕获到事件之后,通过判断e.target.nodeName来判断是否为我们需要处理的节点。并且通过e.target拿到了被点击的Li节点。从而可以获取到相应的信息,并作处理。

10、理解js继承的6种方式

  1. 原型链继承
    让新实例的原型等于父类的实例
  2. 构造函数继承
    用.call()和.apply()将父类构造函数引入子类函数(在子类函数中做了父类函数的自执行(复制))
  3. 组合继承(组合原型链继承和借用构造函数继承)
    结合了两种模式的优点,传参和复用
  4. 原型式继承
    用一个函数包装一个对象,然后返回这个函数的调用,这个函数就变成了个可以随意增添属性的实例或对象。object.create()就是这个原理。
  5. 寄生式继承
    就是给原型式继承外面套了个壳子。
  6. 寄生组合式继承(常用)
    在函数内返回对象然后调用

11、call、apply、bind

可以改变this指向。
call用到构造函数复制父类实例给子类

// 父类
function SuperType (name) {
  this.name = name; // 父类属性
}
SuperType.prototype.sayName = function () { // 父类原型方法
  return this.name;
};

// 子类
function SubType () {
  // 调用 SuperType 构造函数
  // 在子类构造函数中,向父类构造函数传参
  SuperType.call(this, 'SuperType'); 
  // 为了保证子父类的构造函数不会重写子类的属性,需要在调用父类构造函数后,定义子类的属性
  this.subName = "SubType"; 
  // 子类属性
};

// 子类实例
let instance = new SubType(); 
// 运行子类构造函数,并在子类构造函数中运行父类构造函数,this绑定到子类

解决var作用域问题
var bottle = [
  {name: 'an', age: '24'},
  {name: 'anGe', age: '12'}
];

for (var i = 0; i < bottle.length; i++) {
  // 匿名函数
  (function (i) { 
    setTimeout(() => {
      // this 指向了 bottle[i]
      console.log('#' + i  + ' ' + this.name + ': ' + this.age); 
    }, 1000)
  }).call(bottle[i], i);
  // 调用 call 方法,同时解决了 var 作用域问题
}

apply() 方法调用一个具有给定this值的函数,以及作为一个数组(或[类似数组对象)提供的参数。

  1. call、apply 与 扩展运算符
let args = [1, 2, 3];

func.call(context, ...args); // 使用 spread 运算符将数组作为参数列表传递
func.apply(context, args);   // 与使用 call 相同
  • 扩展运算符 … 允许将 可迭代的 参数列表 作为列表传递给 call。
  • apply 只接受 类数组一样的 参数列表。
  1. apply 函数转移
    apply 最重要的用途之一是将调用传递给另一个函数,如下所示:
let wrapper = function() {
  return anotherFunction.apply(this, arguments);
};
  1. apply 连接数组
    array.push.apply 将数组添加到另一数组上:
var array = ['a', 'b']
var elements = [0, 1, 2]
array.push.apply(array, elements)
console.info(array) // ["a", "b", 0, 1, 2]
  1. apply 来链接构造器
Function.prototype.constructor = function (aArgs) {
  var oNew = Object.create(this.prototype);
  this.apply(oNew, aArgs);
  return oNew;
};
  1. apply 和内置函数
/* 找出数组中最大/小的数字 */
let numbers = [5, 6, 2, 3, 7]
/* 应用(apply) Math.min/Math.max 内置函数完成 */

let max = Math.max.apply(null, numbers) 
/* 基本等同于 Math.max(numbers[0], ...) 或 Math.max(5, 6, ..) */

let min = Math.min.apply(null, numbers)

console.log('max: ', max)
// max: 7
console.log('min: ', min)
// min: 2

Function.prototype.bind()
经常犯的一个错误是将一个方法从对象中拿出来,然后再调用,希望方法中的 this 是原来的对象(比如在回调中传入这个方法)。如果不做特殊处理的话,一般 this 就丢失了。
例如:

let bottle = {
  nickname: "bottle",
  sayHello() {
    console.log(`Hello, ${this.nickname}!`)
  },
  sayHi(){
    setTimeout(function(){
      console.log('Hello, ', this.nickname)
    }, 1000)
  }
};

// 问题一
bottle.sayHi();
// Hello, undefined!

// 问题二
setTimeout(bottle.sayHello, 1000); 
// Hello, undefined!

问题一的 this.nickname 是 undefined ,原因是 this 指向是在运行函数时确定的,而不是定义函数时候确定的,再因为 sayHi 中 setTimeout 在全局环境下执行,所以 this 指向 setTimeout 的上下文:window。

问题二的 this.nickname 是 undefined ,是因为 setTimeout 仅仅只是获取函数 bottle.sayHello 作为 setTimeout 回调函数,this 和 bottle 对象分离了。

bind() 最简单的用法是创建一个新绑定函数,当这个新绑定函数被调用时,this 键值为其提供的值,其参数列表前几项值为创建时指定的参数序列,绑定函数与被调函数具有相同的函数体(ES5中)。 问题二可以写为:

let bottle = {
  nickname: "bottle",
  sayHello() {
    console.log(`Hello, ${this.nickname}!`);
  },
  sayHi(){
    // 使用 bind
    setTimeout(function(){
      console.log('Hello, ', this.nickname)
    }.bind(this), 1000)

    // 或箭头函数
    setTimeout(() => {
      console.log('Hello, ', this.nickname)
    }, 1000)
  }
};

// 问题一:完美解决
bottle.sayHi()
// Hello,  bottle
// Hello,  bottle

let sayHello = bottle.sayHello.bind(bottle); // (*)

sayHello(); 
// Hello, bottle!

// 问题二:完美解决
setTimeout(sayHello, 1000); 
// Hello, bottle!

// 更新 bottle
bottle = {
  nickname: "haha",
  sayHello() {
    console.log(`Hi, ${this.nickname}!`)
  }
};

11、构造函数、原型、原型链

  1. 构造函数
    构造函数 ,是一种特殊的方法。主要用来在创建对象时初始化对象。每个构造函数都有prototype(原型)属性
  2. 原型
    每个函数都有prototype(原型)属性,这个属性是一个指针,指向一个对象,这个对象的用途是包含特定类型的所有实例共享的属性和方法,即这个原型对象是用来给实例共享属性和方法的。 而每个实例内部都有一个指向原型对象的指针。
  3. 原型链
    每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含指向原型对象内部的指针。我们让原型对象的实例(1)等于另一个原型对象(2), 此时原型对象(2)将包含一个指向原型对象(1)的指针, 再让原型对象(2)的实例等于原型对象(3),如此层层递进就构成了实例和原型的链条,这就是原型链的概念
  4. 要理解原型和原型链首先要知道几个概念:
    (1) 在js里,继承机制是原型继承。继承的起点是 对象的原型(Object prototype)。
    (2) 一切皆为对象,只要是对象,就会有 proto 属性,该属性存储了指向其构造的指针。 Object prototype也是对象,其 proto 指向null。
    (3) 对象分为两种:函数对象和普通对象,只有函数对象拥有『原型』对象(prototype)。
  • prototype的本质是普通对象。
  • Function prototype比较特殊,是没有prototype的函数对象。
  • new操作得到的对象是普通对象。
    (4) 当调取一个对象的属性时,会先在本身查找,若无,就根据 proto 找到构造原型,若无,继续往上找。最后会到达顶层Object prototype,它的 proto 指向null,均无结果则返回undefined,结束。
    (5) 由 proto 串起的路径就是『原型链』。