手写题

124 阅读14分钟

this 指向考察

var length = 88;
function test () {
console.log(this.length)
}
var obj = {
length: 90,
action: function (test) {
  test()
  arguments[0]()
}
}
obj.action(test, [1, 2, 3])
// 88 2

函数没有调用者,默认指向全局 window(严格模式下 undefined)

// 解析分析
第一问:
test()
由于 test()没有调用者,this 指向是 window,所以 this.length就是88
第二问:
arguments[0]();
arguments[0]指向 test
arguments[0](): 表示 test(),this的指向是 arguments,所以 this.length就是 arguments.length

作用域

var a = 10
function test () {
  console.log(a)
  a = 100
  console.log(this.a)
  var a
  console.log(a)
}

test()
// undefined 10 100
// 解析分析
var a = 10
function test () {
  console.log(a) // 变量声明提升,但未赋值
  a = 100
  console.log(this.a)// this 指向 window window.a
  var a
  console.log(a) // 变量声明,且赋值100
}

美团面试题

var a = 10
function f1 () {
  var b = 2 * a
  var a = 20
  var c = a + 1
  console.log(b)
  console.log(c)
}
f1()
// NaN 21
// 解析分析
NaN 是`not a number`的缩写,表示不是一个合法的数字

NaN 的产生

  • 一个不能被解析的数字
Number('abc') // NaN
Number(undefined) // NaN
  • 失败的操作
Math.log(-1) // NaN
Math.sqrt(-1) // NaN
Math.acos(2) // NaN
  • 一个运算符为NaN
NaN + 1 // NaN
10 / NaN // NaN

ES6新特性

var let 区别

  • 不存在变量提升

  • 同一个作用域下不能重复定义同一个名称 let const都不可 let 可以重复赋值,const 声明的常量不可修改,引用类型可以赋值,声明就一定得赋值

  • 有着严格的作用域

function fun () {
  var n = 10
  if (true) {
    var n = 100
  }
  console.log(n)
}
fun()
// 100 var存在变量提升,后面会覆盖前面,var 属于函数作用域,在函数内部任何地方都能拿到
function fun () {
  let n = 10
  if (true) {
    let n = 100
  }
  console.log(n)
}
fun()
// 10 let 不存在变量提升,let 作用域为块级作用域,即{}就为一个块级作用域

箭头函数 this指向定义时的 this,与调用时机无关

set map

  • set 类似于数组,成员是唯一的
  • map 类似于对象,以键值对的方式添加属性
const s = new Set()
s.add(1).add(2).add(3).add(2)
// {1, 2, 3}

1、数组去重

var arr1 = [1,3,4,5,1,2,3]
var arr2 = [...new Set(arr1)]
// [1,2,3,4,5]
m.set('name', 'xiyan').set('age', '18')
for (let [key, value] of m) {
  console.log(key, value)
}
// name xiyan
// age 18

变量提升

(function () {
  var a = b = 3
})()
console.log(b) // 3
console.log(a) // ReferenceError: a is not defined
console.log(b) // 打印不出来,因为逻辑在上面就已经保错了
var fun = 123
function fun () {
  console.log(fun)
  fun = 345
  console.log(fun)
}
fun()
// 报错 fun is not a function

变量提升函数预解析优先于变量预解析


function fun () {
  console.log(fun)
  fun = 345
  console.log(fun)
}
fun()
var fun = 123
// function 345
var n = 123
function f1 () {
  console.log(n) // 123
}
function f2 () {
  var n = 456;
  f1() // 虽然在 f2中被调用,但是无调用者,作用域任然是 window
}
f2()
console.log(n) // 123
function fun () {
  console.log(this.a)
}

var obj = {
  a: 2,
  f: fun
}
var f2 = obj.f
var a = 'hello'
f2() // 无调用者 this === window
obj.f() // 有调用者,调用者为 obj
function m () {
  console.log(a1);
  console.log(a2);
  console.log(b1);
  console.log(b2);
  if (false) {
    function b1 () { }
    var a1 = 100
  }
  if (true) {
    function b2 () { }
    var a2 = 10
  }
  console.log(a1);
  console.log(a2);
  console.log(b1);
  console.log(b2);
}

m()
// undefined
// undefined
// undefined
// undefined
// undefined
// 10
// undefined
// f b2(){}

