js常见题

2020-04-05  本文已影响0人  一只小丫丫

1.require 与 import 的区别

两者的加载方式不同、规范不同

第一、两者的加载方式不同,

require 是在运行时加载,而 import 是在编译时加载

require('./a')(); // a 模块是一个函数,立即执行 a 模块函数
var data = require('./a').data; // a 模块导出的是一个对象
var a = require('./a')[0]; // a 模块导出的是一个数组,哪都行

import $ from 'jquery';
import * as _ from '_';
import {a,b,c} from './a';
import {default as alias, a as a_a, b, c} from './a'; //用在开头

第二、规范不同,

require 是 CommonJS/AMD 规范,import 是 ESMAScript6+规范
第三、
require 特点:社区方案,提供了服务器/浏览器的模块加载方案。非语言层面的标准。只能在运行时确定模块的依赖关系及输入/输出的变量,无法进行静态优化。
import 特点:语言规格层面支持模块功能。支持编译时静态分析,便于 JS 引入宏和类型检验。动态绑定。

2.什么是原型链?

通过一个对象的proto可以找到它的原型对象,原型对象也是一个对象,就可以通过原型对象的proto,最后找到了我们的 Object.prototype,从实例的原型对象开始一直到 Object.prototype 就是我们的原型链

3. 在 css/js 代码上线之后开发人员经常会优化性能,从用户刷新网页开始,一次 js 请求一般情况下有哪些地方会有缓存处理?

dns 缓存cdn 缓存浏览器缓存服务器缓存

4.iframe 跨域通信和不跨域通信

不跨域通信主页面

‹!DOCTYPE html›
‹html›
  ‹head›
    ‹meta charset="utf-8" /›
    ‹title›‹/title›
  ‹/head›
  ‹body›
    ‹iframe
      name="myIframe"
      id="iframe"
      class=""
      src="flexible.html"
      width="500px"
      height="500px"
    ›
    ‹/iframe›
  ‹/body›
  ‹script type="text/javascript" charset="utf-8"›
    function fullscreen() {
      alert(1111);
    }
  ‹/script›

‹/html›子页面 flexible.html

‹!DOCTYPE html›
‹html›
  ‹head›
    ‹meta charset="utf-8" /›
    ‹title›‹/title›
  ‹/head›
  ‹body›
    我是子页面
  ‹/body›
  ‹script type="text/javascript" charset="utf-8"›
    // window.parent.fullScreens()
    function showalert() {
      alert(222);
    }
  ‹/script›
‹/html›

5.H5 与 Native 如何交互

jsBridge

6.什么是面向对象?

面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。
解析:

面向对象和面向过程的异同

面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。
面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。

7.你对松散类型的理解

JavaScript 中的变量松散类型,所谓松散类型就是指当一个变量被申明出来就可以保存任意类型的值,就是不像 SQL 一样申明某个键值为 int 就只能保存整型数值,申明 varchar 只能保存字符串。一个变量所保存值的类型也可以改变,这在 JavaScript 中是完全有效的,只是不推荐。相比较于将变量理解为“盒子“,《JavaScript 编程精解》中提到应该将变量理解为“触手”,它不保存值,而是抓取值。这一点在当变量保存引用类型值时更加明显。JavaScript 中变量可能包含两种不同的数据类型的值:基本类型和引用类型。基本类型是指简单的数据段,而引用类型指那些可能包含多个值的对象。

8.JS 严格模式和正常模式

严格模式使用"use strict";

作用:
表现:

一、概述除了正常运行模式,

ECMAscript 5 添加了第二种运行模式:"严格模式"(strict mode)。顾名思义,这种模式使得 Javascript 在更严格的条件下运行。设立"严格模式"的目的,主要有以下几个:

"严格模式"体现了 Javascript 更合理、更安全、更严谨的发展方向,包括 IE 10 在内的主流浏览器,都已经支持它,许多大项目已经开始全面拥抱它。另一方面,同样的代码,在"严格模式"中,可能会有不一样的运行结果;一些在"正常模式"下可以运行的语句,在"严格模式"下将不能运行。掌握这些内容,有助于更细致深入地理解 Javascript,让你变成一个更好的程序员。本文将对"严格模式"做详细介绍。

