前端知识点

413 阅读7分钟

`in` 运算符和 `Object.hasOwnProperty` 方法有什么区别?

hasOwnPropert方法

hasOwnPropert()方法返回值是一个布尔值,指示对象自身属性中是否具有指定的属性,因此这个方法会忽略掉那些从原型链上继承到的属性。

看下面的例子:

Object.prototype.phone= '15345025546';

let obj = {
    name: '前端小智',
    age: '28'
}
console.log(obj.hasOwnProperty('phone')) // false
console.log(obj.hasOwnProperty('name')) // true

可以看到,如果在函数原型上定义一个变量phonehasOwnProperty方法会直接忽略掉。

in 运算符

如果指定的属性在指定的对象或其原型链中,则in 运算符返回true

还是用上面的例子来演示:

console.log('phone' in obj) // true

可以看到in运算符会检查它或者其原型链是否包含具有指定名称的属性。

有哪些方法可以处理 JS 中的异步代码?

  • 回调
  • Promise
  • async/await
  • 还有一些库:async.js, bluebird, q, co

函数表达式和函数声明之间有什么区别?

看下面的例子:

hoistedFunc();
notHoistedFunc();

function hoistedFunc(){
  console.log("注意:我会被提升");
}

var notHoistedFunc = function(){
  console.log("注意:我没有被提升");
}

notHoistedFunc调用抛出异常:Uncaught TypeError: notHoistedFunc is not a function,而hoistedFunc调用不会,因为hoistedFunc会被提升到作用域的顶部,而notHoistedFunc 不会。

调用函数,可以使用哪些方法?

在 JS 中有4种方法可以调用函数。

作为函数调用——如果一个函数没有作为方法、构造函数、applycall 调用时,此时 this 指向的是 window 对象(非严格模式)

  //Global Scope

  function add(a,b){
    console.log(this);
    return a + b;
  }  

  add(1,5); // 打印 "window" 对象和 6

  const o = {
    method(callback){
      callback();
    }
  }

  o.method(function (){
      console.log(this); // 打印 "window" 对象
  });

作为方法调用——如果一个对象的属性有一个函数的值,我们就称它为方法。调用该方法时,该方法的this值指向该对象。

const details = {
  name : "Marko",
  getName(){
    return this.name;
  }
}

details.getName(); // Marko

作为构造函数的调用-如果在函数之前使用new关键字调用了函数,则该函数称为构造函数。构造函数里面会默认创建一个空对象,并将this指向该对象。

function Employee(name, position, yearHired) {
  // 创建一个空对象 {}
  // 然后将空对象分配给“this”关键字
  // this = {};
  this.name = name;
  this.position = position;
  this.yearHired = yearHired;
  // 如果没有指定 return ,这里会默认返回 this
};

const emp = new Employee("Marko Polo", "Software Developer", 2017);

使用applycall方法调用——如果我们想显式地指定一个函数的this值,我们可以使用这些方法,这些方法对所有函数都可用。

const obj1 = {
 result:0
};

const obj2 = {
 result:0
};


function reduceAdd(){
   let result = 0;
   for(let i = 0, len = arguments.length; i < len; i++){
     result += arguments[i];
   }
   this.result = result;
}


reduceAdd.apply(obj1, [1, 2, 3, 4, 5]);  // reduceAdd 函数中的 this 对象将是 obj1
reduceAdd.call(obj2, 1, 2, 3, 4, 5); // reduceAdd 函数中的 this 对象将是 obj2

什么是缓存及它有什么作用?

缓存是建立一个函数的过程,这个函数能够记住之前计算的结果或值。使用缓存函数是为了避免在最后一次使用相同参数的计算中已经执行的函数的计算。这节省了时间,但也有不利的一面,即我们将消耗更多的内存来保存以前的结果。

手动实现缓存方法]

function memoize(fn) {
  const cache = {};
  return function (param) {
    if (cache[param]) {
      console.log('cached');
      return cache[param];
    } else {
      let result = fn(param);
      cache[param] = result;
      console.log(`not cached`);
      return result;
    }
  }
}

const toUpper = (str ="")=> str.toUpperCase();

const toUpperMemoized = memoize(toUpper);

toUpperMemoized("abcdef");
toUpperMemoized("abcdef");

这个缓存函数适用于接受一个参数。我们需要改变下,让它接受多个参数。

const slice = Array.prototype.slice;
function memoize(fn) {
  const cache = {};
  return (...args) => {
    const params = slice.call(args);
    console.log(params);
    if (cache[params]) {
      console.log('cached');
      return cache[params];
    } else {
      let result = fn(...args);
      cache[params] = result;
      console.log(`not cached`);
      return result;
    }
  }
}
const makeFullName = (fName, lName) => `${fName} ${lName}`;
const reduceAdd = (numbers, startingValue = 0) => numbers.reduce((total, cur) => total + cur, startingValue);

const memoizedMakeFullName = memoize(makeFullName);
const memoizedReduceAdd = memoize(reduceAdd);

memoizedMakeFullName("Marko", "Polo");
memoizedMakeFullName("Marko", "Polo");

memoizedReduceAdd([1, 2, 3, 4, 5], 5);
memoizedReduceAdd([1, 2, 3, 4, 5], 5);

