前端JS面试必须知道的几个点

I.函数的三种定义方法
1.1 函数声明
// ES5
function getSum()
function (){} //匿名函数

// ES6
() => {} //如果 {} 内容只有一行 {} 和return 关键字可省略
1.2 函数表达式(函数字面量)
// ES5
var sum = function getSum()

// ES6
let sum = () => {} //如果{} 内容只有一行 {} 和return 关键字可省略
1.3 构造函数
var sum = new GetSum(num1, num2);
1.4 三种方法的比较
  1. 函数声明有预解析,而且函数声明的优先级高于变量;
  2. 使用 Function 构造函数定义函数的人方式是一个函数表达式,这种方式会导致解析两次代码,影响性能。第一次解析常规的 Javascript 代码,第二次解析传入构造函数的字符串。
II. ES5 中函数的 4 种调用
2.1 函数调用模式

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

function getSum() {
  console.log(this); //window
}

getSum()(function () {
  console.log(this); //window
})();

var getSum = function () {
  console.log(this); //window
};

getSum();
2.2 方法调用

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

var objList = {
  name: "methods",
  getSum: function () {
    console.log(this); //objList对象
  },
};
objList.getSum();
2.3 构造器调用

new 构造函数名 (), this 指向构造函数

function Person() {
  console.log(this); //指向构造函数Person
}
var personOne = new Person();
2.4 间接调用

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

function foo() {
  console.log(this);
}

foo.apply("我是apply改变的this值");
foo.call("我是call改变的this值");
III. ES6 中函数的调用

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

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

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

arrowFun();

let arrowObj = {
  arrFun: function () {
    (() => {
      console.log(this); //arrowObj
    })();
  },
};
arrowObj.arrFun;
IV. call, applybind
  • IE5 之前不支持 callapplybindES5 之后出来的;
  • callapply 可以调用函数,改变 this,实现继承和借用别的对象的方法。
4.1 callapply 定义

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

4.2 callapply 用法
  • a. 间接调用函数,改变作用域的 this 值;
  • b. 劫持其他对象的方法
var foo = {
  name: "张三",
  logName: function () {
    console.log(this.name);
  },
};

var bar = {
  name: "李四",
};
foo.logName.call(bar); //李四
// 实质是call改变了foo的this指向为bar,并调用函数
  • c. 两个函数实现继承
<div style="font-size:20px" >
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
</div>
  • d. 为类数组(argumentsnodeList)添加数组方法 pushpop
(function () {
  Array.prototype.push.call(arguments, "王五");
  console.log(arguments); //['张三','李四','王五']
})("张三", "李四");
  • e. 合并数组
let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];
Array.prototype.push.apply(arr1, arr2); //将arr2合并到了arr1中
  • f. 求数组最大值
Math.max.apply(null, arr);
  • g. 判断字符类型
Object.prototype.toString.call({});
// [object Object]
V. JS 常见的四种设计模式
5.1 工厂模式

简单的工厂模式可以理解为解决多个相似的问题;

function CreatePerson(name, age, sex) {
  var obj = new Object();
  obj.name = name;
  obj.age = age;
  obj.sex = sex;
  obj.sayName = function () {
    return this.name;
  };
  return obj;
}

var p1 = new CreatePerson("Tom", "28", "男");
var p2 = new CreatePerson("Lisa", "21", "女");

console.log(p1.name); // longen
console.log(p1.age); // 28
console.log(p1.sex); // 男
console.log(p1.sayName()); // longen

console.log(p2.name); // tugenhua
console.log(p2.age); // 27
console.log(p2.sex); // 女
console.log(p2.sayName()); // tugenhua
5.2 单例模式

只能被实例化(构造函数给实例添加属性与方法)一次

//单体模式
var Singleton = function (name) {
  this.name = name;
};

Singleton.prototype.getName = function () {
  return this.name;
};

//获取实例对象
var getInstance = (function () {
  var instance = null;
  return function (name) {
    if (!instance) {
      //相当于一个一次性阀门,只能实例化一次
      instance = new Singleton(name);
    }
    return instance;
  };
})();

//测试单体模式的实例,所以 a===b
var a = getInstance("aa");
var a = getInstance("bb");
5.3 沙箱模式

将一些函数放到自执行函数里面,但要闭包暴露接口,用变量接受暴露的接口,再调用里面的值,否则无法使用里面的值。

let sanboxModel = (function () {
  function sayName() {}
  function sayAge() {}
  return {
    sayName: sayName,
    sayAge: sayAge,
  };
})();
5.4 发布者订阅模式

就例如我们关注了某一个公众号,然后他对应的有新的消息就会给你推送。

//发布者与订阅模式

var shoeObj = {}; // 定义发布者
shoeObj.list = []; //缓存列表 存放订阅者回调函数

// 增加订阅者

shoeObj.listen = function (fn) {
  shoeObj.list.push(fn); // 订阅消息添加到缓存列表
};

// 发布消息
shoeObj.trigger = function () {
  for (var i = 0, fn; (fn = this.list[i++]); ) {
    fn.apply(this, arguments); //第二个参数只是改变fn的this
  }
};

// 小红订阅如下消息
shoeObj.listen(function (color, size) {
  console.log("颜色是: " + color);
  console.log("尺码是:" + size);
});

// 小花订阅如下消息
shoeObj.listen(function (color, size) {
  console.log("再次打印颜色是: " + color);
  console.log("再次打印尺码是:" + size);
});

shoeObj.trigger("红色", 40);
shoeObj.trigger("黑色", 42);

代码实现逻辑是用数组存储订阅者,发布者毁掉函数里面通知的方式是遍历订阅者数组,并将发布者内容传入订阅者数组

VI. 原型链
6.1 定义

对象继承属性的一个链条

6.2 构造函数,实例与原型对象的关系

image

var person = function (name) {
  this.name = name;
}; //person是构造函数

var o3personTwo = new person("personTwo"); //personTwo是实例

iamge

原型对象都有一个默认的constructor属性指向构造函数

6.3 创建实例的方法
  • a. 字面量
let obj = { name: "张三" };
  • b. Object 构造函数创建
let Obj = new Object();
Obj.name = "张三";
  • c. 使用工厂模式创建对象
function createPerson(name) {
  var o = new Object();
  o.name = name;
  return o;
}

var person1 = createPerson("张三");
  • d. 使用构造函数创建对象
function Person(name){
    this.name = name;
}
var person1 = new Person('张三')
6.4 new 运算符
  • a. 创建了一个新对象;
  • b. this指向构造函数;
  • c. 构造函数有返回,会替换 new 出来的对象,如果没有就是 new 出来的对象
  • d. 手动封装一个 new 运算符
var new2 = function (func) {
  var o = Object.create(func.protorype); //创建对象
  var k = func.call(o); //改变this指向,把结果赋给k
  if (typeof k === "object") {
    //判断k的类型是不是对象
    return k;
  } else {
    return o;
  }
};
6.5 对象的原型链

image

VII. 继承的方式

JS 是一门弱类型动态语言,封装和继承是他的两大特性:

7.1 原型链继承