代码块没有预解析,代码块内部声明的变量,在外部也可以访问

(function f (num) {
  function num () { }
  console.log(num);
  var num = 10
  console.log(num)
})(100)

函数声明优先于变量声明

function n () {
  if (2 > 1) {
    arr = 10;
    brr = 10
    let arr
    var brr
    console.log(arr)
    console.log(brr)
  }
}
n()
// ReferenceError: Cannot access 'arr' before initialization

let 无变量提升

for 循环

for (var i = 0; i < 5; i++) {
  setTimeout(() => {
    console.log(i)
  }, 0)
}
// 5 5 5 5 5 

for 循环属于同步任务,var 相当于全局作用域,setTimeOut 属于异步任务,所以会先执行完同步任务 i=4的时候,再执行 setTimeOut

  • 解决方案一 let 块级作用域
for (let i = 0; i < 5; i++) {
  setTimeout(() => {
    console.log(i)
  }, 0)
}
// 0 1 2 3 4

let 产生了块级作用域,三次循环产生3个块级作用域,i 值分别为1, 2, 3

  • 解决方案二
for (var i = 0; i < 5; i++) {
  (function (j) {
    setTimeout(() => {
      console.log(j)
    })
  })(i)
}
console.log(i)
  • 解决方案三
function output (j) {
  setTimeout(() => {
    console.log(j)
  })
}
for (var i = 0; i < 5; i++) {
  output(i)
}
console.log(i)
  • 解决方案四

定义异步任务

let task = []
for (var i = 0; i < 5; i++) {
  ((j) => {
    task.push(new Promise((resolve) => {
      setTimeout(() => {
        console.log(j)
        resolve()
      }, 500 * j) // 时间如果写成固定值,会在延迟固定值后一起输出
    }))
  })(i)
}
// 等待所有异步任务执行完成
Promise.all(task).then(() => {
  setTimeout(() => {
    console.log(i)
  })
})
let task = []
function output (j) {
  new Promise((resolve) => {
    setTimeout(() => {
      console.log(j)
      resolve()
    }, 500 * j)
  })
}
for (var i = 0; i < 5; i++) {
  (() => {
    task.push(output(i))
  })(i)
}

Promise.all(task).then(() => {
  console.log(i)
})
// 5 0 1 2 3 4 
let task = []
function output (j) {
  new Promise((resolve) => {
    console.log(j)
    resolve()
  })
}
for (var i = 0; i < 5; i++) {
  (() => {
    task.push(output(i))
  })(i)
}

Promise.all(task).then(() => {
  console.log(i)
})
// 不加延时,会一次全输出
let task = []
function output (j) {
  new Promise((resolve) => {
    setTimeout(() => {
      console.log(j)
      resolve()
    }, 500 * j)
  })
}
for (var i = 0; i < 5; i++) {
  (() => {
    task.push(output(i))
  })(i)
}

Promise.all(task).then(() => {
  setTimeout(() => {
    console.log(i)
  }, 500*i)
})
// 0 1 2 3 4 5

连等符号

var a = { n: 1 }
var b = a
a.x = a = { n: 2 }
console.log(a.x)
console.log(b.x)
// undefined
// {n:2}

连等开始之前程序会把所有引用都保存下来,连等的过程中,这些值不变的,等到整个连等都完事了,再一块变

防抖

function debounce (fn, wait) {
  let timeout
  return function () {
    const cxt = this
    const args = arguments
    clearTimeout(timeout)
    timeout = setTimeout(() => {
      fn.call(cxt, args)
    }, wait)
  }
}

节流

// 时间戳
function throttle (fn, wait) {
  let preTime = 0
  return function () {
    const cxt = this
    const args = arguments
    const nowTime = new Date()
    if (nowTime - preTime > wait) {
      fn.apply(cxt, args)
      preTime = nowTime
    }
  }
}

// 定时器
function throttle (fn, wait) {
  let timeOut = null
  return function () {
    if (!timeOut) {
      const cxt = this
      const args = arguments
      timeOut = setTimeout(() => {
        fn.apply(cxt, args)
        timeOut = null
      }, wait)
    }
  }
}

实现 deepClone

let oldObj = {
  userName: 'xiyan',
  age: 18,
  hobby: ['read', 'white', 'walk', 'run'],
  field4: {
    child: 'child',
    child2: {
      child2: 'child2'
    }
  }
}