二、进入标志

进入"严格模式"的标志,是下面这行语句:"use strict";老版本的浏览器会把它当作一行普通字符串,加以忽略。

三、如何调用"严格模式"

有两种调用方法,适用于不同的场合。
1 针对整个脚本文件将"use strict"放在脚本文件的第一行,则整个脚本都将以"严格模式"运行。
如果这行语句不在第一行,则无效,整个脚本以"正常模式"运行。如果不同模式的代码文件合并成一个文件,这一点需要特别注意。(严格地说,只要前面不是产生实际运行结果的语句,"use strict"可以不在第一行,比如直接跟在一个空的分号后面。)

 ‹script›
    "use strict";
    console.log("这是严格模式。");
  ‹/script›

  ‹script›
    console.log("这是正常模式。");
  kly, it's almost 2 years ago now. 
I can admit it now - I run it on my school's network that has about 50 computers.
  ‹/script›

上面的代码表示,一个网页中依次有两段 Javascript 代码。前一个 script 标签是严格模式,后一个不是。
2 针对单个函数将"use strict"放在函数体的第一行,则整个函数以"严格模式"运行。

function strict() {
  "use strict";
  return "这是严格模式。";
}

function notStrict() {
  return "这是正常模式。";
}

3 脚本文件的变通写法因为第一种调用方法不利于文件合并,所以更好的做法是,借用第二种方法,将整个脚本文件放在一个立即执行的匿名函数之中。

(function() {
  "use strict"; // some code here

})();

四、语法和行为改变

严格模式对 Javascript 的语法和行为,都做了一些改变。
1 全局变量显式声明在正常模式中,如果一个变量没有声明就赋值,默认是全局变量。严格模式禁止这种用法,全局变量必须显式声明。"use strict";

v = 1; // 报错,v未声明

for (i = 0; i ‹ 2; i++) {
  // 报错,i未声明
}

因此,严格模式下,变量都必须先用 var 命令声明,然后再使用。
2 静态绑定Javascript 语言的一个特点,就是允许"动态绑定",即某些属性和方法到底属于哪一个对象,不是在编译时确定的,而是在运行时(runtime)确定的。
严格模式对动态绑定做了一些限制。某些情况下,只允许静态绑定。也就是说,属性和方法到底归属哪个对象,在编译阶段就确定。这样做有利于编译效率的提高,也使得代码更容易阅读,更少出现意外。具体来说,涉及以下几个方面。

  var v = 1;

  with (o){ // 语法错误
    v = 2;
  }

9.移动端 click 事件、touch 事件、tap 事件的区别

click事件在移动端会有 200-300ms ms 的延迟,主要原因是苹果手机在设计时,考虑到用户在浏览网页时需要放大,所以,在用户点击的 200-300ms 之后,才触发 click,如果 200-300ms 之内还有 click,就会进行放大缩小。

touch 事件是针对触屏手机上的触摸事件。现今大多数触屏手机 webkit 内核提供了 touch 事件的监听,让开发者可以获取用户触摸屏幕时的一些信息。其中包括:touchstart,touchmove,touchend,touchcancel 这四个事件,touchstart touchmove touchend 事件可以类比于 mousedown mouseover mouseup 的触发
tap 事件在移动端,代替 click 作为点击事件,tap 事件被很多框架(如 zepto)封装,来减少这延迟问题, tap 事件不是原生的,所以是封装的,那么具体是如何实现的呢?

  ‹script›
    function tap(ele, callback) {
      // 记录开始时间
      var startTime = 0,
      // 控制允许延迟的时间
          delayTime = 200,
      // 记录是否移动,如果移动,则不触发tap事件
          isMove = false;

      // 在touchstart时记录开始的时间
      ele.addEventListener('touchstart', function (e) {
        startTime = Date.now();
      });

      // 如果touchmove事件被触发,则isMove为true
      ele.addEventListener('touchmove', function (e) {
        isMove = true;
      });

      // 如果touchmove事件触发或者中间时间超过了延迟时间,则返回,否则,调用回调函数。
      ele.addEventListener('touchend', function (e) {
        if (isMove || (Date.now() - startTime › delayTime)) {
          return;
        } else {
          callback(e);
        }
      })
    }

    var btn = document.getElementById('btn');
    tap(btn, function () {
      alert('taped');
    });
  ‹/script›

