专栏首页多云转晴JavaScript 笔试题(三)

JavaScript 笔试题(三)

1. 引用类型

一个经典的例题:

var a = { n: 1 };   // 1
var b = a;          // 2
a.x = a = { n: 2 };     // 3

console.log(a.x);   // ?
console.log(b);     // ?

答案:a.x 是 undefined;b 是 {n: 1, x: {n: 2}}

首先,a 是对象,是个引用类型,存储的指针指向 {n: 1} 这个对象。

第二行把指针 a 赋给 b,a 和 b 都指向 {n: 1};

第三行,首先会对 a.x 进行查找,没有找到就会先赋值 undefined,即:{n: 1, x: undefined}。此时 a 和 b 都指向同一个对象。然后 a 变量又赋值成一个新的对象:{n: 2},最后把新的 a 赋值给 x(前面的 a. 已经被替换成了原来的 a 所指向的那个内存中的对象),x 就有值了,b 就变成了:

{
    n: 1,
    x: { n: 2 }
}

这个题目最大的疑惑可能就是在第三行,a 变量的指向已经变了,而 a.x 会误认为新的 a 中有变量 x 并且它指向 a。事实上在执行第三行代码时,先处理了 a.x,让 x 先等于 undefined(属性访问优先级要比赋值优先级高),我觉得可以把此时的 a.x 看作是一个整体,它是旧的 a 指向的对象中的一个变量,正在等待赋值,当赋值时,这个变量指向 {n:2}。

2. 判断对象类型

因为 typeof null === "object" 成立,而 null 并不是对象。

function isObject(obj){
    return Object.prototype.toString.call(obj) === "[object Object]";
}

Object.prototype.toString.call(null) 的结果是 [object Null]

或者(这种方式不好的一点是不能过滤掉 Date、Array 等类型的数据):

function isObject(obj){
    return typeof obj === "object" && obj !== null;
}

3. isInteger 函数

封装一个 isInteger 函数,用于检测传入的值是整数。

function isInteger(num){
    return typeof num === "number"
                    && isFinite(num)
                    && value % 1 === 0;
}

上面代码中,isFinite 是一个全局的函数,用来判断一个数字是不是有限的,例如:

// 下面三个都是无限的
isFinite(Infinity);  // false
isFinite(NaN);       // false
isFinite(-Infinity); // false

isFinite(0);     // true
// 对于非数值的参数会转换成数值
isFinite("0");  // true

Number 对象中也有一个 isFinite 函数,与全局的 isFinite 不同的是:它不会强制将一个非数值的参数转换成数值,这就意味着,只有数值类型的值,且是有穷的。

4. isNaN2 函数

isNaN 用来判断传入的参数是不是 NaN,是就返回 true。它的 polyfill 如下:

function isNaN(value){
    var n = Number(value);
    // NaN !== NaN 将返回 true,它是一个自身不等于自身的值
    return n !== n;
}

isNaN 的不足:如果它的参数既不是 NaN,也不是数字,而是一个其他的类型变量,例如:一个字符串,这个字符串不能转化成数字,返回的结果不是 false,而是 true。我们要做的是传入参数的是 NaN 才被判定是 true,不然就返回 false。代码如下:

function isNaN2(value){
    return typeof value === 'number' && isNaN(value);
}

typeof NaN 的结果是 number

5. 小数相乘

编写一个函数,能让两个并不大的小数正确相乘。

在 JavaScript 这门语言中的 Number 类型是IEEE 754的双精度数值,IEEE 754 标准就是一个对实数进行计算机编码的标准。这个标准在进行小数运算时精度可能会有不足,使用了 IEEE 754 标准的语言进行小数运算时会出现精度问题,这种问题不止 JS 这门语言独有。

例如下面的运算并没有得到预期的结果:

15.2 * 3.5  // 结果:53.199999999999996,期望结果:53.2
0.1 + 0.2   // 结果:0.30000000000000004,期望结果:0.3

回到问题,编写一个 accmul 函数,让两个并不大的小数正确相乘。代码如下:

function accMul(n1, n2){
    var m = 0,
        s1 = n1.toString(),
        s2 = n2.toString();

    m += s1.split(".")[1].length;
    m += s2.split(".")[1].length;
    var result = Number(s1.replace(".", "")) * Number(s2.replace(".", ""));
    result = result / Math.pow(10, m);
    return result;
}

如果 n1n2 都是小数,先用 toString 将两个小数转成字符串,调用 split 方法分隔整数部分与小数部分,然后拿到小数部分的长度,相乘后的结果的小数位数等于相乘前两个小数的小数位数相加。result 是两个小数转成整数后相乘,再根据总的小数长度得到最终的运算结果。

6. 属性继承

如何判断对象中的某个属性是继承来的?

在 js 对象中,使用对象里的某个属性或者方法时,这个属性或者方法不一定存在于这个对象当中,也可能是继承来的。例如:

var obj = {};   // 该对象没有任何属性和方法
console.log(obj.toString());    // 却可以调用 toString 方法

如何判断对象中的某个属性是继承来的?可以利用 in 运算符和 hasOwnProperty 方法来判断。

  • in 运算符可以判断属性是否在指定的对象或其原型链中;
  • hasOwnProperty 方法可以判断对象自身属性中是否具有指定的属性;

代码如下:

function isInherit(prop, object){
    return prop in object && !object.hasOwnProperty(prop);
}

7. 实现 instanceof 功能

instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。例如:

function C(){}
var o = new C();
o instanceof C;     // true
o instanceof Object;    // true
function D(){}
o instanceof D;     // false

实现:

function myInstanceof(object, constructor){
    let proto = Object.getPrototypeOf(object);
    let prototype = constructor.prototype;
    while(true){
        // 原型链的最上层是 null
        if(proto === null)  return false;
        if(proto === prototype) return true;
        proto = Object.getPrototypeOf(proto);
    }
}

getPrototypeOf 方法可以返回指定对象的原型,相当于 object.__proto__。如果没有继承,则返回 null

Object.prototype.proto === null // true

8. ES5 继承

常见的一种是“借用构造函数”,例如:

function Person (a, b) {
    this.a = a;
    this.b = b;
}
Person.prototype.getA = function(){
    return this.a;
}

function Sub (a, b) {
    // 执行 Persion,让其内部的 this 指向 Sub 的实例
    Person.call(this, a, b);
}
// 把 Sub 的原型指向 Person 的实例,这样 Sub 就可以用到 Person 原型上的属性或方法了
Sub.prototype = new Person;
// 把构造函数改回来
Sub.prototype.constructor = Sub;

这种方式会调用 Person 函数两次,另一种常用的方式是“寄生组合式”继承。通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。例如:

function Person (a, b) {
    this.a = a;
    this.b = b;
}
Person.prototype.getA = function(){
    return this.a;
}

function Sub (a, b) {
    Person.call(this, a, b);
}

// 专门用于继承的函数
function inherit (P, C) {
    // P 是父类,C 是子类
    // 调用 create 方法后,prototype.__proto__ === P.prototype
    var prototype = Object.create(P.prototype);
    prototype.constructor = C;
    C.prototype = prototype;
}
inherit(Person, Sub);
// Sub 的实例对象的 __proto__ 是 Sub.prototype;
// 而 Sub.prototype 是空的,它只有一个 constructor,指向 Sub;
// Sub.prototype.__proto__ 指向 P.prototype

9. 实现 reduce 函数

reduce 是 ES6 中数组的一个方法,它可以接收两个参数,一个是回调函数,一个是初始值(可选参数)。回调函数有四个参数:

  • accumulator 累计器累计回调的返回值;
  • currentValue 数组中正在处理的元素;
  • index 数组中正在处理的当前元素的索引,可选参数;
  • array 调用 reduce 的数组,可选参数;

reduce 如果没有第二参数,将使用数组中的第一个元素作为初始值,在没有初始值的空数组上调用 reduce 将报错。

比如下面的例子,求数组值的总和并加一。

