加载中...
JavaScript常见问题
发表于:2023-04-12 | 分类: 面试总结

闭包 和 作用域

作用域

作用域是指在程序中定义变量的区域,作用域规定了变量与函数的活动范围。

在 es6 之前,js 中只有全局作用域和函数作用域。在这之后,引入了 let 和 const ,它们会创建块级作用域。

在函数调用栈中,当内部函数查找一个外部变量,则 js 引擎就会向上级作用域中查找。这个查找的链条称为作用域链。

闭包

闭包的目的是能在函数的外部作用域中访问函数内部的上下文。通过作用域以及作用域链,我们知道了当前作用域中(outer)的上下文(变量),会在当前调用栈结束调用后被 gc 销毁。但是通过作用域链,我们可以在一个内部函数(inner)中访问 outer 作用域中的变量,创建引用关系。这样,就算调用栈执行完毕后也不会销毁 outer 中被引用的执行上下文。

function outer() {
  // outer
  var a = 1;
  return {
    getA: function () {
      // inner
      return a;
    },
    setA: function (v) {
      // inner
      a = v;
    },
  };
}

你是如何组织自己的代码?是使用模块模式,还是使用经典继承的方法?

正常些 js 方法时使用模块模式,一个 IIFE 就算是一个模块,互相之间不影响。

继承在 react 跟 node 中也是比较常用的。

var jspy = (function () {
  var _count = 0;

  var incrementCount = function () {
    _count++;
  };

  var getCount = function () {
    return _count;
  };

  return {
    incrementCount: incrementCount,
    getCount: getCount,
  };
})();

匿名函数的典型用法

匿名函数就是没有名称的函数, 如:

(function () {})();

!(function () {})();

void (function () {})();

function foo() {
  return function () {};
}

宿主对象(host object) 和原生对象(native object)、内置对象的区别

请指出以下代码的区别:function Person(){}、var person = Person()、var person = new Person()?

  1. 定义了一个名为 Person 的函数
  2. 普通的调用 Person 函数,并将返回值赋值到 person 变量上。
  3. 使用 new 关键字,将 Person 作为构造函数实例化

call apply bind

call, apply 实现
bind 实现

Function.prototype.call = function(context) {
    // this传参为null则指向window
    var context = context || window;

    context.fn = this;

    var args = [];

    for (var i = 1, len = arguments.length; i < len ; i++) {
        args.push('arguments[' + i + ']');
    }

    var result = eval('context.fn(' + args +')');
    delete context.fn;
    return result;
}

Function.prototype.apply = function(context, arr) {
    var context = Object(context) || window;

    context.fn = this;

    var result;

    if (!arr) {
        result = context.fn();
    } else {
        var args = [];
        for (var i = 0 , len = arr.length; i< len ; i++) {
            arr.push('arr['+i+']');
        }
        result = eval('context.fn(+' args '+)')
    }
    delete context.fn
    return result;
}

Function.prototype.bind = function(context) {
    var outerArgs = Array.prototype.slice.call(arguments, 1);
    // 实例
    var self = this;

    return function() {
        var innerArgs = Array.prototype.slice.call(arguments);
        var args = outerArgs.concat(innerArgs)
        return self.apply(context, args)
    }
}

new 实现

function fakeNew() {
  var obj = Object();
  var Constructor = [].shift.call(arguments);

  obj.__proto__ = Constructor.prototype;
  var ret = Constructor.apply(obj, arguments);

  return typeof ret === "object" ? ret : obj;
}

函数防抖和函数节流

防抖函数概念:

该函数只能在指定延时结束后才能调用,如果在过程中重复调用,则重新计时。

应用场景:

  1. 用户在搜索框输入时的数据查询,指定n 毫秒 延时,只能在输入完后过了n毫秒后才会去搜索,中间的持续输入则会重新计时
  2. 在监听 window.onresize 事件,并触发某些操作时。
function debounce(fn, interval) {
  var timer;
  return function () {
    var _self = this;
    if (timer) clearTimeout(timer);

    timer = setTimeout(function () {
      fn.apply(_self, arguments);
    }, interval);
  };
}

函数节流:

在规定时间内,函数只能被调用一次。如果单位时间内多次触发,则忽略

应用场景:

  1. 点击搜索按钮时的防重复。
  2. 监听 scroll 事件时
function throttle(fn, interval) {
  var last = 0;
  return function () {
    var now = +new Date();
    if (now - last >= interval) {
      fn.apply(this, arguments);
      last = +new Date();
    }
  };
}

什么情况下会使用 document.write()

  • 加载需要配合 js 脚本使用的外部 css 文件
<scirpt>
document.write('<link  rel="stylesheet" href="style_neads_js.css">');
</script>
  • 在新打开的页面中写入数据时
    由于 document.write 会重写整个页面,异步调用会影响本页面的文档,如果在新窗口空白页调用,就没影响了。新开一个窗口,把本页面取到的数据在新窗口展示。
document.open();
document.write("hello world");
document.close();

特性检测、特性推断、浏览器 UA 字符串嗅探

Ajax 工作原理

跨域方案

图片 Ping
图片 Ping 是客户端向服务器的单向通信,因为 src 请求资源不属于同源策略,所以一般可以用来做埋点、前端监控,比如监听网页的 PV(Page View),UV(Unique Visitor)。
最好是采用 1 * 1 像素的透明 gif 图,因为:

  1. 能够完成整个 HTTP 请求+响应(尽管不需要响应内容)
  2. 触发 GET 请求之后不需要获取和处理数据、服务器也不需要发送数据
  3. 跨域友好
  4. 执行过程无阻塞
  5. 相比 XMLHttpRequest 对象发送 GET 请求,性能上更好
  6. GIF 的最低合法体积最小(最小的 BMP 文件需要 74 个字节,PNG 需要 67 个字节,而合法的 GIF,只需要 43 个字节)