10.JS 单线程还是多线程,如何显示异步操作

JS 本身是单线程的,他是依靠浏览器完成的异步操作。

解析:具体步骤,

11. JavaScript 数组的函数 map/forEach/reduce/filter

map

// map
//作用:对数组进行遍历
//返回值:新的数组
// 是否改变:否
var arr = [2, 5, 3, 4];
var ret = arr.map(function(value) {
  return value + 1;
});
console.log(ret); //[3,6,4,5]
console.log(arr); //[2,5,3,4]

forEach

// forEach 方法
// 作用:遍历数组的每一项
// 返回值:undefined
// 是否改变:否
var arr = [2, 5, 3, 4];
var ret = arr.forEach(function(value) {
  console.log(value); // 2, 5, 3, 4
});
console.log(ret); //undefined
console.log(arr); //[2,5,3,4]

reduce

// reduce 方法
// 作用:对数组进行迭代,然后两两进行操作,最后返回一个值
// 返回值:return出来的结果
// 是否改变:不会
var arr = [1, 2, 3, 4];
var ret = arr.reduce(function(a, b) {
  return a * b;
});
console.log(ret); // 24
console.log(arr); // [1, 2, 3, 4]

filter

// filter 过滤
// 作用: 筛选一部分元素
// 返回值: 一个满足筛选条件的新数组
// 是否改变原有数组:不会

var arr = [2, 5, 3, 4];
var ret = arr.filter(function(value) {
  return value › 3;
});
console.log(ret); //[5,4]
console.log(arr); //[2,5,3,4]

12. JS 块级作用域、变量提升

块级作用域

JS 中作用域有:全局作用域函数作用域。没有块作用域的概念。ECMAScript 6(简称 ES6)中新增了块级作用域。块作用域由 { } 包括,if 语句和 for 语句里面的{ }也属于块作用域。

变量提升

如果变量声明在函数里面,则将变量声明提升到函数的开头
如果变量声明是一个全局变量,则将变量声明提升到全局作用域的开头
解析:

‹script type="text/javascript"›
    {
        var a = 1;
        console.log(a); // 1
    }
    console.log(a); // 1
    // 可见,通过var定义的变量可以跨块作用域访问到。

    (function A() {
        var b = 2;
        console.log(b); // 2
    })();
    // console.log(b); // 报错,
    // 可见,通过var定义的变量不能跨函数作用域访问到

    if(true) {
        var c = 3;
    }
    console.log(c); // 3
    for(var i = 0; i ‹ 4; i++) {
        var d = 5;
    };
    console.log(i); // 4   (循环结束i已经是4,所以此处i为4)
    console.log(d); // 5
    // if语句和for语句中用var定义的变量可以在外面访问到,
    // 可见,if语句和for语句属于块作用域,不属于函数作用域。

    {
        var a = 1;
        let b = 2;
        const c = 3;

        {
            console.log(a);     // 1    子作用域可以访问到父作用域的变量
            console.log(b);     // 2    子作用域可以访问到父作用域的变量
            console.log(c);     // 3    子作用域可以访问到父作用域的变量

            var aa = 11;
            let bb = 22;
            const cc = 33;
        }

        console.log(aa);    // 11   // 可以跨块访问到子 块作用域 的变量
        // console.log(bb); // 报错   bb is not defined
        // console.log(cc); // 报错   cc is not defined
    }
‹/script›

拓展:var、let、const 的区别