function clone (oldObj) {
  const newObj = {}
  for (const key in oldObj) {
    newObj[key] = oldObj[key]
  }
  return newObj
}

const test1 = clone(oldObj)

let newObj = {}
function deepClone (newObj, oldObj) {
  for (const key in oldObj) {
    let value = oldObj[key]
    if (typeof value === 'object') {
      newObj[key] = Array.isArray(value) ? [] : {}
      deepClone(newObj[key], value)
    } else {
      newObj[key] = value
    }
  }
}

deepClone(newObj, oldObj)

实现 add(1)(2)(3)

主要思路是什么呢,要判断当前传入函数的参数个数 (args.length) 是否大于等于原函数所需参数个数 (fn.length) ,如果是,则执行当前函数;如果是小于,则返回一个函数。

const curry = (fn, ...args) => 
    // 函数的参数个数可以直接通过函数数的.length属性来访问
    args.length >= fn.length // 这个判断很关键!!!
    // 传入的参数大于等于原始函数fn的参数个数,则直接执行该函数
    ? fn(...args)
    /**
     * 传入的参数小于原始函数fn的参数个数时
     * 则继续对当前函数进行柯里化,返回一个接受所有参数(当前参数和剩余参数) 的函数
    */
    : (..._args) => curry(fn, ...args, ..._args);

function add1(x, y, z) {
    return x + y + z;
}
const add = curry(add1);
console.log(add(1, 2, 3));
console.log(add(1)(2)(3));
console.log(add(1, 2)(3));
console.log(add(1)(2, 3));

柯里化主要有3个作用: 参数复用提前返回延迟执行

我们来简单的解释一下: 参数复用:拿上面 f这个函数举例,只要传入一个参数 z,执行,计算结果就是 1 + 2 + z 的结果,1 和 2 这两个参数就直接可以复用了。

提前返回 和 延迟执行 也很好理解,因为每次调用函数时,它只接受一部分参数,并返回一个函数(提前返回),直到(延迟执行)传递所有参数为止。

大数运算

JS 在存放整数的时候是有一个安全范围的,一旦数字超过这个范围便会损失精度。

我们不能拿精度损失的数字进行运行,因为运算结果一样是会损失精度的。

所以,我们要用字符串来表示数据!(不会丢失精度)

parseInt() 函数可解析一个字符串,并返回一个整数。

Math.floor() 方法可对一个数进行下舍入。

let a = "9007199254740991";
let b = "1234567899999999999";
function add (a, b) {
  let maxLength = Math.max(a.length, b.length)
  console.log(maxLength)
  a = a.padStart(maxLength, 0)
  b = b.padStart(maxLength, 0)
  let t = 0 // 余数
  let f = 0 // 进位
  let sum = ''
  for (let i = maxLength - 1; i >= 0; i--) {
    t = parseInt(a[i]) + parseInt(b[i]) + f
    f = Math.floor(t / 10)
    sum = t % 10 + sum
  }
  if (f === 1) {
    sum = '1' + sum
  }
}

add(a, b)

数组拍平

遍历数组的方案

  • for 循环
  • for...of
  • for...in
  • forEach()
  • entries()
  • keys()
  • values()
  • reduce()
  • map()
  • every
  • some
  • filter 判断是否是数组的方法

1、instanceof 操作符判断是否是Array 是否是它的实例

let arr = [];
console.log(arr instanceof Array); // true

2、arr.constructor === Array

let arr = [];
console.log(arr.constructor === Array); // true

3、用法:Array.prototype.isPrototypeOf(arr)
Array.prototype  属性表示 Array 构造函数的原型
其中有一个方法是 isPrototypeOf() 用于测试一个对象是否存在于另一个对象的原型链上。

let arr = [];
console.log(Array.prototype.isPrototypeOf(arr)); // true

4、用法:Object.getPrototypeOf(arr) === Array.prototype
Object.getPrototypeOf() 方法返回指定对象的原型

所以只要跟Array的原型比较即可

let arr = [];
console.log(Object.prototype.toString.call(arr) === '[object Array]'); // true

5、用法:Object.prototype.toString.call(arr) === '[object Array]'

虽然Array也继承自Object,但js在Array.prototype上重写了toString,而我们通过toString.call(arr)实际上是通过原型链调用了

