前端强化(六月)

330 阅读5分钟

深入this

1.this

箭头函数的绑定无法被修改(new也不行)。

/**
 * 非严格模式
 */

var name = 'window'

function Person (name) {
  this.name = name;
  this.show1 = function () {
    console.log(this.name)
  }
  this.show2 = () => console.log(this.name)
  this.show3 = function () {
    return function () {
      console.log(this.name)
    }
  }
  this.show4 = function () {
    return () => console.log(this.name)
  }
}

var personA = new Person('personA')
var personB = new Person('personB')

personA.show1()
personA.show1.call(personB)

personA.show2()
personA.show2.call(personB)

personA.show3()()
personA.show3().call(personB)
personA.show3.call(personB)()

personA.show4()()
personA.show4().call(personB)
personA.show4.call(personB)()

正确答案如下:

personA.show1() // personA,隐式绑定,调用者是 personA
personA.show1.call(personB) // personB,显式绑定,调用者是 personB

personA.show2() // personA,首先personA是new绑定,产生了新的构造函数作用域,
				// 然后是箭头函数绑定,this指向外层作用域,即personA函数作用域
personA.show2.call(personB) // personA,同上

personA.show3()() // window,默认绑定,***调用者是window***
personA.show3().call(personB) // personB,显式绑定,调用者是personB
personA.show3.call(personB)() // window,默认绑定,***调用者是window***

personA.show4()() // personA,箭头函数绑定,this指向外层作用域,即personA函数作用域
personA.show4().call(personB) // personA,箭头函数绑定,call并没有改变外层作用域,
							  // this指向外层作用域,即personA函数作用域
personA.show4.call(personB)() // personB,解析同题目1,最后是箭头函数绑定,
							  // this指向外层作用域,即改变后的person2函数作用域

2. call

foo.call(obj)(window) foo里面的this还是第一个

function foo() {
    console.log( this.a );
}

var obj = {
    a: 2
};

var bar = function() {
    foo.call( obj );
};

bar(); // 2
setTimeout( bar, 100 ); // 2

// 硬绑定的bar不可能再修改它的this
bar.call( window ); // 2

3. 隐式绑定

function foo(el) {
	console.log( el, this.id );
}

var obj = {
    id: "awesome"
}

var myArray = [1, 2, 3]
// 调用foo(..)时把this绑定到obj
myArray.forEach( foo, obj );
// 1 awesome 2 awesome 3 awesome

4. 手写一个new实现


function Person() {...}
// 使用内置函数new
var person = new Person(...)

// 使用手写的new,即create
var person = create(Person, ...)

function create() {
	// 创建一个空的对象
    var obj = new Object(),
	// 获得构造函数,arguments中去除第一个参数
    Con = [].shift.call(arguments);
	// 链接到原型,obj 可以访问到构造函数原型中的属性
    obj.__proto__ = Con.prototype;
	// 绑定 this 实现继承,obj 可以访问到构造函数中的属性
    var ret = Con.apply(obj, arguments);
	// 优先返回构造函数返回的对象
	return ret instanceof Object ? ret : obj;
};

5. ES6 call 和 apply 的模拟实现

1 call 
Function.prototype.call = function (context) {
  context = context ? Object(context) : window; 
  context.fn = this;

  let args = [...arguments].slice(1);
  let result = context.fn(...args);

  delete context.fn
  return result;
}
2 apply
Function.prototype.apply = function (context, arr) {
    context = context ? Object(context) : window; 
    context.fn = this;
  
    let result;
    if (!arr) {
        result = context.fn();
    } else {
        result = context.fn(...arr);
    }
      
    delete context.fn
    return result;
}

6. 进阶序列问题:用 JS 实现一个无限累加的函数 add

add(1); // 1 add(1)(2); // 3 add(1)(2)(3); // 6 add(1)(2)(3)(4); // 10 // 以此类推

function add(a) {
	function sum(b) { // 使用闭包
    	a = a + b; // 累加
    	return sum;
 	}
 	sum.toString = function() { // 重写toString()方法
        return a;
    }
 	return sum; // 返回一个函数
}

add(1); // 1
add(1)(2);  // 3
add(1)(2)(3); // 6
add(1)(2)(3)(4); // 10 
我们知道打印函数时会自动调用 toString()方法,函数 add(a) 返回一个闭包 sum(b),函数 sum() 中累加计算 a = a + b,只需要重写sum.toString()方法返回变量 a 就OK了。

7. ES6 中如何获取URL地址中的参数

