前端javascript面试技巧

176 阅读10分钟

课程总结

课程总结

变量类型和计算

题目

  • JS中使用typeof 能得到哪些类型
  • 何时使用=== 何时使用===
  • JS 中有哪些内置函数
  • JS 变量按照存储方式区分为哪些类型,并描述其特点
  • 如何理解JSON

解答

JS中使用typeof 能得到哪些类型 2020-03-13_224658.png

何时使用=== 何时使用===

    if (obj.a == null) {
        //这里相当于obj.a===null || obj.a===undefined 简写形式
        //这是jQuery 源码中推荐的写法
    }

JS 中的内置函数 2020-03-13_224602.png

如何理解JSON

    // JSON 只不过是一个JS对象而已
    JSON.stringify({a: 10, b: 20});
    JSON.parse('{"a":10,"b":20}');

变量类型

值类型 VS 引用类型

值类型

   var a = 100;
   var b = a;
   a = 200;
   console.log(b);

引用类型:对象、数组、函数

    var a = {age: 200};
   var b = a;
   b.age = 21;
   console.log(a.age);

typeof 运算符详解

变量计算--强制类型转换

字符串拼接

   var a = 100 + 10; //110
   var b = 100 + "10"; //10010

== 运算符

    console.log(100 == "100") //true
   console.log(0 == "") //true
   console.log(null == undefined) //true

if 语句

逻辑计算

原型和原型链

题目

  • 如何准确判断一个变量是数组类型
  • 写一个原型链继承的例子
  • 描述new 一个对象的过程
  • zepto(或其他框架)源码中如何使用原型链

解答

如何准确判断一个变量是数组类型

    var arr = [];
    console.log(arr instanceof Array);
    console.log(typeof arr); //typeof 无法判断arr是否是数组

写一个原型链继承的例子

    function Anaimal() {
        this.eat = function () {
            console.log("animal eat");
        }
    };

    function Dog() {
        this.bark = function () {
            console.log("dog bark");
        }
    };

    Dog.prototype = new Anaimal();
    // 哈士奇
    var hashiqi = new Dog();
    console.log(hashiqi);

描述new 一个对象的过程 2020-03-30_020434.png

构造函数

    function Foo(name, age) {
        //this = {}; //this 变成空对象
        this.name = name;
        this.age = age;
        this.calss = "class-1";
        // return this; //默认有这一行
    }

    var f = new Foo("zhangsan", 20);
    var f1 = new Foo("lisi", 20); //创建多个对象

构造函数--扩展

2020-03-13_231555.png

原型规则和示例

5条原型规则,原型规则是学习原型链的基础

  • 所有的引用类型(数组、对象、函数),都具有对象特性,即可自由扩展属性(除了“null”)。
  • 所有的引用类型(数组、对象、函数),都有一个_proto_(隐式原型)属性,属性值是一个普通的对象。
  • 所有的函数,都有一个prototype(显式原型)属性,属性值也是一个普通的对象。
  • 所有的引用类型(数组、对象、函数),_proto_属性值是指向它的构造函数的“prototype”属性值。
  • 当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性时,那么会去它的_proto_(即它的构造函数的“prototype”)中寻找。
    var obj = {};
    obj.a = 100;

    var arr = [];
    arr.a = 100;

    function fn() {};
    fn.a = 100;

    console.log(obj.__proto__);
    console.log(arr.__proto__);
    console.log(fn.__proto__);

    console.log(fn.prototype);
    console.log(obj._proto_ === Object.prototype);

如果这个对象本身没有这个属性时,那么会去它的_proto_(即它的构造函数的“prototype”)中寻找

    // 构造函数
    function Foo(name, age) {
        this.name = name;
    };
    Foo.prototype.alertName = function () {
        alert(this.name);
    };
    // 创建实例
    var f = new Foo("zhangsan");
    f.printName = function () {
        console.log(this.name);
    };

    // 测试
    f.alertName();
    f.printName();

循环对象自身的属性

    var item;
    for (item in f) {
        /*高级浏览器已经在for in中屏蔽了来自原型的属性,但是在这里建议
        大家加上这个判断,保证程序的健壮性。*/
        if (f.hasOwnProperty(item)) {
            console.log(item);
        }
    }