let arr = [];
console.log(Object.prototype.toString.call(arr) === '[object Array]'); // true

6、用法:Array.isArray(arr)
ES5中新增了Array.isArray方法,IE8及以下不支持

Array.isArray ( arg )
isArray 函数需要一个参数 arg,如果参数是个对象并且 class 内部属性是 "Array", 返回布尔值 true;否则它返回 false。采用如下步骤:
如果 Type(arg) 不是 Object, 返回 false。
如果 arg 的 [[Class]] 内部属性值是 "Array", 则返回 true。
返回 false.

let arr = [];
console.log(Array.isArray(arr)); // true

forEach(): 针对每一个元素执行提供的函数(executes a provided function once for each array element)。\

map(): 创建一个新的数组,其中每一个元素由调用数组中的每一个元素执行提供的函数得来(creates a new array with the results of calling a provided function on every element in the calling array)。\

到底有什么区别呢?forEach()方法不会返回执行结果,而是undefined。也就是说,forEach()会修改原来的数组。而map()方法会得到一个新的数组并返回。

一般方式

const arr = [1, 2, 3, 4, [1, 2, 3, [1, 2, 3, [1, 2, 3]]], 5, "string", { name: "弹铁蛋同学" }];
// 扩展运算符 + concat
[].concat(...arr)
// [1, 2, 3, 4, 1, 2, 3, [1, 2, 3, [1, 2, 3]], 5, "string", { name: "弹铁蛋同学" }];

// concat + apply
[].concat.apply([], arr);
// [1, 2, 3, 4, 1, 2, 3, [1, 2, 3, [1, 2, 3]], 5, "string", { name: "弹铁蛋同学" }];

// toString  + split
const arr2 =[1, 2, 3, 4, [1, 2, 3, [1, 2, 3, [1, 2, 3]]]]
arr2.toString().split(',').map(v=>parseInt(v))
// [1, 2, 3, 4, 1, 2, 3, 1, 2, 3, 1, 2, 3]

递归方式

const arr = [1, 2, 3, 4, [1, 2, 3, [1, 2, 3, [1, 2, 3]]], 5, "string", { name: "弹铁蛋同学" }];
// concat + 递归
function flat(arr) {
  let arrResult = [];
  arr.forEach(item => {
    if (Array.isArray(item)) {
      arrResult = arrResult.concat(arguments.callee(item));   // 递归
      // 或者用扩展运算符
      // arrResult.push(...arguments.callee(item));
    } else {
      arrResult.push(item);
    }
  });
  return arrResult;
}
flat(arr)
// [1, 2, 3, 4, 1, 2, 3, 1, 2, 3, 1, 2, 3, 5, "string", { name: "弹铁蛋同学" }];

reduce 方式

const arr = [1, 2, 3, 4, [1, 2, 3, [1, 2, 3, [1, 2, 3]]], 5, "string", { name: "弹铁蛋同学" }]

// 首先使用 reduce 展开一层
arr.reduce((pre, cur) => pre.concat(cur), []);
// [1, 2, 3, 4, 1, 2, 3, [1, 2, 3, [1, 2, 3]], 5, "string", { name: "弹铁蛋同学" }];

// 用 reduce 展开一层 + 递归
const flat = arr => {
  return arr.reduce((pre, cur) => {
    return pre.concat(Array.isArray(cur) ? flat(cur) : cur);
  }, []);
};
// [1, 2, 3, 4, 1, 2, 3, 1, 2, 3, 1, 2, 3, 5, "string", { name: "弹铁蛋同学" }];

使用栈的思想

// 栈思想
function flat(arr) {
  const result = []; 
  const stack = [].concat(arr);  // 将数组元素拷贝至栈,直接赋值会改变原数组
  //如果栈不为空,则循环遍历
  while (stack.length !== 0) {
    const val = stack.pop(); 
    if (Array.isArray(val)) {
      stack.push(...val); //如果是数组再次入栈,并且展开了一层
    } else {
      result.unshift(val); //如果不是数组就将其取出来放入结果数组中
    }
  }
  return result;
}
const arr = [1, 2, 3, 4, [1, 2, 3, [1, 2, 3, [1, 2, 3]]], 5, "string", { name: "弹铁蛋同学" }]
flat(arr)
// [1, 2, 3, 4, 1, 2, 3, 1, 2, 3, 1, 2, 3, 5, "string", { name: "弹铁蛋同学" }];