变量声明提升

js 冒泡机制

property(属性)和 attribute(特性)

  • DOM 有其默认的基本属性,而这些属性就是所谓的“property”,无论如何,它们都会在初始化的时候再 DOM 对象上创建。

  • HTML 标签中定义的属性和值会保存该 DOM 对象的 attributes 属性里面;

  • 这些 attribute 属性的 JavaScript 中的类型是 Attr,而不仅仅是保存属性名和值这么简单;

<input id="in_2" sth="whatever" />
var in2 = document.getElementById("in_2");
console.log(in2);
// id: "in_2"
// value: null

console.log(in1.attibutes.sth); // 'sth="whatever"'

document load 和 document DOMContentLoaded

  • load 方法在网页中所有的资源(HTML,CSS,image)都完全加载后才触发
  • 当初始的 HTML 文档被完全加载和解析完成之后,DOMContentLoaded 事件被触发,而无需等待样式表、图像和子框架的完成加载

== 和 === 有什么不同

同源策略 (same-origin policy)

strict 模式

为何通常会认为保留网站现有的全局作用域 (global scope) 不去改变它,是较好的选择

  • 减少命名冲突
  • 有利于模块化

请解释什么是单页应用 (single page app), 以及如何使其对搜索引擎友好 (SEO-friendly)

单页应用是指在浏览器中运行的应用,它们在使用期间不会重新加载页面。像所有的应用一样,它旨在帮助用户完成任务,比如“编写文档”或者“管理 Web 服务器”。可以认为单页应用是一种从 Web 服务器加载的富客户端

使用服务端渲染

Promise 如何使用,实现

可变对象和不可变对象

可变对象

我们知道,JavaScript 中对象是弱类型的。一般情况下,可以不受限制的为对象添加属性,修改属性,删除属性。大部分情况下,我们使用的都是可变对象。

不可变对象

对应的,我们不希望代码中某些对象被任意修改,比如添加、修改、删除等。这就是我们的不可变对象。JavaScript 为我们提供了一些原生方法,借助它们可以讲一些可变对象转变成不可变对象。一共有三种:不可扩展,密封,冻结。

  1. Object.preventExtensions(obj) 不可扩展(无法阻止深层属性的扩展)
function Person(name) {
  this.name = name;
}

var person1 = new Person("william");
Object.preventExtensions(person1); // 阻止扩展属性
person1.age = 10;

console.log(person1); // {name: 'william'}

var person2 = {
  name: "william",
  info: {
    age: 23,
    height: "183cm",
  },
};

Object.preventExtensions(person2);
person2.info.sex = "male"; // 可以扩展深层属性

person2.info.sex; // male
  1. Object.seal(obj) 方法可以让一个对象密封,并返回被密封后的对象。密封对象将会阻止向对象添加新的属性。另外也会 改变属性的 configurable 描述。

  2. Object.freeze(obj) 方法可以冻结一个对象。冻结指的是不能向这个对象添加新的属性,不能修改其已有属性的值,不能删除已有属性,以及不能修改该对象已有属性的可枚举性、可配置性、可写性。也就是说,这个对象永远是不可变的。该方法返回被冻结的对象。

什么是事件循环

let var const

数组的方法

web worker

函数柯里化

创建对象的三种方法

  • 构造函数: var obj = new Object();
  • 对象字面量: var obj = {};
  • Object.create(obj[, otherobj]);

深拷贝和浅拷贝

var obj = {
  name: "william",
  age: 20,
  other: {
    work: "code",
    eat: ["胡萝卜", "西瓜", { test: 1 }],
  },
};

var arr = [1, 2, 3, [4, 5, { test: 6 }]];

function deepClone(data) {
  var type = Object.prototype.toString.call(data);
  var newData;

  if (type === "[object Object]") {
    newData = {};
    for (var key in data) {
      if (
        Object.prototype.toString.call(data[key]) !== "[object Object]" &&
        Object.prototype.toString.call(data[key]) !== "[object Array]" &&
        hasOwnProperty.call(data, key) &&
        !hasOwnProperty.call(newData, key)
      ) {
        newData[key] = data[key];
      } else {
        newData[key] = deepClone(data[key]);
      }
    }
  } else if (type === "[object Array]") {
    newData = [];
    for (var i = 0; i < data.length; i++) {
      if (
        Object.prototype.toString.call(data[i]) !== "[object Object]" &&
        Object.prototype.toString.call(data[i]) !== "[object Array]"
      ) {
        newData.push(data[i]);
      } else {
        newData.push(deepClone(data[i]));
      }
    }
  } else {
    return data;
  }
  return newData;
}

网页上各种高度

实现页面加载进度条

箭头函数和普通函数有什么区别

箭头函数

箭头函数 普通函数
写法 var fn = () => {} function fn() {}
arguments
this 运行时向父作用域查找,直到 window 1. 作为函数调用,指向 window 2. 作为对象的方法调用,指向当前对象
new 不能使用 函数作为构造函数
原型属性 没有原型属性console.log(fn.prototype) //undefined 有原型属性
上一篇:
前端日常-基于React-Router(V6)的权限控制
下一篇:
前端性能优化篇
本文目录
本文目录