**var **定义的变量,没有块的概念,可以跨块访问, 不能跨函数访问。
let 定义的变量,只能在块作用域里访问,不能跨块访问,也不能跨函数访问。
const 用来定义常量,使用时必须初始化(即必须赋值),只能在块作用域里访问,而且不能修改。
同一个变量只能使用一种方式声明,不然会报错

‹script type="text/javascript"›
    // 块作用域
    {
        var a = 1;
        let b = 2;
        const c = 3;
        // c = 4; // 报错

        // let a = 'a'; // 报错  注:是上面 var a = 1; 那行报错
        // var b = 'b'; // 报错:本行报错
        // const a = 'a1';  // 报错  注:是上面 var a = 1; 那行报错
        // let c = 'c'; // 报错:本行报错

        var aa;
        let bb;
        // const cc; // 报错
        console.log(a); // 1
        console.log(b); // 2
        console.log(c); // 3
        console.log(aa); // undefined
        console.log(bb); // undefined
    }
    console.log(a); // 1
    // console.log(b); // 报错
    // console.log(c); // 报错

    // 函数作用域
    (function A() {
        var d = 5;
        let e = 6;
        const f = 7;
        console.log(d); // 5
        console.log(e); // 6  (在同一个{ }中,也属于同一个块,可以正常访问到)
        console.log(f); // 7  (在同一个{ }中,也属于同一个块,可以正常访问到)
    })();
    // console.log(d); // 报错
    // console.log(e); // 报错
    // console.log(f); // 报错
‹/script›

13. null/undefined 的区别

null: Null 类型,代表“空值",代表一个空对象指针,使用 typeof 运算得到 “object",所以你可以认为它是一个特殊的对象值。
undefined: Undefined 类型,当一个声明了一个变量未初始化时,得到的就是 undefined。

14. JS 哪些操作会造成内存泄露

function leak() {
  leak = "xxx"; //leak成为一个全局变量,不会被回收
}

function bindEvent() {
  var obj = document.createElement("XXX");
  obj.οnclick = function() {
    //Even if it's a empty function
  };
}

闭包可以维持函数内局部变量,使其得不到释放。 上例定义事件回调时,由于是函数内定义函数,并且内部函数--事件回调的引用外暴了,形成了闭包。
解决之道,将事件处理函数定义在外部,解除闭包,或者在定义事件处理函数的外部函数中,删除对 dom 的引用。

//将事件处理函数定义在外部
function onclickHandler() {
  //do something
}
function bindEvent() {
  var obj = document.createElement("XXX");
  obj.οnclick = onclickHandler;
}

//在定义事件处理函数的外部函数中,删除对dom的引用
function bindEvent() {
  var obj = document.createElement("XXX");
  obj.οnclick = function() {
    //Even if it's a empty function
  };
  obj = null;
}

var elements={
    button: document.getElementById("button"),
    image: document.getElementById("image"),
    text: document.getElementById("text")
};
function doStuff(){
    image.src="http://some.url/image";
    button.click():
    console.log(text.innerHTML)
}
function removeButton(){
    document.body.removeChild(document.getElementById('button'))
}

var someResouce = getData();
setInterval(function() {
  var node = document.getElementById("Node");
  if (node) {
    node.innerHTML = JSON.stringify(someResouce);
  }
}, 1000);

这样的代码很常见, 如果 id 为 Node 的元素从 DOM 中移除, 该定时器仍会存在, 同时, 因为回调函数中包含对 someResource 的引用, 定时器外面的 someResource 也不会被释放。

function fn() {
  var a = {};
  var b = {};
  a.pro = b;
  b.pro = a;
}
fn();

