1. call 和 apply 的区别是什么,哪个性能更好一些
- apply和call 的作用是一样的,区别在于传入参数的不同;
- call:第一个参数是为函数内部指定this指向,后续的参数则是函数执行时所需要的参数,一个一个传递。
- apply:第一个参数与call相同,为函数内部this指向,而函数的参数,则以数组的形式传递,作为apply第二参数。
- call 的性能更好
记忆方法:call后面的两个 l 是复数,代表参数是散的
/// call源码实现
Function.prototype.call = function (obj, ...arg) {
const context = obj || window;
const fn = Symbol(); // 保证唯一
context[fn] = this; //谁调用这个函数,this就指向谁
const ret = context[fn](...arg);
delete context[fn];
return ret;
}
// apply源码实现
Function.prototype.apply = function(obj, arg) {
const context = obj;
const fn = Symbol();
context[fn] = this;
const ret = context[fn](...arg);
delete context[fn]
return ret;
}
fn.call.call(fn2)//实际执行的是fn2
相当于 Function.prototype.call.call(fn2)
相当于 fn2.call() 即为fn2
2. 编写parse函数,实现访问对象里属性的值
let obj = { a: 1, b: { c: 2 }, d: [1, 2, 3], e: [{ f: [4, 5, 6] }] };
let r1 = parse(obj, 'a') // 1
let r2 = parse(obj, 'b.c') // 2
let r3 = parse(obj, 'd[2]') // 3
let r4 = parse(obj, 'e[0].f[0]') // 4
console.log(r1, r2, r3, r4)
// 方法1
function parse (obj, str) {
return new Function('obj', `return obj.${str}`)(obj)
}
// 方法2
function parse (obj, str) {
str = str.replace(/\[(\d+)\]/g, '.$1') //[n] -> .n
const attrs = str.split('.')
attrs.forEach(attr => {
obj = obj[attr]
})
return obj
new Function ([arg1[, arg2[, ...argN]],] functionBody)
// 创建了一个能返回两个参数和的函数
const adder = new Function("a", "b", "return a + b");
// 调用函数
adder(2, 6);
// 8
3.数组扁平化flat方法的多种实现
let arr = [
[1],
[2, 3],
[4, 5, 6, [7, 8, [9, 10, [11]]]],
12
]
// 方法1:ES6中 Array.prototype.flat()
console.log(arr.flat(Infinity))
// 方法2:toString
function flat (arr) {
return arr.toString().split(',').map(item => Number(item)) //转成字符串会只有一个逗号相隔
}
console.log(flat(arr))
// 方法3:stringify
function flat (arr) {
let str = JSON.stringify(arr)
str = str.replace(/\[|\]/g, '') //去掉中括号
return str.split(',').map(item => Number(item)))
}
console.log(flat(arr))
// 方法4:concat
function flat (arr) {
while(arr.some(item => Array.isArray(item))) {
arr = [].concat(...arr)
}
return arr
}
console.log(flat(arr))
// 方法5:递归
function flat (arr) {
let result = []
function _loop (arr) {
arr.forEach(item => {
if (Array.isArray(item)) {
_loop(item)
} else {
result.push(item)
}
})
}
_loop(arr)
return result
}
4.实现一个不可变对象
- 无论是不可扩展,密封,还是冻结,都是浅层控制的,即只控制对象本身属性的增删改。如果对象属性是一个引用类型,比如数组 subArr 或对象 subObj等,虽然subArr、subObj 的不可被删改,但subArr、subObj 的属性仍然可增删改
- 由于每个对象都有一个属性proto,该属性的值是该对象的原型对象,也是引用类型,由于冻结是浅层的所以原型对象并不会被连着冻结,仍然可以通过给对象的原型对象加属性达到给当前对象新增属性的效果。所以如果想进一步冻结还需要把原型对象也冻结上
不可扩展
Object.preventExtensions()
可以使一个对象不可再添加新的属性,参数为目标对象,返回修改后的对象 ```javascript var obj = { name: ‘zhufeng’ }; console.log(Object.isExtensible(obj));// true Object.preventExtensions(obj); console.log(Object.isExtensible(obj)); // false
//Object.defineProperty(obj, ‘age’, {value: 10}); //TypeError: Cannot define property age, object is not extensible obj.age = 10; console.log(obj.age); // undefined
<a name="E2cek"></a>
#### 密封
- Object.seal() 可以使一个对象无法添加新属性的同时,也无法删除旧属性。参数是目标对象,返回修改后的对象
- 其本质是通过修改属性的 configurable 为 false 来实现的
- configurable 为 false 时,其他配置不可改变,writable 只能 true 变 false,且属性无法被删除。而由于只要 writable 或 configurable 其中之一为 true,则 value 可改,所以密封之后的对象还是可以改属性值的
- Object.isSealed() 可以检测一个对象是否密封,即是否可以增删属性。参数是目标对象,返回布尔值,true 代表被密封不可增删属性,false 代表没被密封可增删属性
```javascript
var obj = new Object();
Object.isExtensible(obj); // true
Object.isSealed(obj); // false
Object.seal(obj);
Object.isExtensible(obj); // false,注意 seal 后对象的 isExtensible() 也随之改变
Object.isSealed(obj); // true
var obj = { name: 'zhufeng' };
console.log(Object.getOwnPropertyDescriptor(obj, 'name'));
/**
{
value: 'zhufeng',
writable: true,
enumerable: true,
configurable: true
}
*/
Object.seal(obj);
console.log(Object.getOwnPropertyDescriptor(obj, 'name')); // seal 后 configurable 变为 false
/**
{
value: 'zhufeng',
writable: true,
enumerable: true,
configurable: false
}
*/
冻结
- Object.freeze() 可以使对象一个对象不能再添加新属性,也不可以删除旧属性,且不能修改属性的值。参数是目标对象,返回修改后的对象。
- Object.isFrozen() 可以检测一个对象是否冻结,即是否可以增删改。参数是目标对象,返回布尔值,true 表示已经冻结不可再增删改,false 反之 ```javascript var obj = new Object(); Object.isExtensible(obj); // true Object.isSealed(obj); // false Object.isFrozen(obj); // false Object.freeze(obj); Object.isExtensible(obj); // false,注意 freeze 后对象的 isExtensible() 也随之改变 Object.isSealed(obj); // true,注意 freeze 后对象的 isSealed() 也随之改变 Object.isFrozen(obj); // true var obj = Object.freeze({ name: ‘zhufeng’ });
// 直接定义新的属性会报错 Object.defineProperty(obj, ‘name’, { value: ‘zhufeng’ });
obj.name = ‘zhufeng’; obj.name; // undefined
delete obj.name; // 删除失败,返回 false
obj.name = ‘jiagou’; obj.name; // 仍然是 “zhufeng”
- [ ] 深冻结 (类似深拷贝)
<a name="AuJ12"></a>
## 5. +号
- 两个操作数如果是number则直接相加出结果
- 如果其中有一个操作数为string,则将另一个操作数隐式的转换为string,然后进行字符串拼接得出结果
- 如果操作数为对象或者是数组这种复杂的数据类型,那么就将两个操作数都转换为字符串,进行拼接
- 如果操作数是像boolean这种的简单数据类型,那么就将操作数转换为number相加得出结果
- [ ] + { } 因为[]会被强制转换为"", 然后+运算符 链接一个{ }, { }强制转换为字符串就是"[object Object]"
- { } 当作一个空代码块,`+[]`是强制将[]转换为number,转换的过程是 +[] => +"" =>0 最终的结果就是0
```javascript
[]+{} // [object Object] []转为"",{}转为[object Object]
{}+[] // +[]强制转为number +[] -> +'' -> 0 最终结果为0 {}为代码块
{}+0 //0
[]+0 //"0 "
[].toString() //0
6.柯里化
1.bind
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
// 使用举例
(function (prototype) {
function bind(context = global, ...outerArgs) {
return (...innerArgs) => {
return this.call(context, ...outerArgs, ...innerArgs);
}
}
prototype.bind = bind;
})(Function.prototype);
function sum(...args) {
return this.prefix + args.reduce((acc, curr) => acc + curr, 0);
}
let obj = { prefix: '$' };
let bindSum = sum.bind(obj, 1, 2, 3);
console.log(bindSum(4, 5)); //$15
// bind实现
Function.prototype.bind = function (context, ...args) {
const fn = Symbol('fn')
context[fn] = this
return function (...others) {
const result = context[fn](...args, ...others)
delete context[fn]
return result
}
}
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype.toString = function () {
return this.x + ',' + this.y;
}
let YPoint = Point.bind(null, 1);
let axiosPoint = new YPoint(2);
console.log(axiosPoint.toString()) // '1,2'
console.log(axiosPoint instanceof Point) // true
console.log(axiosPoint instanceof YPoint) // tue
上面的bind实现是不支持new的
Function.prototype.bind = function(context,..outerAgrs){
let thatFunc = this; //缓存当前的函数
let fBound = function(...innerAgrs){
//如果在这里判断我这个函数是new来调用的还是直接调用
return thatFunc.apply(
// 如果是在new 这绑定的后函数的话,则bind绑定的时候传的context就没有用了
this instanceof thatFunc ? this : context, [...outerAgrs,...innderArgs]
);
};
fBound.prototype = Object.create(thatFunc.prototype); // 不能直接=,会污染原型对象
return fBound;
}
2.add
add(1); // 1
add(1)(2); // 3
add(1)(2)(3); // 6
add(1)(2,3); // 6
add(1,2)(3); // 6
add(1,2,3); // 6
// 第一写法
const add = (function (total) {
let allArgs = [];
function _add(...args) {
allArgs = [...allArgs, ...args];
if (allArgs.length >= total) {
let ret = allArgs.reduce((pre, cur) => pre + cur, 0);
allArgs.length = 0; // 要清空数组
return ret;
} else {
return _add
}
}
return _add
})(5);
// 第二种写法
function add(..args){
let _add = add.bind(null,...args);
_add.toString = function(){
return args.reduce((pre, cur) => pre + cur, 0);
}
return _add();
}
函数柯里化就是把接受多个参数的函数变换成接受一个单一参数的函数,并且返回接受余下参数返回结果的技术。
function curry(fn,...args){
return args.length < fn.length ? (...innerArgs)=>{ // fn.length表示形参的数量
return curry(fn,..args,...innerArgs);
} : fn(...args)
}
7.拷贝
1 JSON.parse
无法支持所有类型,比如函数
let obj = { name: 'zhufeng', age: 10 };
console.log(JSON.parse(JSON.stringify(obj)));
2 浅拷贝
function clone(source) {
let target = {};
for (const key in source) {
target[key] = source[key];
}
return target;
};
3 深拷贝
支持对象和数组 ```javascript let obj = { name: ‘zhufeng’, age: 10, home: { name: ‘北京’ }, hobbies: [‘抽烟’, ‘喝酒’, ‘烫头’] }; function clone(source) { if (typeof source === ‘object’) {
let target = Array.isArray(source) ? [] : {};
for (const key in source) {
target[key] = clone(source[key]);
}
return target;
} return source;
}; let cloned = clone(obj); console.log(Array.isArray(cloned.hobbies));
<a name="a6HO2"></a>
### 4 循环引用
```javascript
let obj = {
name: 'zhufeng',
age: 10,
home: { name: '北京' },
hobbies: ['抽烟', '喝酒', '烫头']
};
obj.obj = obj;
function clone(source, map = new Map()) {
if (typeof source === 'object') {
if (map.get(source)) {
return map.get(source);
}
let target = Array.isArray(source) ? [] : {};
map.set(source, target);
for (const key in source) {
target[key] = clone(source[key], map);
}
return target;
}
return source;
};
let cloned = clone(obj);
console.log(cloned.obj);
6 精确类型
6.1 判断类型方式
- typeof
- 返回结果都是字符串
- 字符串中包含了对应的数据类型 number string boolean undefined symbol
- typeof null == ‘object’
- typeof {} [] /&$/ Date== ‘object’
- instanceof
- Object.prototype.toString.call ```javascript let obj = { married: true, age: 10, name: ‘zhufeng’, girlfriend: null, boyfriend: undefined, flag: Symbol(‘man’), home: { name: ‘北京’ }, set: new Set(), map: new Map(), getName: function () { }, hobbies: [‘抽烟’, ‘喝酒’, ‘烫头’], error: new Error(‘error’), pattern: /^regexp$/ig, math: Math, json: JSON, document: document, window: window }; obj.set.add(1); obj.map.set(‘name’, ‘value’); obj.obj = obj;
let OBJECT_TYPES = [{}, [], new Map(), new Set(), new Error(), new Date(), /^$/].map(item => getType(item)); const MAP_TYPE = getType(new Map()); const SET_TYPE = getType(new Set()); const CONSTRUCT_TYPE = [new Error(), new Date()].map(item => getType(item)); const SYMBOL_TYPE = getType(Symbol(‘1’)); const REGEXP_TYPE = getType(/^$/); function clone(source, map = new Map()) { let type = getType(source); if (!OBJECT_TYPES.includes(type)) {//基本数据类型 return source; } if (map.get(source)) { return map.get(source); } if (CONSTRUCT_TYPE.includes(type)) { return new source.constructor(source); } let target = new source.constructor(); map.set(source, target);
if (SYMBOL_TYPE === type) {
return Object(Symbol.prototype.valueOf.call(source));
}
if (REGEXP_TYPE === type) {
const flags = /\w*$/;
const target = new source.constructor(source.source, flags.exec(source));
target.lastIndex = source.lastIndex;
return target;
}
if (SET_TYPE === type) {
source.forEach(value => {
target.add(clone(value, map));
});
return target;
}
if (MAP_TYPE === type) {
source.forEach((value, key) => {
target.set(key, clone(value, map));
});
return target;
}
let keys = Object.keys(source);
let length = keys.length;
let index = 0;
while (index < length) {
target[keys[index]] = clone(source[keys[index]], map);
index++;
}
return target;
};
function getType(source) { return Object.prototype.toString.call(source); } let cloned = clone(obj); console.log(cloned); console.log(obj.home === cloned.home); console.log(obj.set === cloned.set); console.log(obj.map === cloned.map); / [object Boolean] [object Number] [object String] [object Null] [object Undefined] [object Symbol] [object Object] [object Function] [object Array] [object Error] [object RegExp] [object Math] [object JSON] [object HTMLDocument] [object Window]” / ```