通过传入整数参数控制‘拉平’层数

// reduce + 递归
function flat(arr, num = 1) {
  return num > 0
    ? arr.reduce(
        (pre, cur) =>
          pre.concat(Array.isArray(cur) ? flat(cur, num - 1) : cur),
        []
      )
    : arr.slice();
}
const arr = [1, 2, 3, 4, [1, 2, 3, [1, 2, 3, [1, 2, 3]]], 5, "string", { name: "弹铁蛋同学" }]
flat(arr, Infinity);
// [1, 2, 3, 4, 1, 2, 3, 1, 2, 3, 1, 2, 3, 5, "string", { name: "弹铁蛋同学" }];

使用 Generator 实现 flat 函数

function* flat(arr, num) {
  if (num === undefined) num = 1;
  for (const item of arr) {
    if (Array.isArray(item) && num > 0) {   // num > 0
      yield* flat(item, num - 1);
    } else {
      yield item;
    }
  }
}
const arr = [1, 2, 3, 4, [1, 2, 3, [1, 2, 3, [1, 2, 3]]], 5, "string", { name: "弹铁蛋同学" }]
// 调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象。
// 也就是遍历器对象(Iterator Object)。所以我们要用一次扩展运算符得到结果
[...flat(arr, Infinity)]    
// [1, 2, 3, 4, 1, 2, 3, 1, 2, 3, 1, 2, 3, 5, "string", { name: "弹铁蛋同学" }];

数组去重

1、双循环去重

双重 for 循环是一个比较笨拙的方法,它的实现原理是创建一个包含原数组第一项的新数组,然后遍历原始数组,将原始数组中的每个元素与新数组中的每个元素进行比对,如果不重复则添加到新数组中,最后返回新数组,如果数组长度很大,那么将会非常耗费内存

function unique(arr) { 
    if (!Array.isArray(arr)) { 
        console.log('type error!') 
        return 
    } 
    let res = [arr[0]] 
    for (let i = 1; i < arr.length; i++) { 
        let flag = true 
        for (let j = 0; j < res.length; j++) { 
            if (arr[i] === res[j]) { 
                flag = false; 
                break 
            } 
        } 
        if (flag) { 
            res.push(arr[i]) 
        } 
    } 
    return res 
}

2、indexOf方法去重1

数组的 indexOf()方法返回指定的元素在数组中首次出现的位置,该方法首先定义一个空数组,然后调用indexOf方法对原来的数组进行遍历判断,如果元素不在res中,则将其push进res中,最后将res返回即可获得去重的数组

function unique(arr) {
    if (!Array.isArray(arr)) {
        console.log('type error!')
        return
    }
    let res = []
    for (let i = 0; i < arr.length; i++) {
        if (res.indexOf(arr[i]) === -1) {
            res.push(arr[i])
        }
    }
    return res
}

3、indexOf方法去重2

利用indexOf检测元素在数组中第一次出现的位置是否和元素现在的位置相等,如果不等则说明该元素是重复元素

function unique(arr) {
    if (!Array.isArray(arr)) {
        console.log('type error!')
        return
    }
    return Array.prototype.filter.call(arr, function(item, index){
        return arr.indexOf(item) === index;
    });
}

4、相邻元素去重

这种方法首先调用了数组的排序方法sort(),然后根据排序后的结果进行遍历及相邻元素比对,如果相等则跳过改元素,直到遍历结束

function unique(arr) {
    if (!Array.isArray(arr)) {
        console.log('type error!')
        return
    }
    arr = arr.sort()
    let res = []
    for (let i = 0; i < arr.length; i++) {
        if (arr[i] !== arr[i-1]) {
            res.push(arr[i])
        }
    }
    return res
}

5、利用对象属性去重

创建空对象,遍历数组,将数组中的值设为对象的属性,并给该属性赋初始值1,每出现一次,对应的属性值增加1,这样,属性值对应的就是该元素出现的次数了

function unique(arr) {
    if (!Array.isArray(arr)) {
        console.log('type error!')
        return
    }
    let res = [],
        obj = {}
    for (let i = 0; i < arr.length; i++) {
        if (!obj[arr[i]]) {
            res.push(arr[i])
            obj[arr[i]] = 1
        } else {
            obj[arr[i]]++
        }
    }
    return res
}

