梦想吧

153 分类: WEB前端

JS 原生面经从初级到高级

这篇文章写得挺好的,不管是面试还是学习都挺受用,摘抄放博客,防止以后找不到了。
边看变更,文章来自掘金专栏https://juejin.im/post/5daeefc8e51d4524f007fb15

1. 函数

1.1函数的3种定义方法

1.1.1 函数声明

//ES5
function getSum(){}
function (){}//匿名函数
//ES6
()=>{}//如果{}内容只有一行{}和return关键字可省,

1.1.2 函数表达式(函数字面量)

//ES5
var sum=function(){}
//ES6
let sum=()=>{}//如果{}内容只有一行{}和return关键字可省,

1.1.3 构造函数

const sum = new Function('a', 'b' , 'return a + b')

1.1.4 三种方法的对比

  1. 函数声明有预解析,而且函数声明的优先级高于变量;
  2. 使用Function构造函数定义函数的方式是一个函数表达式,这种方式会导致解析两次代码,影响性能。第一次解析常规的JavaScript代码,第二次解析传入构造函数的字符串

1.2.ES5中函数的4种调用

在ES5中函数内容的this指向和调用方法有关

1.2.1 函数调用模式

包括函数名()和匿名函数调用,this指向window

 function getSum() {
    console.log(this) //这个属于函数名调用,this指向window
 }
 getSum()
 
 (function() {
    console.log(this) //匿名函数调用,this指向window
 })()
 
 var getSum=function() {
    console.log(this) //实际上也是函数名调用,window
 }
 getSum()

1.2.2 方法调用

对象.方法名(),this指向对象

var objList = {
   name: 'methods',
   getSum: function() {
     console.log(this) //objList对象
   }
}
objList.getSum()

1.2.3 构造器调用

new 构造函数名(),this指向实例化的对象

function Person() {
  console.log(this); //是构造函数调用,指向实例化的对象personOne
}
var personOne = new Person();

1.2.4 间接调用

利用call和apply来实现,this就是call和apply对应的第一个参数,如果不传值或者第一个值为null,undefined时this指向window

function foo() {
   console.log(this);
}
foo.apply('我是apply改变的this值');//我是apply改变的this值
foo.call('我是call改变的this值');//我是call改变的this值

1.3 ES6中函数的调用

箭头函数不可以当作构造函数使用,也就是不能用new命令实例化一个对象,否则会抛出一个错误 箭头函数的this是和定义时有关和调用无关 调用就是函数调用模式

(() => {
   console.log(this)//window
})()

let arrowFun = () => {
  console.log(this)//window
}
arrowFun()

let arrowObj = {
  arrFun: function() {
   (() => {
     console.log(this)//this指向的是arrowObj对象
   })()
   }
 }
 arrowObj.arrFun();

1.4.call,apply和bind

  1. IE5之前不支持call和apply,bind是ES5出来的;
  2. call和apply可以调用函数,改变this,实现继承和借用别的对象的方法;

1.4.1 call和apply定义

调用方法,用一个对象替换掉另一个对象(this) 对象.call(新this对象,实参1,实参2,实参3.....) 对象.apply(新this对象,[实参1,实参2,实参3.....])

1.4.2 call和apply用法

  1. 间接调用函数,改变作用域的this值
  2. 劫持其他对象的方法
var foo = {
  name:"张三",
  logName:function(){
    console.log(this.name);
  }
}
var bar={
  name:"李四"
};
foo.logName.call(bar);//李四
实质是call改变了foo的this指向为bar,并调用该函数
  1. 两个函数实现继承
function Animal(name){   
  this.name = name;   
  this.showName = function(){   
    console.log(this.name);   
  }   
}   
function Cat(name){  
  Animal.call(this, name);  
}    
var cat = new Cat("Black Cat");   
cat.showName(); //Black Cat
  1. 为类数组(arguments和nodeList)添加数组方法push,pop
(function(){
  Array.prototype.push.call(arguments,'王五');
  console.log(arguments);//['张三','李四','王五']
})('张三','李四')
  1. 合并数组
let arr1=[1,2,3]; 
let arr2=[4,5,6]; 
Array.prototype.push.apply(arr1,arr2); //将arr2合并到了arr1中
  1. 求数组最大值
Math.max.apply(null,arr)
  1. 判断字符类型
Object.prototype.toString.call({})

1.4.3 bind

bind是function的一个函数扩展方法,bind以后代码重新绑定了func内部的this指向,不会调用方法,不兼容IE8

var name = '李四'
 var foo = {
   name: "张三",
   logName: function(age) {
   console.log(this.name, age);
   }
 }
 var fooNew = foo.logName;
 var fooNewBind = foo.logName.bind(foo);
 fooNew(10)//李四,10
 fooNewBind(11)//张三,11  因为bind改变了fooNewBind里面的this指向

