一、原型链

  1. 构造函数Person 通过prototype属性指向实例原型;实例原型也是一个对象,对象都有属性__proto__,实例原型通过__proto__里面的contructor指向构造函数Person。
  2. 构造函数Person实例person,为一个对象,通过__proto__指向实例原型
  3. 原型链为 实例person通过__proto__指向实例原型Person.prototype,实例原型通过__proto__指向实例原型的原型,......,最终指向根原型Object.prototype(因为Object.prototype的原型为null)

二、普通函数与箭头函数

1、区别
箭头函数是匿名函数,不能作为构造函数,不能使用new

let FunConstructor = () => {
    console.log('lll');
}

let fc = new FunConstructor();

箭头函数不绑定arguments,取而代之用rest参数...解决

function A(a){
  console.log(arguments);
}
A(1,2,3,4,5,8);  //  [1, 2, 3, 4, 5, 8, callee: ƒ, Symbol(Symbol.iterator): ƒ]


let B = (b)=>{
  console.log(arguments);
}
B(2,92,32,32);   // Uncaught ReferenceError: arguments is not defined


let C = (...c) => {
  console.log(c);
}
C(3,82,32,11323);  // [3, 82, 32, 11323]

箭头函数不绑定this,会捕获其所在的上下文的this值,作为自己的this值

var obj = {
  a: 10,
  b: () => {
    console.log(this.a); // undefined
    console.log(this); // Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}
  },
  c: function() {
    console.log(this.a); // 10
    console.log(this); // {a: 10, b: ƒ, c: ƒ}
  }
}
obj.b(); 
obj.c();
var obj = {
  a: 10,
  b: function(){
    console.log(this.a); //10
  },
  c: function() {
     return ()=>{
           console.log(this.a); //10
     }
  }
}
obj.b(); 
obj.c()();

箭头函数通过 call() 或 apply() 方法调用一个函数时,只传入了一个参数,对 this 并没有影响。

let obj2 = {
    a: 10,
    b: function(n) {
        let f = (n) => n + this.a;
        return f(n);
    },
    c: function(n) {
        let f = (n) => n + this.a;
        let m = {
            a: 20
        };
        return f.call(m,n);
    }
};
console.log(obj2.b(1));  // 11
console.log(obj2.c(1)); // 11

箭头函数没有原型属性

var a = ()=>{
  return 1;
}

function b(){
  return 2;
}

console.log(a.prototype);  // undefined
console.log(b.prototype);   // {constructor: ƒ}

箭头函数不能当做Generator函数,不能使用yield关键字

2、JS this指向问题
普通函数的this的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定this到底指向谁,实际上this的最终指向的是那个调用它的对象。
箭头函数比较特殊没有调用者,不存在this.箭头函数()的概念,但是它内部可以有this,而内部的this由上下文决定
例子1:


var o = {
    user:"追梦子",
    fn:function(){
        console.log(this.user);  //追梦子
    }
}
o.fn();

这里的this指向的是对象o,因为你调用这个fn是通过o.fn()执行的,那自然指向就是对象o。

例子2:

var o = {
    a:10,
    b:{
        // a:12,
        fn:function(){
            console.log(this.a); //undefined
        }
    }
}
o.b.fn();

尽管对象b中没有属性a,这个this指向的也是对象b,因为this只会指向它的上一级对象,不管这个对象中有没有this要的东西。

例子3:

var o = {
    a:10,
    b:{
        a:12,
        fn:function(){
            console.log(this.a); //undefined
            console.log(this); //window
        }
    }
}
var j = o.b.fn;
j();

这里this指向的是window,this永远指向的是最后调用它的对象,也就是看它执行的时候是谁调用的。

function fn(){
    this.num = 1;
}
var a = new fn();
console.log(a.num); //1

  为什么this会指向a?首先new关键字会创建一个空的对象,然后会自动调用一个函数apply方法,将this指向这个空对象,这样的话函数内部的this就会被这个空的对象替代。

注: 1 .在严格版中的默认的this不再是window,而是undefined。

  2. new操作符会改变函数this的指向问题。

三、JS数组相关问题