6、set与结构赋值去重

ES6中新增了数据类型set,set的一个最大的特点就是数据不重复。Set函数可以接受一个数组(或类数组对象)作为参数来初始化,利用该特性也能做到给数组去重

function unique(arr) {
    if (!Array.isArray(arr)) {
        console.log('type error!')
        return
    }
    return [...new Set(arr)]
}

7、Array.from与set去重

Array.from方法可以将Set结构转换为数组结果,而我们知道set结果是不重复的数据集,因此能够达到去重的目的

function unique(arr) {
    if (!Array.isArray(arr)) {
        console.log('type error!')
        return
    }
    return Array.from(new Set(arr))
}

对象去重

var array = [1, 2, 1, 1, '1'];

function unique(array) {
    var obj = {};
    return array.filter(function(item, index, array){
        return obj.hasOwnProperty(item) ? false : (obj[item] = true)
    })
}

console.log(unique(array)); // [1, 2]

// 我们可以发现,是有问题的,因为 1 和 '1' 是不同的,但是这种方法会判断为同一个值,这是因为对象的键值只能是字符串,所以我们可以使用 `typeof item + item` 拼成字符串作为 key 值来避免这个问题:

var array = [1, 2, 1, 1, '1'];

function unique(array) {
    var obj = {};
    return array.filter(function(item, index, array){
        return obj.hasOwnProperty(typeof item + item) ? false : (obj[typeof item + item] = true)
    })
}

console.log(unique(array)); // [1, 2, "1"]

排序后去重

var array = [1, 1, '1'];

function unique(array) {
    var res = [];
    var sortedArray = array.concat().sort();
    var seen;
    for (var i = 0, len = sortedArray.length; i < len; i++) {
        // 如果是第一个元素或者相邻的元素不相同
        if (!i || seen !== sortedArray[i]) {
            res.push(sortedArray[i])
        }
        seen = sortedArray[i];
    }
    return res;
}

console.log(unique(array));

filter

// 使用 indexOf 方法
var array = [1, 2, 1, 1, '1'];

function unique(array) {
    var res = array.filter(function(item, index, array){
        return array.indexOf(item) === index;
    })
    return res;
}

console.log(unique(array));

// 使用排序 sort 方法
var array = [1, 2, 1, 1, '1'];

function unique(array) {
    return array.concat().sort().filter(function(item, index, array){
        return !index || item !== array[index - 1]
    })
}

console.log(unique(array));

es6

// 1
var array = [1, 2, 1, 1, '1'];

function unique(array) {
   return Array.from(new Set(array));
}

console.log(unique(array)); // [1, 2, "1"]



// 2
function unique(array) {
    return [...new Set(array)];
}




// 3
var unique = (a) => [...new Set(a)]



// 4
function unique (arr) {
    const seen = new Map()
    return arr.filter((a) => !seen.has(a) && seen.set(a, 1))
}

实现千位分隔符

function numFormat(num){
    num=num.toString().split(".");  // 分隔小数点
    var arr=num[0].split("").reverse();  // 转换成字符数组并且倒序排列
    var res=[];
    for(var i=0,len=arr.length;i<len;i++){
      if(i%3===0&&i!==0){
         res.push(",");   // 添加分隔符
      }
      res.push(arr[i]);
    }
    res.reverse(); // 再次倒序成为正确的顺序
    if(num[1]){  // 如果有小数的话添加小数部分
      res=res.join("").concat("."+num[1]);
    }else{
      res=res.join("");
    }
    return res;
}

var a=1234567894532;
var b=673439.4542;
console.log(numFormat(a)); // "1,234,567,894,532"
console.log(numFormat(b)); // "673,439.4542"

js自带的函数 toLocaleString()

语法: `numObj.toLocaleString([locales [, options]])`
var a=1234567894532;
var b=673439.4542;

console.log(a.toLocaleString());  // "1,234,567,894,532"
console.log(b.toLocaleString());  // "673,439.454"  (小数部分四舍五入了)

使用正则表达式replace函数,相对前两种我更喜欢这种方法