1.4.4 call,apply和bind原生实现

call实现:

Function.prototype.newCall = function(context, ...parameter) {
 if (typeof context === 'object' || typeof context === 'function') {
    context = context || window
} else {
    context = Object.create(null)
}
  context[fn] = this  
  const res =context[fn](...parameter)
  delete context.fn;
  return res
}
let person = {
  name: 'Abiel'
}
function sayHi(age,sex) {
  console.log(this.name, age, sex);
}
sayHi.newCall (person, 25, '男'); // Abiel 25 男

apply实现:

Function.prototype.newApply = function(context, parameter) {
  if (typeof context === 'object' || typeof context === 'function') {
    context = context || window
  } else {
    context = Object.create(null)
  }
  let fn = Symbol()
  context[fn] = this
  return res=context[fn](..parameter);
  delete context[fn]
  return res
}
sayHi.newApply (person,[ 25, '男']) //Abiel 25 男

bind实现:

Function.prototype.bind = function (context,...innerArgs) {
  var me = this
  return function (...finnalyArgs) {
    return me.call(context,...innerArgs,...finnalyArgs)
  }
}
let person = {
  name: 'Abiel'
}
function sayHi(age,sex) {
  console.log(this.name, age, sex);
}
let personSayHi = sayHi.bind(person, 25)
personSayHi('男')

1.4.5 三者异同

  • 同:都是改变this指向,都可接收参数
  • 异:bind和call是接收单个参数,apply是接收数组

1.5 函数的节流和防抖

4.1 栈

栈的特点:先进后出

class Stack {
    constructor() {
      this.items = [];
    }

    // 入栈
    push(element) {
      this.items.push(element);
    }

    // 出栈
    pop() {
      return this.items.pop();
    }

    // 末位
    get peek() {
      return this.items[this.items.length - 1];
    }

    // 是否为空栈
    get isEmpty() {
      return !this.items.length;
    }

    // 长度
    get size() {
      return this.items.length;
    }

    // 清空栈
    clear() {
      this.items = [];
    }
  }

  // 实例化一个栈
  const stack = new Stack();
  console.log(stack.isEmpty); // true

  // 添加元素
  stack.push(5);
  stack.push(8);

  // 读取属性再添加
  console.log(stack.peek); // 8
  stack.push(11);
  console.log(stack.size); // 3
  console.log(stack.isEmpty); // false

4.2 队列

队列:先进先出

class Queue { 
    constructor(items) { 
      this.items = items || []; 
    }
    enqueue(element) {
      this.items.push(element);
    }

    dequeue() {
      return this.items.shift();
    }

    front() {
      return this.items[0];
    }

    clear() {
      this.items = [];
    }

    get size() {
      return this.items.length;
    }

    get isEmpty() {
      return !this.items.length;
    }

    print() {
      console.log(this.items.toString());
    }
  }

  const queue = new Queue();
  console.log(queue.isEmpty); // true

  queue.enqueue("John");
  queue.enqueue("Jack");
  queue.enqueue("Camila");
  console.log(queue.size); // 3
  console.log(queue.isEmpty); // false
  queue.dequeue();
  queue.dequeue();

4.3 链表

链表:
存贮有序元素的集合;
但是不同于数组,每个元素是一个存贮元素本身的节点和指向下一个元素引用组成
要想访问链表中间的元素,需要从起点开始遍历找到所需元素

class Node {
    constructor(element) {
        this.element = element;
        this.next = null;
    }
}

// 链表
class LinkedList {
    constructor() {
        this.head = null;
        this.length = 0;
    }

    // 追加元素
    append(element) {
        const node = new Node(element);
        let current = null;
        if (this.head === null) {
            this.head = node;
        } else {
            current = this.head;
            while (current.next) {
                current = current.next;
            }
            current.next = node;
        }
        this.length++;
    }

    // 任意位置插入元素
    insert(position, element) {
        if (position >= 0 && position <= this.length) {
            const node = new Node(element);
            let current = this.head;
            let previous = null;
            let index = 0;
            if (position === 0) {
                this.head = node;
                node.next = current;
            } else {
                while (index++ < position) {
                    previous = current;
                    current = current.next;
                }
                node.next = current;
                previous.next = node;
            }
            this.length++;
            return true;
        }
        return false;
    }

    // 移除指定位置元素
    removeAt(position) {
        // 检查越界值
        if (position > -1 && position < length) {
            let current = this.head;
            let previous = null;
            let index = 0;
            if (position === 0) {
                this.head = current.next;
            } else {
                while (index++ < position) {
                    previous = current;
                    current = current.next;
                }
                previous.next = current.next;
            }
            this.length--;
            return current.element;
        }
        return null;
    }