fn()执行完毕后,两个对象都已经离开环境,在标记清除方式下是没有问题的,但是在引用计数策略下,因为 a 和 b 的引用次数不为 0,所以不会被垃圾回收器回收内存,如果 fn 函数被大量调用,就会造成内存泄漏。在 IE7 与 IE8 上,内存直线上升。
IE 中有一部分对象并不是原生 js 对象。
例如,其内存泄漏 DOM 和 BOM 中的对象就是使用 C++以 COM 对象的形式实现的,而 COM 对象的垃圾回收机制采用的就是引用计数策略。因此,即使 IE 的 js 引擎采用标记清除策略来实现,但 js 访问的 COM 对象依然是基于引用计数策略的。换句话说,只要在 IE 中涉及 COM 对象,就会存在循环引用的问题。

var element = document.getElementById("some_element");
var myObject = new Object();
myObject.e = element;
element.o = myObject;

上面的例子在一个 DOM 元素(element)与一个原生 js 对象(myObject)之间创建了循环引用。其中,变量 myObject 有一个名为 e 的属性指向 element 对象;而变量 element 也有一个属性名为 o 回指 myObject。由于存在这个循环引用,即使例子中的 DOM 从页面中移除,它也永远不会被回收。看上面的例子,有人会觉得太弱了,谁会做这样无聊的事情,但是其实我们经常会这样做

window.οnlοad=function outerFunction(){
  var obj=document.getElementById("element"):
  obj.οnclick=function innerFunction(){};
};

这段代码看起来没什么问题,但是 obj 引用了 document.getElementById(“element”),而 document.getElementById(“element”)的 onclick 方法会引用外部环境中的变量,自然也包括 obj,是不是很隐蔽啊。最简单的解决方式就是自己手工解除循环引用,比如刚才的函数可以这样

myObject.element=null;
element.o=null;
window.οnlοad=function outerFunction(){
  var obj=document.getElementById("element"):
  obj.οnclick=function innerFunction(){};
  obj=null;
};

将变量设置为 null 意味着切断变量与它此前引用的值之间的连接。当垃圾回收器下次运行时,就会删除这些值并回收它们占用的内存。 要注意的是,IE9+并不存在循环引用导致 Dom 内存泄漏问题,可能是微软做了优化,或者 Dom 的回收方式已经改变

15.重排与重绘的区别,什么情况下会触发?

简述重排的概念

浏览器下载完页面中的所有组件(HTML、JavaScript、CSS、图片)之后会解析生成两个内部数据结构(DOM 树和渲染树),DOM 树表示页面结构,渲染树表示 DOM 节点如何显示。重排是 DOM 元素的几何属性变化,DOM 树的结构变化,渲染树需要重新计算。

简述重绘的概念

重绘是一个元素外观的改变所触发的浏览器行为,例如改变 visibility、outline、背景色等属性。浏览器会根据元素的新属性重新绘制,使元素呈现新的外观。由于浏览器的流布局,对渲染树的计算通常只需要遍历一次就可以完成。但 table 及其内部元素除外,它可能需要多次计算才能确定好其在渲染树中节点的属性值,比同等元素要多花两倍时间,这就是我们尽量避免使用 table 布局页面的原因之一。

简述重绘和重排的关系

重绘不会引起重排,但重排一定会引起重绘,一个元素的重排通常会带来一系列的反应,甚至触发整个文档的重排和重绘,性能代价是高昂的。

什么情况下会触发重排?
重排优化有如下五种方法

17.发布订阅设计模式

发布/订阅模式(Publish Subscribe Pattern)属于设计模式中的行为(Behavioral Patterns)

18. jsonp 优缺点?

jsonp 优缺点

优点
缺点

19.兼容各种浏览器版本的事件绑定

/*
兼容低版本IE,ele为需要绑定事件的元素,
eventName为事件名(保持addEventListener语法,去掉on),fun为事件响应函数
*/

function addEvent(ele, eventName, fun) {
  if (ele.addEventListener) {
    ele.addEventListener(eventName, fun, false);
  } else {
    ele.attachEvent("on" + eventNme, fun);
  }
}

20.typescript 遇到过什么坑

main.js 报错( Cannot find module './App.vue'.)
原因:typescript 不能识别.vue 文件
解决办法:引入 vue 的 typescript declare 库

上一篇 下一篇

猜你喜欢

热点阅读