function query(url){
  var paramsArr=url.split('?')[1].split('&')//[c=3,d=4]
  let ret={};
  paramsArr.forEach((item)=>{
    let key=item.split('=')[0];
    let val=item.split('=')[1];
    ret[key]=val;
  })
  return ret
}
 var result = query('https://shiting.com/s?c=3&d=4')
 console.log(result); //{ c: '3', d: '4' }

8. js面试题 a为何值时 输出为1

var a; //?
if(a==1&&a==2&&a==3){
  console.log(1);
}
// 剖析 只要a是对象就行 对象每进行一次比较或者拼接都会执行toString()方法
var a={
  i:1,
  toString:function(){
    return this.i++
  }
}
if (a == 1 && a == 2 && a == 3) {
  console.log(1);
}

var num=1;
var b = {
  toString: function () {
    return ++num
  }
}
console.log(b+'');//2 string
console.log(b+'');//3 string
console.log(b+1);//5 number
console.log(b==5);//true

9. bind 原生

// f.bind(obj1,2,3)(2)验证
Function.prototype.bindq=function(){
  let self = this;
  let con=[...arguments].shift();
  let args = [...arguments];
  return function(){
    return self.apply(con, args.concat([...arguments]))
  }
}


Function.prototype.bind = function () {
  let self = this,
    args = Array.from(arguments),
    context = args.shift();
    console.log(args,context);
    
  return function () {
    return self.apply(context, args.concat(...arguments))
  }
}
// 实现bind() new 

10. 手写promise

// 手写一个promise 面试够用

function myPromise(constructor){
  let self=this;
  self.status='pending';
  self.value=undefined;
  self.reason=undefined;
  function resolve(value) {
    if (self.status === 'pending'){
      self.status = 'resolved';
      self.value = value;
    }
  };
  function reject(value) {
    if (self.status === 'pending'){
      self.status = 'rejected';
      self.reason = value;
    }
  };
  try{
    // 调用构造函数
    constructor(resolve, reject)
  }catch(e){
    reject(e);
  }
}
myPromise.prototype.then = function (onFullfilled, onRejected) {
  
  switch (this.status) {
    case 'resolved':
      onFullfilled(this.value)
      break;
    case 'rejected':
      onRejected(this.reason)
      break;
  
    default:
      break;
  }
}

var p=new myPromise(function(resolve,reject){resolve(9)});
p.then(function (x) {
  console.log(x);//9
})

10. 手写防抖(Debouncing)和节流(Throttling)

scroll 事件本身会触发页面的重新渲染,同时 scroll 事件的 handler 又会被高频度的触发, 因此事件的 handler 内部不应该有复杂操作,例如 DOM 操作就不应该放在事件处理中。 针对此类高频度触发事件问题(例如页面 scroll ,屏幕 resize,监听用户输入等),有两种常用的解决方法,防抖和节流。

防抖

当一次事件发生后,事件处理器要等一定阈值的时间,如果这段时间过去后 再也没有 事件发生,就处理最后一次发生的事件。假设还差 0.01 秒就到达指定时间,这时又来了一个事件,那么之前的等待作废,需要重新再等待指定时间。最后一次触发事件

奋斗

// 防抖动函数
function debounce(fn,wait=50,immediate) {
    let timer;
    return function() {
        if(immediate) {
            fn.apply(this,arguments)
        }
        if(timer) clearTimeout(timer)
        timer = setTimeout(()=> {
            fn.apply(this,arguments)
        },wait)
    }
}

                <!--验证-->
// 简单的防抖动函数
// 实际想绑定在 scroll 事件上的 handler
function realFunc(){
    console.log("Success");
}

// 采用了防抖动
window.addEventListener('scroll',debounce(realFunc,500));
// 没采用防抖动
window.addEventListener('scroll',realFunc);


节流

固定频率调用,间隔相同

奋斗

//时间间隔
function throttle(fn,wait=50){
  // 节流
  let prev=new Date();
  return function(){
    let now=new Date();
    if(now-prev > wait){
      fn.apply(this,arguments);
      prev = new Date();
    }
  }
}
function throttle1(fn,wait=50){
  // 定时器
  let canRun=true;
  return function(){
    if (!canRun) return;
    canRun = false;
    setTimeout(() => {
       fn.apply(this, arguments);
       canRun = true;
    }, wait);
  }
}
// 简单的防节流函数
// 实际想绑定在 scroll 事件上的 handler
function realFunc() {
  console.log("Success");
}
// 采用了防节流
window.addEventListener('scroll', throttle(realFunc, 500));
// 没采用防节流
window.addEventListener('scroll', realFunc);


参考前端劝退师