1.call、apply 和 bind

       面试题:call、apply 及 bind 函数内部实现是怎么样的?

  • 定义:调用一个对象的一个方法,用另一个对象代替当前对象
  • 使用:B.call(A, args1, args2),B.apply(A, arguments),A 对象应用 B 对象的方法

       实现 call 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Function.prototype.myCall = function(context) {
if (typeof this !== 'function') {
throw new TypeError('Error');
}
context = context || window; // 可选参数,不传默认为window
context.fn = this; //创建fn,此时的this是需要调用的函数,本例中是add()
const args = [...arguments].slice(1); //剥离参数
const result = context.fn(...args); //调用函数
delete context.fn; //将对象上的函数删除
return result;
}
function add(a,b){
return a+b;
}
function sub(a,b){
return a-b;
}
var a1 = add.myCall(sub,4,2); //6,sub调用add的方法
  • bind()是 ES5 中的方法,bind 后函数不会立即执行,只是返回一个改变了上下文的函数副本,call 和 apply 是直接执行函数,ie6~ie8 不支持该方法
           实现 bind 方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Function.prototype.myBind = function (context) {
if (typeof this !== 'function') {
throw new TypeError('Error');
}
const _this = this;
const args = [...arguments].slice(1); // argument是类数组,先转成数组再去掉context
// 返回一个函数
return function F() {
// 判断是否是通过new调用函数的,不会被任何方式改变this
if (this instanceof F) {
return new _this(...args, ...arguments);
}
// 因为bind可以实现f.bind(obj, 1)(2)这样的代码,所以需要将参数拼接起来
return _this.apply(context, args.concat(...arguments)); // 直接调用函数
}
}

2.new

       面试题:new 的原理是什么?通过 new 的方式创建对象和通过字面量创建有什么区别?

       通过 new 来调用构造函数时,函数体内会发生:

  1. 创建一个空对象,将它的引用赋给 this,并继承函数的原型
  2. 通过 this 将属性和方法添加至这个对象
  3. 最后返回 this 指向的新对象
           自己实现一个 new
1
2
3
4
5
6
7
8
9
10
11
12
function create() {
let obj = {};
let Con = [...arguments].shift(); // 获取构造函数,例子里是t()函数
obj.__proto__ = Con.prototype; // 设置空对象的原型,例子里是t()函数的prototype
let result = Con.apply(obj, arguments); // 绑定this,执行构造函数
return result instanceof Object ? result : obj; //确保返回值是对象
}
function t() {
console.log(1);
}
let a = create(t);
a; // 打印出1

       字面量优势:

  1. 代码量更少容易阅读
  2. 可以强调对象是一个简单的可变的散列表,不必一定派生自某个类
  3. 对象字面量可以在解析时被优化,不需要“作用域解析”,因为存在你已经创建了一个同名的构造函数 Object()的可能,当调用 Object()的时候,解析器需要顺着作用域链从当前作用域开始查找,找到全局 Object()构造函数为止
  4. Object()可以接收参数,通过这个参数可以把对象实例的创建过程委托给另一个内置构造函数,并返回另一个对象实例,往往不是我们想要的结果
1
2
3
4
5
6
// 字符串对象
var o = new Object("I am a string");
console.log(o.constructor === String); // true
// 普通对象没有substring()方法,但字符串对象有
// 最终得到的对象是不同的构造函数生成的
console.log(typeof o.substring); // "function"

3.instanceof

       面试题:instanceof 的原理是什么?

       instanceof 可以正确的判断对象的类型,因为内部机制是通过判断对象的原型链中是不是能找到类型的 prototype。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function myInstanceof(left, right) {
let prototype = right.prototype; // 获取类型的原型
left = left.__proto__; // 获得对象的原型
while (true) {
if (left === null || left === undefined)
return false; // 原型链最终为null,所以搜索到对象原型为null为止
if (prototype === left)
return true;
left = left.__proto__; // 向原型链上一层查找
}
}
function a() {}
myInstanceof(a,Function); // true
let b = [1,2,3]
myInstanceof(b,Array); // true

3.数字精度问题

       面试题:为什么 0.1 + 0.2 != 0.3?如何解决这个问题?

        0.1 在二进制中是无限循环的一些数字,不只是 0.1,JS 采用的 IEEE 754 双精度版本(64 位)的浮点数标准会裁减掉数字,0.1 就会变成 0.100000000000000002,同样的 0.2 在二进制中也是无限循环的,所以相加不是 0.3。
        为什么 console.log(0.1)是正确的?因为在输入内容的时候二进制转换为了十进制,然后再转为字符串,这个转换的过程中取了近似值。
       做计算时可以调用如下的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
floatAdd: function(arg1, arg2) {
var r1, r2, m;
try {
r1 = ('' + arg1).split('.')[1].length;
} catch (e) {
r1 = 0;
}
try {
r2 = ('' + arg2).split('.')[1].length;
} catch (e) {
r2 = 0;
}
m = Math.pow(10, Math.max(r1, r2));
return (arg1 * m + arg2 * m) / m;
},

floatSub: function(arg1, arg2) {
var r1, r2, m, n;
try {
r1 = ('' + arg1).split('.')[1].length;
} catch (e) {
r1 = 0;
}
try {
r2 = ('' + arg2).split('.')[1].length;
} catch (e) {
r2 = 0;
}
m = Math.pow(10, Math.max(r1, r2));
//动态控制精度长度
n = r1 >= r2 ? r1 : r2;
return ((arg1 * m - arg2 * m) / m).toFixed(n);
},

//浮点数乘法运算
floatMul: function(arg1, arg2) {
var m = 0,
s1 = '' + arg1,
s2 = '' + arg2;

try {
m += s1.split('.')[1].length;
} catch (e) {}
try {
m += s2.split('.')[1].length;
} catch (e) {}
return (
Number(s1.replace('.', '')) *
Number(s2.replace('.', '')) /
Math.pow(10, m)
);
},

//浮点数除法运算
floatDiv: function(arg1, arg2) {
var t1 = 0,
t2 = 0,
r1,
r2;
try {
t1 = ('' + arg1).split('.')[1].length;
} catch (e) {}
try {
t2 = ('' + arg2).split('.')[1].length;
} catch (e) {}

r1 = Number(('' + arg1).replace('.', ''));
r2 = Number(('' + arg2).replace('.', ''));
return r1 / r2 * Math.pow(10, t2 - t1);
}