function numFormat(num){
  var res=num.toString().replace(/\d+/, function(n){ // 先提取整数部分
       return n.replace(/(\d)(?=(\d{3})+$)/g,function($1){
          return $1+",";
        });
  })
  return res;
}

var a=1234567894532;
var b=673439.4542;
console.log(numFormat(a)); // "1,234,567,894,532"
console.log(numFormat(b)); // "673,439.4542"

继承(含es6)、多种继承方式

(1)第一种是以原型链的方式来实现继承

function Parent () {
    this.name = 'kevin';
}

Parent.prototype.getName = function () {
    console.log(this.name);
}

function Child () {
}

Child.prototype = new Parent();

var child1 = new Child();

console.log(child1.getName()) // kevin

缺点:

1、引用类型的属性被所有实例共享

2、在创建 Child 的实例时,不能向Parent传参

(2)第二种方式是使用借用构造函数的方式,这种方式是通过在子类型的函数中调用超类型的构造函数来实现的,这一种方法解决了不能向超类型传递参数的缺点,但是它存在的一个问题就是无法实现函数方法的复用,并且超类型原型定义的方法子类型也没有办法访问到。

function Parent () {
    this.names = ['kevin', 'daisy'];
}

function Child () {
    Parent.call(this);
}

var child1 = new Child();

child1.names.push('yayu');

console.log(child1.names); // ["kevin", "daisy", "yayu"]

var child2 = new Child();

console.log(child2.names); // ["kevin", "daisy"]

优点:

1.避免了引用类型的属性被所有实例共享

2.可以在 Child 中向 Parent 传参

(3)第三种方式是组合继承,组合继承是将原型链和借用构造函数组合起来使用的一种方式。通过借用构造函数的方式来实现类型的属性的继承,通过将子类型的原型设置为超类型的实例来实现方法的继承。这种方式解决了上面的两种模式单独使用时的问题,但是由于我们是以超类型的实例来作为子类型的原型,所以调用了两次超类的构造函数,造成了子类型的原型中多了很多不必要的属性。

function Parent (name) {
    this.name = name;
    this.colors = ['red', 'blue', 'green'];
}

Parent.prototype.getName = function () {
    console.log(this.name)
}

function Child (name, age) {

    Parent.call(this, name);
    
    this.age = age;

}

Child.prototype = new Parent();
Child.prototype.constructor = Child;

var child1 = new Child('kevin', '18');

child1.colors.push('black');

console.log(child1.name); // kevin
console.log(child1.age); // 18
console.log(child1.colors); // ["red", "blue", "green", "black"]

var child2 = new Child('daisy', '20');

console.log(child2.name); // daisy
console.log(child2.age); // 20
console.log(child2.colors); // ["red", "blue", "green"]

(4)第四种方式是原型式继承,原型式继承的主要思路就是基于已有的对象来创建新的对象,实现的原理是,向函数中传入一个对象,然后返回一个以这个对象为原型的对象。这种继承的思路主要不是为了实现创造一种新的类型,只是对某个对象实现一种简单继承,ES5 中定义的 Object.create() 方法就是原型式继承的实现。缺点与原型链方式相同。

function createObj(o) {
    function F(){}
    F.prototype = o;
    return new F();
}

(5)第五种方式是寄生式继承,寄生式继承的思路是创建一个用于封装继承过程的函数,通过传入一个对象,然后复制一个对象的副本,然后对象进行扩展,最后返回这个对象。这个扩展的过程就可以理解是一种继承。这种继承的优点就是对一个简单对象实现继承,如果这个对象不是我们的自定义类型时。缺点是没有办法实现函数的复用。

function createObj (o) {
    var clone = Object.create(o);
    clone.sayName = function () {
        console.log('hi');
    }
    return clone;
}

(6)第六种方式是寄生式组合继承,组合继承的缺点就是使用超类型的实例做为子类型的原型,导致添加了不必要的原型属性。寄生式组合继承的方式是使用超类型的原型的副本来作为子类型的原型,这样就避免了创建不必要的属性。

function Parent (name) {
    this.name = name;
    this.colors = ['red', 'blue', 'green'];
}

Parent.prototype.getName = function () {
    console.log(this.name)
}

function Child (name, age) {
    Parent.call(this, name);
    this.age = age;
}

Child.prototype = new Parent();

var child1 = new Child('kevin', '18');

console.log(child1)