前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JavaScript 笔试题(三)

JavaScript 笔试题(三)

作者头像
多云转晴
发布2020-06-30 10:15:11
7380
发布2020-06-30 10:15:11
举报
文章被收录于专栏:webTowerwebTower

1. 引用类型

一个经典的例题:

代码语言:javascript
复制
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 就变成了:

代码语言:javascript
复制
{
    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 并不是对象。

代码语言:javascript
复制
function isObject(obj){
    return Object.prototype.toString.call(obj) === "[object Object]";
}

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

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

代码语言:javascript
复制
function isObject(obj){
    return typeof obj === "object" && obj !== null;
}

3. isInteger 函数

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

代码语言:javascript
复制
function isInteger(num){
    return typeof num === "number"
                    && isFinite(num)
                    && value % 1 === 0;
}

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

代码语言:javascript
复制
// 下面三个都是无限的
isFinite(Infinity);  // false
isFinite(NaN);       // false
isFinite(-Infinity); // false

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

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

4. isNaN2 函数

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

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

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

代码语言:javascript
复制
function isNaN2(value){
    return typeof value === 'number' && isNaN(value);
}

typeof NaN 的结果是 number

5. 小数相乘

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

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

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

代码语言:javascript
复制
15.2 * 3.5  // 结果:53.199999999999996,期望结果:53.2
0.1 + 0.2   // 结果:0.30000000000000004,期望结果:0.3

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

代码语言:javascript
复制
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 对象中,使用对象里的某个属性或者方法时,这个属性或者方法不一定存在于这个对象当中,也可能是继承来的。例如:

代码语言:javascript
复制
var obj = {};   // 该对象没有任何属性和方法
console.log(obj.toString());    // 却可以调用 toString 方法

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

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

代码如下:

代码语言:javascript
复制
function isInherit(prop, object){
    return prop in object && !object.hasOwnProperty(prop);
}

7. 实现 instanceof 功能

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

代码语言:javascript
复制
function C(){}
var o = new C();
o instanceof C;     // true
o instanceof Object;    // true
function D(){}
o instanceof D;     // false

实现:

代码语言:javascript
复制
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 继承

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

代码语言:javascript
复制
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 函数两次,另一种常用的方式是“寄生组合式”继承。通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。例如:

代码语言:javascript
复制
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 将报错。

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

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

实现

代码语言:javascript
复制
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 函数

实现如下:

代码语言:javascript
复制
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;
}

验证:

代码语言:javascript
复制
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严格模式

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-06-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 WebTower 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 引用类型
  • 2. 判断对象类型
  • 3. isInteger 函数
  • 4. isNaN2 函数
  • 5. 小数相乘
  • 6. 属性继承
  • 7. 实现 instanceof 功能
  • 8. ES5 继承
  • 9. 实现 reduce 函数
    • 实现
    • 10. 实现 filter 函数
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档