    // 寻找元素下标
    findIndex(element) {
        let current = this.head;
        let index = -1;
        while (current) {
            if (element === current.element) {
                return index + 1;
            }
            index++;
            current = current.next;
        }
        return -1;
    }

    // 删除指定文档
    remove(element) {
        const index = this.findIndex(element);
        return this.removeAt(index);
    }

    isEmpty() {
        return !this.length;
    }

    size() {
        return this.length;
    }

    // 转为字符串
    toString() {
        let current = this.head;
        let string = "";
        while (current) {
            string += ` ${current.element}`;
            current = current.next;
        }
        return string;
    }
}

const linkedList = new LinkedList();

console.log(linkedList);
linkedList.append(2);
linkedList.append(6);
linkedList.append(24);
linkedList.append(152);

linkedList.insert(3, 18);
console.log(linkedList);
console.log(linkedList.findIndex(24));

4.4 字典

字典:类似对象,以key,value存贮值

/**
 * 二叉树节点
 */
class NodeTree {
    constructor(key) {
        this.key = key;
        this.left = null;
        this.right = null;
    }
}

/**
 * 二叉树
 */
class BinarySearchTree {
    constructor() {
        this.root = null;
    }

    insert(key) {
        const newNode = new NodeTree(key);
        const insertNode = (node, newNode) => {
            if (newNode.key < node.key) {
                if (node.left === null) {
                    node.left = newNode;
                } else {
                    insertNode(node.left, newNode);
                }
            } else {
                if (node.right === null) {
                    node.right = newNode;
                } else {
                    insertNode(node.right, newNode);
                }
            }
        };
        if (!this.root) {
            this.root = newNode;
        } else {
            insertNode(this.root, newNode);
        }
    }

    //访问树节点的三种方式:中序,先序,后序
    inOrderTraverse(callback) {
        const inOrderTraverseNode = (node, callback) => {
            if (node !== null) {
                inOrderTraverseNode(node.left, callback);
                callback(node.key);
                inOrderTraverseNode(node.right, callback);
            }
        };
        inOrderTraverseNode(this.root, callback);
    }

    min(node) {
        const minNode = node => {
            return node ? (node.left ? minNode(node.left) : node) : null;
        };
        return minNode(node || this.root);
    }

    max(node) {
        const maxNode = node => {
            return node ? (node.right ? maxNode(node.right) : node) : null;
        };
        return maxNode(node || this.root);
    }
}

const tree = new BinarySearchTree();
tree.insert(11);
tree.insert(7);
tree.insert(5);
tree.insert(3);
tree.insert(9);
tree.insert(8);
tree.insert(10);
tree.insert(13);
tree.insert(12);
tree.insert(14);
tree.inOrderTraverse(value => {
    console.log(value);
});

console.log(tree.min());
console.log(tree.max());

5.算法篇

5.1 排序

冒泡排序,选择排序,插入排序,此处不做赘述.
八种排序算法的实现(JavaScript)

5.2 斐波那契

特点:第三项等于前面两项之和

function fibonacci(num) {
    if (num === 1 || num === 2) {
        return 1
    }
    return fibonacci(num - 1) + fibonacci(num - 2)
}

5.3 动态规划

特点:通过全局规划,将大问题分割成小问题来取最优解
案例:最少硬币找零
美国有以下面额(硬币):d1=1, d2=5, d3=10, d4=25
如果要找36美分的零钱,我们可以用1个25美分、1个10美分和1个便士( 1美分)

class MinCoinChange {

    constructor(coins) {
        this.coins = coins
        this.cache = {}
    }

    makeChange(amount) {
        if (!amount) return []
        if (this.cache[amount]) return this.cache[amount]
        let min = [], newMin, newAmount
        this.coins.forEach(coin => {
            newAmount = amount - coin
            if (newAmount >= 0) {
                newMin = this.makeChange(newAmount)
            }
            if (newAmount >= 0 &&
                (newMin.length < min.length - 1 || !min.length) &&
                (newMin.length || !newAmount)) {
                min = [coin].concat(newMin)
            }
        })
        return (this.cache[amount] = min)
    }
}

const rninCoinChange = new MinCoinChange([1, 5, 10, 25])
console.log(rninCoinChange.makeChange(36))
// [1, 10, 25]
const minCoinChange2 = new MinCoinChange([1, 3, 4])
console.log(minCoinChange2.makeChange(6))
// [3, 3]

#前端, JS, 面试, JavaScript

作者: Jim

版权: 除特别声明,均采用BY-NC-SA 4.0许可协议,转载请表明出处

目录Content

评论