1、二维数组转为一维数组

  1. 利用Array.prototype.flat()实现
    语法:var newArray = arr.flat(depth),参数说明:depth,可选,指定嵌套数组中的结构深度,默认值为1

特殊说明:flat()方法会移除数组中的空项。但undefined、null仍会保留。

var arr = [1, 2, undefined , 4, 5, null];
arr.flat(); // [1, 2, undefined , 4, 5, null]
  1. 利用apply实现
var arr1 = [[0, 1], [2, 3], [4, 5]];
function flatten(arr) { return [].concat( ...arr.map(x => Array.isArray(x) ? flatten(x) : x) ) }
var arr2 = flatten(arr1); // arr2 [0, 1, 2, 3, 4, 5]
  1. 利用es6
var arr1 = [[0, 1], [2, 3], [4, 5]];
function flatten(arr) { return [].concat( ...arr.map(x => Array.isArray(x) ? flatten(x) : x) ) }
var arr2 = flatten(arr1); // arr2 [0, 1, 2, 3, 4, 5]

//优点: 多维数组也可以
//比如:var arr = [[1,2],3,[[[4], 5]]]

2、数组去重

  1. 利用ES6 Set去重(ES6中最常用)
function unique (arr) {
  return Array.from(new Set(arr))
}
  1. 利用indexOf去重
function unique(arr) {
    if (!Array.isArray(arr)) {
      console.log('type error!')
      return
    }
    var array = [];
    for (var i = 0; i < arr.length; i++) {
      if (array.indexOf(arr[i]) === -1) {
        array.push(arr[i])
      }
    }
    return array;
}
  1. 利用Object键值对去重
  //将数组转换成对象
  //利用对象的key值不能重复这一特性
  var toObject = function(array) {
     var obj = {};
     for(var i=0,j=array.length;i<j;i++){
        obj[array[i]] = true;
     }
     return obj;
  }

  //再将对象转换成数组
  var toArray = function(obj){
    var arr = [];
    for(var i in obj){
      arr.push(i);
    }
    return arr;
  }

  //综合前两者,完成去数组重复项方法
  var uiq = function(arr){
      return toArray(toObject(arr));
  }

四、浅拷贝和深拷贝

浅拷贝
先拷贝,只是简单的对象引用,并没有真正的从内存中开辟一块空间。

深拷贝
将一个对象从内存中完整的拷贝一份出来,开辟一个新的区域空间来存放对象。

js中的浅拷贝
基本数据类型:名字和值都存在栈内存中
引用数据类型,名字存在栈内存中,值存在堆内存中。
实现的几种方式

Object.assign()
如果数组元素包含对象, 数组的方法concat为浅拷贝
扩展运算符

js中的深拷贝
使用JSON.parse序列化,这样子可以解决大多数情况,但不能处理函数。
使用递归,来动手写一个深拷贝。
实现的几种方式

元素全部为基础类型的数组, 数组的方法concat为深拷贝
数组的方法map、filter、slice
let a = {}, let b = JSON.pase(JSON.stringify(a));
注: JSON.stringify()转换对象过程中,undefined、任意的函数以及 symbol 值,在序列化过程中会被忽略(出现在非数组对象的属性值中时)或者被转换成null(出现在数组中时)。函数、undefined 被单独转换时,会返回 undefined。

递归拷贝方法实现

// 定义一个深拷贝函数  接收目标target参数
function deepClone(target) {
    // 定义一个变量
    let result;
    // 如果当前需要深拷贝的是一个对象的话
    if (typeof target === 'object') {
    // 如果是一个数组的话
        if (Array.isArray(target)) {
            result = []; // 将result赋值为一个数组,并且执行遍历
            for (let i in target) {
                // 递归克隆数组中的每一项
                result.push(deepClone(target[i]))
            }
         // 判断如果当前的值是null的话;直接赋值为null
        } else if(target===null) {
            result = null;
         // 判断如果当前的值是一个RegExp对象的话,直接赋值    
        } else if(target.constructor===RegExp){
            result = target;
        }else {
         // 否则是普通对象,直接for in循环,递归赋值对象的所有值
            result = {};
            for (let i in target) {
                result[i] = deepClone(target[i]);
            }
        }
     // 如果不是对象的话,就是基本数据类型,那么直接赋值
    } else {
        result = target;
    }
     // 返回最终结果
    return result;
}