let res = [1,2,3,4].reduce((acc, elem) => {
    acc += elem;    // 每次累加器都等于前一个累加器加上当前的数组元素
    return acc;     // 返回累加器,确保下一次迭代继续使用
},1);   // 累加器 acc 的初始值设置成 1
console.log(res);   // 11

实现

function reduce (ary, cb, initialVal) {
    if(Object.prototype.toString.call(ary) !== '[object Array]')
        throw new TypeError(ary + ' is not an Array.');
    if(typeof cb !== 'function')
        throw new TypeError(cb + ' is not a function.');

    var len = ary.length;
    var value, idx = 0;
    if(initialVal === undefined) {
        if(!len)
            // 空数组,没有初始值时 报错
            throw new Error('Reduce of empty array with no initial value.');
        // 不是空数组,累加器初始值设置成数组的第一个元素
        value = ary[idx ++];
    }else
        // 否则,初始值设置成 initialVal
        value = initialVal;

    while(idx < len) {
        // 迭代
        value = cb(value, ary[idx], idx, ary);
        idx ++;
    }
    return value;
}

10. 实现 filter 函数

实现如下:

function filter (ary, cb, thisArg) {
    if(Object.prototype.toString.call(ary) !== '[object Array]')
        throw new TypeError(ary + ' is not an Array.');
    if(typeof cb !== 'function')
        throw new TypeError(cb + ' is not a function.');

    var len = ary.length,
        res = [],
        idx = 0;

    if (thisArg === undefined) {
        // 如果没有传入第三个参数
        while (idx < len) {
            if(cb(ary[idx], idx, ary)) {
                res.push(ary[idx]);
            }
            idx ++;
        }
    } else {
        while (idx < len) {
            // 传入了第三个参数就要给 cb 绑定 this
            if( cb.call(thisArg, ary[idx], idx, ary)) {
                res.push(ary[idx]);
            }
            idx ++;
        }
    }
    return res;
}

验证:

var obj = { num: 3 };
// 绑定 this
let res = filter([4,7,9,11,44,16], function (elem) {
    return elem % this.num === 1;
}, obj);
console.log(res);   // [4, 7, 16]

需要注意的是,如果绑定 this,最好不要使用箭头函数。因为给箭头函数绑定 this 会被忽略。

相关文章:

JavaScript笔试题(一)

JavaScript笔试题(二)

JavaScript严格模式

本文分享自微信公众号 - Neptune丶(Neptune_mh_0110),作者:多云转晴

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-06-28

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • JavaScript 笔试题

    ... 是 ES6 中的扩展运算符,相当于:push(1, 2, 3); push 方法返回的是该数组的新长度。因此是 3。Array 中的各个方法的返回值都是...

    多云转晴
  • JavaScript 笔试题(二)

    上面代码中我们该判断了 result 的类型,在原生的 new 关键字上,如果你返回了一个对象,则接收时接收的会是这个对象,例如:

    多云转晴
  • JavaScript笔记(三)

    两个数字相加,返回数字相加的和;两个字符串相加,变量会连接成一个字符串;如果数字与字符串相加,返回字符串。

    木尤
  • 【Linux运维面试题】三剑客笔试题集合

    1.在给定文件中查找与条件相符字符串的命令及查找某个目录下相应文件的命令为:(多选)(AC)

    kubernetes中文社区
  • JavaScript学习笔记(三)

    运行期环境是由宿主环境通过脚本引擎创建的,实际上是由JavaScript引擎创建的一个代码解析初始化环境。

    wsuo
  • JavaScript笔记总结(三)

    两个数字相加,返回数字相加的和;两个字符串相加,变量会连接成一个字符串;如果数字与字符串相加,返回字符串。

    宸寰客
  • 嵌入式笔试面试题目系列(三)

    本系列将按照类别对题目进行分类整理,重要的地方标上星星,这样有利于大家打下坚实的基础。

    Jasonangel
  • JavaScript面试题

    Dreamy.TZK
  • JavaScript面试题

    挨踢小子部落阁

扫码关注云+社区

领取腾讯云代金券