原型链

    // 构造函数
    function Foo(name, age) {
        this.name = name;
    };
    Foo.prototype.alertName = function () {
        alert(this.name);
    };
    // 创建实例
    var f = new Foo("zhangsan");
    f.printName = function () {
        console.log(this.name);
    };

    // 测试
    f.alertName();
    f.printName();
    f.toString();  //要去f.__proto__.__proto__中查找

2020-03-30_015449.png

instanceof

用于判断引用类型属于哪个构造函数的方法。 2020-03-30_015628.png

作用域和闭包

题目

  • 说一下对变量提升的理解
  • 说明this 几种不同的使用场景
  • 创建10个a标签,点击的时候弹出来对应的序号
  • 如何理解作用域
  • 实际开发中闭包的应用

解答

函数声明与函数表达式的区别

    fn();
    function fn() { }; //函数声明

    fn1(); //会报错
    var fn1 = function () {}; //函数表达式

执行上下文

    console.log(a); //undefined
    var a = 100;

    fn("zhangsan"); //"zhangsan",20
    function fn(name, age) {
        age = 20;
        console.log(name, age);
        var age;
    };
  • 范围:一段< script >或者一个函数
  • 全局:变量定义、函数声明
  • 函数:变量定义、函数声明,this,arguments

this

  • this 要在执行时才能确认值,定义时无法确认 2020-03-14_001356.png
  • 作为构造函数执行
  • 作为对象属性执行
  • 作为普通函数执行
  • call apply bind
    // 作为构造函数执行
    function Foo(name) {
        this.name = name;
    };
    var f = new Foo("zhangsan");

    // 作为对象属性执行
    var obj = {
        name: "A",
        printName: function () {
            console.log(this.name);
        }
    }
    obj.printName();

    // 作为普通函数执行
    function fn() {
        console.log(this);
    };
    fn();

    // call apply
    function fn1(name, age) {
        alert(name);
        console.log(this);
    };
    fn1.call({x: 100}, "zhangsan", 20); //工作中常用这个
    fn1.apply({x: 100}, ["zhangsan", 20]);

    // bind 只能用函数表达式,函数声明会报错
    var fn2 = function (name, age) {
        alert(name);
        console.log(this);
    }.bind({y: 200});
    fn2("zhangsan", 20);

作用域

  • 没有块级作用域
  • 只有函数和全局作用域
    // 无块级作用域
    if (true) {
        var name = "zhangsan";
    }
    console.log(name); // zhangsan
    
    // 函数和全局作用域
    var a = 100;
    function fn() {
        var a = 200;
        console.log("fn", a);
    };
    console.log("global", a);
    fn();

作用域链

    var a = 100;
    function fn() {
        var b = 200;
        //当前作用域没有定义的变量,即“自由变量”
        console.log(a);

        console.log(b);
    };
    fn();

另一个例子

    var a = 100;
    function F1() {
        var b = 200;
        function F2() {
            var c = 300;
            console.log(a); //自由变量
            console.log(b); //自由变量
            console.log(c);
        };
        F2()
    };
    F1();

闭包

函数作为返回值

    function F1() {
        var a = 100;

        // 返回一个函数(函数作为返回值)
        return function () {
            console.log(a); // 自由变量,父作用域寻找
        }
    }
    // f1 得到一个函数
    var f1 = F1();
    var a = 200; //一个函数的作用域,是它定义时候的作用域,而不是它执行时候的作用域
    f1();

闭包的使用场景

  • 函数作为返回值(上一个demo)
  • 函数作为参数传递(自己思考)

函数作为参数传递

    function F1() {
        var a = 100;

        return function () {
            console.log(a);
        }
    }
    var f1 = F1();

    function F2(fn) {
        var a = 200;
        fn(); //定义时的作用域,而非执行时的作用域
    }
    F2(f1);

异步和单线程

题目

  • 同步和异步的区别是什么?分别举一个同步和异步的例子
  • 一个关于setTimeout的笔试题
  • 前端使用异步的场景有哪些?

解答 同步和异步的区别是什么?

  • 同步会阻塞代码执行,而异步不会
  • alert 是同步,setTimeout 是异步

