前端js面试题 (三)

文章目录

    • JavaScript有哪些垃圾回收机制?
    • null和 undefined的区别是什么?
    • new操作符的作用是什么?
    • this 的指向哪几种 ?
    • JS 中继承实现的几种方式
      • 原型继承
      • 构造函数继承(对象冒充继承)
      • 组合继承(终极版),即构造函数的继承和原型的继承相结合
      • 寄生组合继承(究极版)
    • 作用域与作用域链
    • 数组去除空项
    • typeof null 返回结果
    • NaN === NaN 的结果
    • 类型转换

JavaScript有哪些垃圾回收机制?

引用计数垃圾回收(Reference Counting):
这是最简单的垃圾回收机制之一,它跟踪每个对象的引用次数。当引用计数为零时,对象被视为不再被引用,从内存中移除。然而,这个方法不适用于循环引用的情况,因为即使对象之间互相引用,它们的引用计数也不会变为零。

标记-清除垃圾回收(Mark and Sweep):
这是一种更复杂的垃圾回收机制,它通过根对象(如全局对象或函数调用栈中的局部变量)开始,递归地标记所有从根对象可达的对象,并将未标记的对象视为垃圾,最后清除这些垃圾对象。标记-清除可以有效处理循环引用。

标记-整理垃圾回收(Mark and Compact):
这种垃圾回收机制是标记-清除的改进版本。它首先标记所有活动对象,然后将它们移动到内存的一端,从而在内存中创建一块连续的空间。这有助于减少内存碎片。

null和 undefined的区别是什么?

nullundefined 是 JavaScript 中的两个特殊值,用于表示缺少值或未定义的情况,但它们有一些区别:

  1. undefined:

    • undefined 是一个原始数据类型,表示变量已声明但尚未被赋值,或者对象属性不存在时的默认值。例如:

      let variable;
      console.log(variable); // 输出 undefined
      
    • 函数没有显式返回值时,它们默认返回 undefined

  2. null:

    • null 也是一个特殊的原始数据类型,表示变量或对象属性的值被明确设置为没有值,或者用于清空引用以释放内存。例如:

      let emptyValue = null;
      
    • 当需要明确指定一个变量为空时,通常使用 null

    • 作为对象原型链的终点。

  3. 类型:

    • undefined 是一个特殊的原始数据类型。
    • null 也是一个特殊的原始数据类型,但它被认为是一个空对象引用。
  4. 产生方式:

    • undefined 通常表示变量被声明但未初始化,或者访问不存在的对象属性。
    • null 通常是开发人员主动设置的,用于表示变量或对象属性的值为空。
  5. 用途:

    • undefined 通常表示初始状态,表示缺少值。
    • null 通常表示意图赋予变量或属性的值为空,或者用于显式释放对象引用。
  6. 相等性比较:

    • 在松散相等比较(==)中,nullundefined 是相等的,但在严格相等比较(===)中,它们不相等。

      console.log(null == undefined); // 输出 true
      console.log(null === undefined); // 输出 false
      

new操作符的作用是什么?

(1)创建一个空对象。

(2)该对象继承该函数的原型(更改原型链的指向)

(3)把属性和方法加入到this引用的对象中。

(4)新创建的对象由被构造函数的this引用,最后隐式地返回该对象,过程如下:

function myNew(constructor, ...args) {
    // 创建一个空对象。
    const obj = {}
    // 该对象继承该函数的原型(更改原型链的指向)
    obj.__proto__ = constructor.prototype

    // 把构造函数的属性和方法加入到对象中,新创建的对象由被构造函数的this引用
    const result = constructor.apply(obj, args);

    // 要判断一下 constructor 是否会返回一个对象,如果是对象就返回result,如果不是一个对象就返回obj
    return result instanceof Object ? result : obj;
}

function Person(name, age) {
    this.name = name
    this.age = age

    this.sayName = function() {
        console.log(this.name)
    }
}

const person1 = myNew(Person, 'dx', 18)

console.log(person1)
person1.sayName()

this 的指向哪几种 ?

总结起来,this 的指向规律有如下几条:

在函数体中,非显式或隐式地简单调用函数时,在严格模式下,函数内的 this 会被绑定到 undefined 上,在非严格模式下则会被绑定到全局对象 window/global 上。
一般使用 new 方法调用构造函数时,构造函数内的 this 会被绑定到新创建的对象上。
一般通过 call/apply/bind 方法显式调用函数时,函数体内的 this 会被绑定到指定参数的对象上。
一般通过上下文对象调用函数时,函数体内的 this 会被绑定到该对象上。
在箭头函数中,this 的指向是由外层(函数或全局)作用域来决定的。

JS 中继承实现的几种方式

原型继承

重点:让新实例的原型等于父类的实例。
特点:实例可继承的属性有:实例的构造函数的属性,父类构造函数属性,父类原型的属性。(新实例不会继承父类实例的属性!)
缺点:
1、新实例无法向父类构造函数传参。
2、继承单一。
3、所有新实例都会共享父类实例的属性。(原型上的属性是共享的,一个实例修改了原型属性,另一个实例的原型属性也会被修改!)

// 定义一个动物类
function Animal (name,age) {
  // 属性
  this.name = name || 'Animal';
  this.age = age
  // 实例方法
  this.sleep = function(){
    console.log(this.name + '正在睡觉!');
  }
  this.now = function(){
	console.log(this.age)
	}
}
// 原型方法
Animal.prototype.eat = function(food) {
  console.log(this.name + '正在吃:' + food);
};

//下面的cat构造函数,将要继承上面的animal构造函数的方法和属性
function Cat(name){
//让子构造函数的this去指向animal构造函数的this,从而获取animal构造函数本身的属性
  this.name = name || 'Tom';
}

// 只继承了父类原型链的方法
Cat.prototype = Animal.prototype;

//另外一种原型链继承,继承父类构造函数的实例,既可以继承父类的原型,也可以继承父类本身
Cat.prototype = new Animal();
//缺点,不能传参。比如
var cat1 = new Cat('小喵',2)
console.log(cat1.now()) // undefined

// 因为在父类的构造函数中,this仅仅只是父类函数上下文。为了说明这个this,
//下面的例子
function Dog() {
}
Dog.prototype = new Animal('小汪'4);

var dog1 = new Dog()
console.log(dog1.now()) // 4

构造函数继承(对象冒充继承)

重点:用 call( ) 和 apply( ) 将父类构造函数引入子类函数(在子类函数中做了父类函数的自执行(复制))

特点:

  • 1、只继承了父类构造函数的属性,没有继承父类原型的属性。
  • 2、解决了原型链继承缺点1、2、3。
  • 3、可以继承多个构造函数属性(call多个)。
  • 4、在子实例中可向父实例传参。

缺点:

  • 1、只能继承父类构造函数的属性。
  • 2、无法实现构造函数的复用。(每次用每次都要重新调用)
  • 3、每个新实例都有父类构造函数的副本,臃肿。
function Animal (name,age) {
  // 属性
  this.name = name || 'Animal';
  this.age = age
  // 实例方法
  this.sleep = function(){
    console.log(this.name + '正在睡觉!');
  }
  this.now = function(){
	console.log(this.age)
	}
}
// 原型方法
Animal.prototype.eat = function(food) {
  console.log(this.name + '正在吃:' + food);
};

function Cat(name,age){
//让子构造函数的this去指向animal构造函数的this,从而获取animal构造函数本身的属性
  Animal.call(this);
}
let Cat1 = new Cat('小喵',2)
console.log(Cat1.eat('猫粮')) // 会报错,该方法不存在

组合继承(终极版),即构造函数的继承和原型的继承相结合

重点:结合了两种模式的优点,传参和复用

特点:

  • 1、可以继承父类原型上的属性,可以传参,可复用。
  • 2、每个新实例引入的构造函数属性是私有的。

缺点:调用了两次父类构造函数(耗内存),子类的构造函数会代替原型上的那个父类构造函数。
寄生组合式继承(圣杯模式)

// 定义一个动物类
function Animal (name) {
  // 属性
  this.name = name || 'Animal';
  // 实例方法
  this.sleep = function(){
    console.log(this.name + '正在睡觉!');
  }
}
// 原型方法
Animal.prototype.eat = function(food) {
  console.log(this.name + '正在吃:' + food);
};