为什么typeof null 返回 object?如何检查一个值是否为 null?

typeof null == 'object'总是返回true,因为这是自 JS 诞生以来null的实现。曾经有人提出将typeof null == 'object'修改为typeof null == 'null',但是被拒绝了,因为这将导致更多的bug

我们可以使用严格相等运算符===来检查值是否为null

function isNull(value){
  return value === null;
}

new 关键字有什么作用?

new关键字与构造函数一起使用以创建对象:

function Employee(name, position, yearHired) {
  this.name = name;
  this.position = position;
  this.yearHired = yearHired;
};

const emp = new Employee("Marko Polo", "Software Developer", 2017);

new关键字做了4件事:

  • 创建空对象 {}
  • 将空对象分配给 this
  • 将空对象的**proto**指向构造函数的prototype
  • 如果没有使用显式return语句,则返回this

看下面事例:

function Person() { this.name = '前端小智' }

根据上面描述的,new Person()做了:

  • 创建一个空对象:var obj = {}

  • 将空对象分配给 this 值:this = obj

  • 将空对象的**proto__`指向构造函数的`prototype`:`this.__proto** = Person().prototype

  • 返回this:return this

模拟一个new?

function objectFactory() {
    var obj = new Object(),
    Constructor = [].shift.call(arguments);
    obj.__proto__ = Constructor.prototype;
    var ret = Constructor.apply(obj, arguments);
    //需要判断返回的值是不是一个对象,如果是一个对象,我们就返回这个对象,如果没有,我们该返回什么就返回什么
    return typeof ret === 'object' ? ret : obj;
};

什么时候不使用箭头函数? 说出三个或更多的例子?

不应该使用箭头函数一些情况:

  • 当想要函数被提升时(箭头函数是匿名的)
  • 要在函数中使用this/arguments时,由于箭头函数本身不具有this/arguments,因此它们取决于外部上下文
  • 使用命名函数(箭头函数是匿名的)
  • 使用函数作为构造函数时(箭头函数没有构造函数)
  • 当想在对象字面是以将函数作为属性添加并在其中使用对象时,因为咱们无法访问 this 即对象本身。

Object.freeze() 和 const 的区别是什么?]

constObject.freeze是两个完全不同的概念。

const 声明一个只读的变量,一旦声明,常量的值就不可改变:

const person = {
    name: "Leonardo"
};
let animal = {
    species: "snake"
};
person = animal; // ERROR "person" is read-only    

Object.freeze适用于值,更具体地说,适用于对象值,它使对象不可变,即不能更改其属性。

let person = {
    name: "Leonardo"
};
let animal = {
    species: "snake"
};
Object.freeze(person);
person.name = "Lima"; //TypeError: Cannot assign to read only property 'name' of object
console.log(person); 

如何在 JS 中“深冻结”对象?

如果咱们想要确保对象被深冻结,就必须创建一个递归函数来冻结对象类型的每个属性:

没有深冻结

let person = {
    name: "Leonardo",
    profession: {
        name: "developer"
    }
};
Object.freeze(person); 
person.profession.name = "doctor";
console.log(person); //output { name: 'Leonardo', profession: { name: 'doctor' } }

深冻结

function deepFreeze(object) {
    let propNames = Object.getOwnPropertyNames(object);
    for (let name of propNames) {
        let value = object[name];
        object[name] = value && typeof value === "object" ?
            deepFreeze(value) : value;
    }
    return Object.freeze(object);
}
let person = {
    name: "Leonardo",
    profession: {
        name: "developer"
    }
};
deepFreeze(person);
person.profession.name = "doctor"; // TypeError: Cannot assign to read only property 'name' of object

`Iterator`是什么,有什么作用?

遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署Iterator接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。

Iterator 的作用有三个:

  1. 为各种数据结构,提供一个统一的、简便的访问接口;
  2. 使得数据结构的成员能够按某种次序排列;
  3. ES6 创造了一种新的遍历命令for…of循环,Iterator 接口主要供for…of消费。

遍历过程:

  1. 创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
  2. 第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。
  3. 第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。
  4. 不断调用指针对象的next方法,直到它指向数据结构的结束位置。

每一次调用next方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含valuedone两个属性的对象。其中,value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束。

//obj就是可遍历的,因为它遵循了Iterator标准,且包含[Symbol.iterator]方法,方法函数也符合标准的Iterator接口规范。
//obj.[Symbol.iterator]() 就是Iterator遍历器
let obj = {
  data: [ 'hello', 'world' ],
  [Symbol.iterator]() {
    const self = this;
    let index = 0;
    return {
      next() {
        if (index < self.data.length) {
          return {
            value: self.data[index++],
            done: false
          };
        } else {
          return { value: undefined, done: true };
        }
      }
    };
  }
};

`Generator` 函数是什么,有什么作用?

如果说 JavaScrip 是 ECMAScript 标准的一种具体实现、Iterator遍历器是Iterator的具体实现,那么Generator函数可以说是Iterator接口的具体实现方式。

执行Generator函数会返回一个遍历器对象,每一次Generator函数里面的yield都相当一次遍历器对象的next()方法,并且可以通过next(value)方法传入自定义的value,来改变Generator函数的行为。

Generator函数可以通过配合Thunk 函数更轻松更优雅的实现异步编程和控制流管理。