深入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);
参考: 前端劝退师