五、JS数字精度丢失的问题

1、一些典型问题

  1. 两个简单的浮点数相加
0.1 + 0.2 != 0.3 // true
  1. 大整数运算
9999999999999999 == 10000000000000001 // true

9007199254740992 + 1  ==  9007199254740992 // true
  1. toFixed不会四舍五入(Chrome)
1.335.toFixed(2) // 1.33

2、解决方案

  1. toFixed()
    因为toFixed() 进行并转换之后是string类型的,需要在进行强制Number() 转换
Number((0.1+0.2).toFixed(2))
  1. 一些类库

    math.js

  2. 转为整数

对于整数,前端出现问题的几率可能比较低,毕竟很少有业务需要需要用到超大整数,只要运算结果不超过 Math.pow(2, 53) 就不会丢失精度。
对于小数,前端出现问题的几率还是很多的,尤其在一些电商网站涉及到金额等数据。解决方式:把小数放到位整数(乘倍数),再缩小回原来倍数(除倍数)。

// 0.1 + 0.2

(0.1*10 + 0.2*10) / 10 == 0.3 // true

六、函数的节流与防抖

  1. 概念
  • 节流:事件触发后,规定时间内,事件处理函数不能再次被调用。也就是说在规定的时间内,函数只能被调用一次,且是最先被触发调用的那次。
  • 防抖:多次触发事件,事件处理函数只能执行一次,并且是在触发操作结束时执行。也就是说,当一个事件被触发准备执行事件函数前,会等待一定的时间,如果没有再次触发,那么就执行,如果被触发了,那就本次作废,重新从新触发的时间开始计算,并再次等待1秒,知道能最终执行。
  1. 使用场景
  • 节流:滚动加载更多、搜索框搜索联想功能、高频点击、表单重复提交
  • 防抖:搜索框搜索输入,并输入完成以后自动搜索;手机号,邮箱验证输入检测;窗口大小resize变化后,再重新渲染
  1. 代码实例
/*
* 函数节流
*/
function throttle(fn, wait) {
    let last = 0
    let dur = wait || 400
    return function() {
        let self = this
        const current_time = new Date()
        if(current_time - last > dur) {
            fn.apply(self, arguments)
            last =+ new Date()
        }
    }
}

/*
* 函数防抖
*/
function throttle(fn, wait) {
    let timer
    let dur = wait || 400
    return function() {
        clearTimeout(timer)
        let self = this
        let args = arguments
        timer = setTimeout(function() {
            fn.apply(self, args)
        }, dur)
    }
}

七、Github Flow


八、移动端常见问题

1.1 CSS 动画页面闪白,动画卡顿 解决方法:

  • 尽可能地使用合成属性 transform 和 opacity 来设计 CSS3 动画,不使用 position 的 left 和 top 来定位
  • 开启硬件加速

1.2 上下拉动滚动条时卡顿、慢

body {
  -webkit-overflow-scrolling: touch;
  overflow-scrolling: touch;
}

Android3+ 和 iOS5+支持 CSS3 的新属性为 overflow-scrolling

1.3 手势事件的应用

  • 可使用touchstart或者touchend事件,代替click事件,用来触发移动端的点击事件
  • 可使用touchmove事件代替scroll事件,来检测移动端的滑动事件。并且可以获取滚动条滚动的高度。

1.4 固定定位布局键盘挡住输入框内容

// input点击事件或获取焦点事件中
document.querySelector('#input').scrollIntoView()

1.5 如何解决phone 及 ipad 下输入框默认内阴影

element {
  -webkit-appearance: none;
}

1.6 如何解决Android手机圆角失效问题

background-clip: padding-box;

1.7 在ios和Android下,如何实现触摸元素时出现半透明灰色遮罩

element {
  -webkit-tap-highlight-color: rgba(255, 255, 255, 0);
}

1.8 如何解决长时间按住页面出现闪退的问题

element {
  -webkit-touch-callout: none;
}

朦胧之月
3 声望1 粉丝

爱编程、爱生活