(1)new运算符的作用是什么?
- 创建一个空对象
- 由this变量引用该对象
- 该对象继承该函数的原型(更改原型链的指向)
- 把属性和方法加入到this引用的对象中
(2)模拟new运算符
function objectFactory(){
const obj = new Object();
const Constructor =[].shift.call(arguments);
obj.__proto__ = Constructor.prototype;
const ret = Constructor.apply(obj, arguments);
return typeof ret ==="object"?ret:obj;
}
(3) call和apply的区别和作用?
作用都是在函数执行的时候,动态改变函数的运行环境(执行上下文)
call和apply的第一个参数是改变运行环境的对象
区别:call从第二个参数开始,每一个参数都会依次传递给调用函数;
apply的第二个参数是数组
func.call(obj,var1,var2,var3);
func.apply(obj,[var1,var2,var3]);
(4)JS中callee和caller的作用是什么?
caller返回一个关于函数的引用,该函数调用了当前函数;callee返回正在执行的函数,也就是指定的function对象的正文。
(5)如果一对兔子每月生一对小兔子,一对新兔从第二月期就开始生小兔子,同时假定每对兔子都是一雌一雄,试问一对兔子在第n个月能繁殖多少对兔子(使用callee完成)?
分析:这是一个典型的斐波那契数列
function getRabbitNum(num){
var result = [];
function fn(n){
if(n<=2){
result[n]=1;
return 1;
}else{
if(result[n]){
return result[n];
}else{
result[n]=arguments.callee(n-1)+arguments.callee(n-2);
return result[n];
}
}
};
fn(num);
return result;
}
console.log(getRabbitNum(10));
(6)call(),apply()及bind的作用及区别
作用:call、apply及bind都是改变函数执行的上下文,改变了this的指向
区别: call及apply改变了函数的this,并且执行了该函数,而bind是改变了函数的this指向,不执行该函数
// 案例 call
var doThu = function(a,b){
console.log(this);
console.log(this.name);
console.log([a,b]);
}
//在stu上添加一个属性doThu,再执行这个函数,就将doThu的this指向了stu
var stu={
name:'xiaoming',
doThu:doThu
}
//只不过call为stu添加了doThu方法后,执行了doThu,然后再将doThu这个方法从stu中删除。
var stu={
name:'xiaoming',
}
doThu.call(stu,1,2);//stu对象 xiaoming [1,2]
//使用call方法调用匿名函数
var animals =[{specis:'Lion',name:'King'},{specis:'Whale',name:'Fail'}];
for(var i=0;i<animals.length;i++){
(function(i){
this.print=function(){
console.log(`#${i} ${this.specis}:${this.name}`);//#0 Lion:King #1 Whale:Fail
}
this.print();
}).call(animals[i],i);
}
Note:使用 call 方法调用函数并且不指定第一个参数(argument) 这时候this的值会被绑定为全局对象,在严格模式下,this 的值将会是 undefined;
call 函数的内部实现原理
// call 函数的内部实现原理
Function.prototype.call = function(thisArg,args){
if(typeof this !=='function'){
// 调用call的若不是函数则报错
throw new TypeError('error');
}
thisArg = thisArg ||Window;
thisArg.fn = this; // 将调用call函数的对象添加到thisArg的属性中
/* slice()方法返回一个新的数组对象,
这一对象是一个由 begin 和 end 决定的原数组的浅拷贝(包括 begin,不包括end)。
原始数组不会被改变。 */
const result = thisArg.fn(...[...arguments].slice(1));
delete thisArg.fn; //删除该属性
return result;
}
bind的实现原理比call和apply要复杂一些,bind中需要考虑一些复杂的边界条件。
bind后的函数会返回一个函数,而这个函数也可能被用来实例化:
Function.prototype.bind = function(thisArg){
if(typeof this !=='function'){
throw new TypeError("not a function");
}
//存储函数本身
const _this = this;
//取出thisArg的其他参数,转为数组
const args =[...arguments].slice(1);
const bound = function(){
//可能返回一个构造函数,我们可以new F(),所以需要判断
if(this instanceof bound){
return new _this(...args,...arguments);
}
// apply修改this的指向 把两个函数的参数合并 传给thisArg函数
//并执行thisArg函数,返回执行结果
return _this.apply(thisArg, args.concat(...arguments));
}
return bound;
}
(7)JS延迟加载的方式有哪些?
包括defer和async、动态创建DOM(创建script,插入DOM中,加载完毕后回调,按需异步载入JavaScript)
(8)哪些操作会造成内存泄露
内存泄露指的是不再拥有或需要任何对象(数据)之后,它们仍然存在内存中。
如果setTimeout()的一个参数为字符串而非函数,会引起内存泄露
闭包、控制台日志、循环(两个对象彼此引用且彼此保留时,就会产生一个循环)等造成内存泄露
(9)模拟Object.create()方法创建对象,使用现有的对象来提供新创建的对象的proto
function create(proto){
function F(){};
F.prototype = proto;
return new F();
}
(10)如何实现异步编程?
- 通过回调函数的方式
优点:简单、容易理解和部署
缺点:不利于代码的阅读和维护,各个部分之间高度耦合,流程混乱,而且每个任务只能指定一个回调函数。
- 通过事件监听。可以绑定多个事件
优点:每个事件可以指定多个回调函数,而且可以去耦合,有利于实现模块化
缺点:整个程序会变成事件驱动型,运行流程会变得很不清晰
- 采用发布/订阅方式。性质与事件监听相似,但是明显优于后者
- 通过Promise对象实现。Promise对象是CommonJS工作组提出的一种规范,旨在为异步编程提供统一接口。它的思想是,每一个异步任务返回一个promise对象,该对象有个then方法,允许指定回调函数