什么是异步(对比同步)

什么是异步

    console.log(100);
    setTimeout(function () {
        console.log(200);
    }, 0);
    console.log(300);

对比同步

    console.log(100);
    alert(200);
    console.log(300);

何时需要异步

  • 在可能发生等待的情况
  • 等待过程中不能像(alert)一样阻塞程序执行
  • 因此,所有的“等待情况”都需要异步

前端使用异步的场景

  • 定时任务setTimeout、setInterval
  • 网络请求:ajax请求,动态< img >加载
  • 事件绑定

Ajax 请求代码示例

    console.log(100);
    $.get("./data.json", function (data) {
        console.log(data);
    });
    console.log(300);

< img >加载示例

    console.log("start");
    var img = document.createElement("img");
    img.onload = function () {
        console.log("loaded");
    };
    img.src = "xxx.png";
    console.log("end");

事件绑定示例

    console.log("start");
    document.getElementById("btn").addEventListener("click", function () {
        alert("clicked");
    });
    console.log("end");

异步和单线程

因为javascript 是单线程的,才会有异步这个概念。

    console.log(100);
    setTimeout(function () {
        console.log(200);
    }); // 没写时间
    console.log(300);

2020-03-14_144117.png

其它知识

题目

  • 获取2017-06-10 格式的日期
  • 获取随机数,要求是长度一致的字符串格式
  • 写一个能遍历数组和对象的forEach 函数

解答

获取2017-06-10 格式的日期

    function formatDate(dt) {
        if (!dt) {
            dt = new Date();
        }
        var year = dt.getFullYear();
        var month = dt.getMonth();
        var date = dt.getDate();
        if (month < 10) {
            month = "0" + month;
        }
        if (date < 10) {
            date = "0" + date;
        }
        return year + "-" + month + "-" + date;
    }

    var dt = new Date();
    var formatDate = formatDate(dt);
    console.log(formatDate);

获取随机数,要求是长度一致的字符串格式

    var random = Math.random();
    random = random + "0000000000"; //后面加上10个零
    random = random.slice(0, 10);
    console.log(random);

写一个能遍历数组和对象的forEach 函数

    function forEach(obj, fn) {
        var key;
        if (obj instanceof Array) {
            // 准确判断是不是数组
            obj.forEach(function (item, index) {
                fn(index, item);
            })
        } else {
            // 不是数组就是对象
            for (key in obj) {
                fn(key, obj[key]);
            }
        }
    }

    var arr = [1, 2, 3];
    // 注意:这里参数的顺序换了,为了和对象的遍历格式一致
    forEach(arr, function (index, item) {
        console.log(index, item);
    });
    var obj = {x: 100, y: 200};
    forEach(obj, function (key, value) {
        console.log(key, value);
    });

日期

    Date.now(); //获取当前时间毫秒数
    var dt = new Date();
    dt.getTime(); //获取毫秒数
    dt.getFullYear(); //年
    dt.getMonth(); //月(0-11)
    dt.getDate(); //日(0-31)
    dt.getHours(); //小时(0-23)
    dt.getMinutes(); //分钟(0-59)
    dt.getSeconds(); //秒(0-59)

Math

  • 获取随机数 Math.rondom();

数组API

2020-03-14_162430.png forEach

    var arr = [1, 2, 3];
    arr.forEach(function (item, index) {
        //遍历数组的所有元素
        console.log(index, item);
    });

every

    var arr = [1, 2, 3];
    var result = arr.every(function (item, index) {
        //用来判断所有的数组元素,都满足一个条件
        if (item < 4) {
            return true;
        }
    });
    console.log(result);

some

    var arr = [1, 2, 3];
    var result = arr.some(function (item, index) {
        //用来判断所有的数组元素,只有有一个满足条件即可
        if (item < 2) {
            return true;
        }
    });
    console.log(result);

sort

    var arr = [1, 4, 2, 3, 5];
    var arr2 = arr.sort(function (a, b) {
        //从小到大排序
        return a - b;

        //从大到小排序
        // return b - a;
    });
    console.log(arr2);

