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 就变成了:
{
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 等类型的数据):
代码语言: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
是一个全局的函数,用来判断一个数字是不是有限的,例如:
// 下面三个都是无限的
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 这门语言独有。
例如下面的运算并没有得到预期的结果:
代码语言:javascript复制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;
}
如果 n1
与 n2
都是小数,先用 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
属性是否出现在某个实例对象的原型链上。例如:
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
函数两次,另一种常用的方式是“寄生组合式”继承。通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。例如:
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严格模式