//下面的cat构造函数,将要继承上面的animal构造函数的方法和属性
function Cat(name){
// 让子构造函数的this去指向animal构造函数的this,从而获取animal构造函数本身的属性
  Animal.call(this);
  this.name = name || 'Tom';
}

//让子构造函数的原型等于animal构造函数的实例,此处继承了animal构造函数原型的属性,也继承了animal构造函数本身的属性
// 缺点 两次继承了Animal本身的方法和属性
Cat.prototype = new Animal();

// 如果是这种原型继承呢
Cat.prototype = Animal.prototype

// 缺点 Cat虽然只继承了Animal原型的方法,没有重复继承Animal本身的函数和方法,但Cat和Animal原型是不独立的(只是原型的引用),其中一个原型发生改变,另一个也会发生改变

寄生组合继承(究极版)

function Cat(name){
//继承animal构造函数本身的属性
  Animal.call(this);
  this.name = name || 'Tom';
}
(function(){
  // 创建一个没有实例方法的类
  var Super = function(){};
  //通过一个中间值,只继承animal原型上的属性
  Super.prototype = Animal.prototype;
  //将实例作为子类的原型
  //super构造函数只有anmial原型上的属性,它的实例自然也只有animal原型上的东西.
  Cat.prototype = new Super();
})();

作用域与作用域链

作用域是在运行时代码中的某些特定部分中变量,函数和对象的可访问性。换句话说,作用域决定了代码区块中变量和其他资源的可见性。ES6 之前 JavaScript 没有块级作用域,只有全局作用域和函数作用域。ES6 的到来,为我们提供了块级作用域。
作用域链指的是作用域与作用域之间形成的链条。当我们查找一个当前作用域没有定义的变量(自由变量)的时候,就会向上一级作用域寻找,如果上一级也没有,就再一层一层向上寻找,直到找到全局作用域还是没找到,就宣布放弃。这种一层一层的关系,就是作用域链 。

数组去除空项

在数组中有空项的时候,使用 flat 方法会将中的空项进行移除。

var arr = [1, 2, , 4, 5];
console.log(arr.flat()); // [1, 2, 4, 5]

typeof null 返回结果

返回 'object'
至于为什么会返回 object,这实际上是来源于 JavaScript 从第一个版本开始时的一个 bug,并且这个 bug 无法被修复。修复会破坏现有的代码。
原理这是这样的,不同的对象在底层都表现为二进制,在 JavaScript 中二进制前三位都为 0 的话会被判断为 object 类型,null 的二进制全部为 0,自然前三位也是 0,所以执行 typeof 值会返回 object。

NaN === NaN 的结果

返回 false

const a = 123 - 'a' // NaN 
const b = 456 = 'b' // NaN

a === b // false
a == b // false

NaN表示 not a number,在js中NaN是一种数字的特殊表达,typeof NaN === 'number' // true ,它本身是一种特殊的数字,造成它的原因不尽相同。
比较两个值是否相等,那就必须得有值来进行判断,NaN是数字,却不是一个值,没法进行比较

类型转换

  • 1、转换函数

js 提供了诸如 parseInt 和 parseFloat 这些转换函数,通过这些转换函数可以进行数据类型的转换 。

parseInt('11') // 11
parseInt(true) // NaN
parseInt('1dx') // 1
parseFloat('1.12dx456') // 1.12
  • 2、强制类型转换
    还可使用强制类型转换(type casting)处理转换值的类型。
    例如:
    Boolean(value) 把给定的值转换成 Boolean 型;
Boolean(null) // false
Boolean(undefined) // false
Boolean(NaN) // false
Boolean({}) // true
Boolean([]) // true
Boolean(0) // false
Boolean(-1) // true
Boolean('0') // true

Number(value)——把给定的值转换成数字(可以是整数或浮点数);

Number(true) // 1
Number(false) // 0
Number(null) // 0
Number(undefined) // NaN

String(value)——把给定的值转换成字符串。

  • 3、利用 js 变量弱类型转换
1 + '1' // '11'
'1' / 1 // 1
'1' * 1 // 1
'1' - 1 // 0
!!1 // true
!'1' //false

你可能感兴趣的:(前端面试,前端,javascript,面试题)