map

    var arr = [1, 2, 3, 4];
    var arr2 = arr.map(function (item, index) {
        // 将元素重新组装,并返回
        return "<b>" + item + "</b>";
    });
    console.log(arr2);

filter

    var arr = [1, 2, 3];
    var arr2 = arr.filter(function (item, index) {
        // 通过某一个条件过滤数组
        if (item >= 2) {
            return true;
        }
    });
    console.log(arr2);

对象API

    var obj = {
        x: 100,
        y: 200,
        z: 300
    };
    var key;
    for (key in obj) {
        //注意这里的hasOwnProperty,在讲原型链时讲过了
        if (obj.hasOwnProperty(key)) {
            console.log(key, obj[key]);
        }
    }

JS-Web-API

回顾JS 基础知识 2020-03-14_165453.png JS-Web-API

  • 基础知识: ECMA 262标准 (这个标准是规定一些基础语法、规则,在这个规则上面,我们怎么去开发,怎么去弹出一个弹框)
  • JS-Web-API: W3C标准(浏览器对JS的运行,既要遵循ECMA 262标准,又要遵循W3C标准) 2020-03-14_170543.png 总结 2020-03-14_170928.png

DOM 操作

文档对象模型:Dodument Object Model 题目

  • DOM 是哪种基本的数据结构?
  • DOM 操作的常用API 有哪些?
  • DOM 节点的Attribute 和 property 有何区别?

解答

DOM 是哪种基本的数据结构?

DOM 操作的常用API 有哪些?

  • 获取DOM 节点,以及DOM 的property 和 Attribute
  • 获取父节点,获取子节点

DOM 节点的Attribute 和 property 有何区别?

  • property 只是一个JS 对象的属性的修改
  • Attribute 是对html 标签属性的修改

DOM 的本质

XML 2020-03-14_172129.png html 是 xml 的一种特殊类型 2020-03-14_172343.png DOM 可以理解为:浏览器拿到的html代码,结构化一个浏览器能识别并且js可操作的一个模型而已。

DOM 节点操作

  • 获取DOM 节点
  • Prototype
  • Attribute

获取DOM 节点

    var div1 = document.getElementById("div"); //元素
    var divList = document.getElementsByTagName("div"); //集合
    var containerList = document.getElementsByClassName(".container"); //集合
    var pList = document.querySelectorAll("p"); //集合

Prototype

    var pList = document.querySelectorAll("p");
    var p = pList[0];
    console.log(p.style.width); //获取样式
    p.style.width = "100px"; //修改样式
    console.log(p.className); //获取class
    p.className = ".para"; //修改class

    // 获取nodeName 和 nodeType
    console.log(p.nodeName);
    console.log(p.nodeType);

Attribute

    var pList = document.querySelectorAll("p");
    var p = pList[0];
    p.getAttribute("data-name");
    p.setAttribute("data-name", "imooc");
    p.getAttribute("style");
    p.setAttribute("style", "font-size:30px");

DOM 结构操作

  • 新增节点
  • 获取父元素
  • 获取子元素
  • 删除节点

新增节点

    var div1 = document.getElementById("div");
    // 添加新节点
    var p1 = document.createElement("p");
    p1.innerHTML = "this is p1";
    div1.appendChild(p1); //添加新创建的元素
    // 移动已有节点
    var p2 = document.getElementById("p2");
    div1.appendChild(p2);

获取父元素和子元素

    var div1 = document.getElementById("div");
    var parent = div1.parentElement;

    var child = div1.childNodes;
    div1.removeChild(child[0]);

删除节点

    var div1 = document.getElementById("div");
    var child = div1.childNodes;
    div1.removeChild(child[0]);

BOM 操作

BOM:Brower Object Model 题目

  • 如何检测浏览器的类型
  • 拆解URL 的各个部分

解答

如何检测浏览器的类型?

    var ua = navigator.userAgent;
    var isChrome = ua.indexOf("Chrome");
    console.log(isChrome);

navigator

    var ua = navigator.userAgent;
    var isChrome = ua.indexOf("Chrome");
    console.log(isChrome);

screen

    console.log(screen.width);
    console.log(screen.height);

location

    console.log(location.href);
    console.log(location.protocol);
    console.log(location.pathname);
    console.log(location.search);
    console.log(location.hash);
    console.log(location.host);

history

    history.back();
    history.forward();

事件

题目

  • 编写一个通用的事件监听函数
  • 描述事件冒泡流程
  • 对于一个无限下拉加载图片的页面,如何给每个图片绑定事件

通用事件绑定

    var btn = document.getElementById("btn");
    btn.addEventListener("click", function (e) {
        alert("clicked");
    });

    function bindEvent(elem, type, fn) {
        elem.addEventListener(type, fn);
    };
    var a = document.getElementById("link");
    bindEvent(a, "click", function (e) {
        e.preventDefault();
        alert("clicked");
    });

完善通用事件绑定函数

    function bindEvent(elem, type, selector, fn) {
        if (fn == null) {
            fn = selector;
            selector = null;
        }
        elem.addEventListener(type, function (e) {
            var target;
            if (selector) {
                target = e.target;
                if (target.matches(selector)) {
                    fn.call(target, e);
                }
            } else {
                fn(e);
            }
        });
    }

事件冒泡

HTML 结构

<div id="div1">
    <p id="p1">激活</p>
    <p id="p2">取消</p>
    <p id="p3">取消</p>
    <p id="p4">取消</p>
</div>
<div id="div2">
    <p id="p5">取消</p>
    <p id="p6">取消</p>
</div>

script 结构

    var p1 = document.getElementById("p1");
    var body = document.body;

    p1.addEventListener("click", function (e) {
        // e.stopPropagation(); 
        alert("激活");
    });
    body.addEventListener("click", function (e) {
        alert("取消");
    });

代理

事件冒泡的具体应用

<div id="div1">
    <a href="#">a1</a>
    <a href="#">a2</a>
    <a href="#">a3</a>
    <!--会随时新增更多 a 标签-->
</div>

script 结构

    var div1 = document.getElementById("div1");
    
    div1.addEventListener("click", function (e) {
        var target = e.target;
        if (target.nodeName === "A") {
            alert(target.innerHTML);
        }
    });

使用代理

    var div1 = document.getElementById("div1");
    bindEvent(div1, "click", "a", function (e) {
        console.log(this.innerHTML)
    });

不使用代理

    var div1 = document.getElementById("div1");
    var a = document.getElementById("a1");
    bindEvent(div1, "click", function (e) {
        console.log(a.innerHTML)
    });

Ajax和跨域

Ajax

    var xhr = new XMLHttpRequest();
    xhr.open("GET", "/api", false);
    xhr.onreadystatechange = function () {
        if (xhr.readyState === 4) {
            if (xhr.status === 200) {
                alert(xhr.responseText);
            }
        }
    };
    xhr.send(null);

什么是跨域

  • 浏览器有同源策略,不允许ajax访问其他域接口。协议、域名、端口,有一个不同就算跨域。

有三个标签允许跨域加载资源

  • < img src=xxx>
  • < link href=xxx>
  • < script src=xxx>

三个标签使用的场景 2020-03-29_211320.png

JSONP

JSONP 原理 2020-03-29_211352.png 2020-03-29_211418.png 2020-03-29_211442.png

服务端设置http header 2020-03-29_211513.png

存储

题目

  • 请描述下cookie、sessionStorage 和 localStorage 的区别?

cookie

  • 本身用于客户端和服务端通信
  • 但是它有本地存储的功能,于是就被“借用”
  • 使用document.cookie=xxx获取和修改即可

cookie 用于存储的缺点

  • 存储量太小,只有4kb
  • 所有http请求都带着,会影响获取资源的效率
  • API简单,需要封装才能用document.cookie=xxx

sessionStorage 和 localStorage

  • HTML5专门为存储而设计,存储量5M
  • API 简单易用

cookie、sessionStorage 和 localStorage 的区别

  • 容量
  • 是否会携带到ajax中
  • API易用性

模块化

不使用模块化的情况

不使用模块化 2020-04-06_204124.png 代码 2020-04-06_204232.png 使用 2020-04-06_204403.png

使用模块化

2020-04-06_205154.png

AMD

2020-04-06_210022.png 使用 require.js 2020-04-06_210209.png

CommonJS

Webpack

Webpack Webpack