前面系列即为前端面试系列(Front-end interview series), 主要内容是一些前端面试中常常被问到的题.html
系列问答中没有繁琐的讲解过程, 力求保证面试者给予面试官一个简洁、具备重点的答案, 因此适合于有必定知识基础的前端童鞋👨🎓. 固然, 在每题的最后我也会贴上关于这一章节比较好文章, 以供你们更好的理解所提到的知识点.前端
请认准github地址: LinDaiDai-FInode
this
会绑定到
undefined
)
obj.foo()
的调用方式,
foo
内的
this
指向
obj
)
call()
或者
apply()
方法直接指定
this
的绑定对象, 如
foo.call(obj)
)
this
的指向由外层做用域决定的)
注⚠️git
隐式丢失github
”
被隐式绑定的函数在特定的状况下会丢失绑定对象, 应用默认绑定, 把this
绑定到全局对象或者undefined
上:web
function foo () {
console.log(this.a)
}
var obj = {
a: 1,
foo: foo
}
var bar = obj.foo; // 使用另外一个变量赋值
var a = 2;
bar(); // 2
复制代码
// 参数传递形成的隐式绑定丢失
function foo() {
console.log(this.a)
}
var obj = {
a: 1,
foo: foo // 即便换成 () => foo() 也没用
}
function doFoo(fn) {
fn();
}
var a = 2;
doFoo(obj.foo) // 2
复制代码
解决显示绑定中丢失绑定问题面试
”
// 硬绑定
function foo(params) {
console.log(this.a, params);
return this.a + params;
}
var bar = function() {
return foo.apply(obj, arguments);
}
var obj = {
a: 1
}
var a = 2;
console.log(bar(3)) // 1, 3; return 4
复制代码
// 1.简单的辅助绑定函数
function bind (obj, fn) {
return function () {
return fn.apply(obj, arguments);
}
}
// 2. ES5内置了 Function.prototype.bind
var bar = foo.bind(obj);
复制代码
JS
中一些内置函数(数组的
forEach、map、filter
)提供的可选参数, 能够指定绑定
this
, 其做用和
bind
同样:
// 内置函数提供的可选参数, 指定绑定this
function foo(el) {
console.log(el, this.a)
}
var obj = {
a: 'obj a'
};
var a = 'global a';
var arr = [1, 2, 3];
arr.forEach(foo, obj) // 第二个参数为函数的this指向
// 1 'obj a', 2 'obj a', 3 'obj a'
复制代码
详细指南: 《木易杨前端进阶-JavaScript深刻之史上最全--5种this绑定全面解析》数组
[[prototype]]
链接, 将新对象的原型指向构造函数,这样新对象就能够访问到构造函数原型中的属性
this
的指向为新建的对象,这样新对象就能够访问到构造函数中的属性
详细指南: 《木易杨前端进阶-JavaScript深刻之史上最全--5种this绑定全面解析》浏览器
语法:缓存
func.apply(thisArg, [argsArray])
func.call(thisArg, arg1, arg2, ...)
复制代码
Array.prototype.push.apply(arr1, arr2)
)
Math.max.apply(null, arr)
)
Object.prototype.toString.call(obj)
)
Array.prototype.slice.call(domNodes)
或者
[].slice.call(domNodes)
)
SuperType.call(this)
)
Object.prototype.hasOwnProperty.call(obj)
来检测
Object.create(null)
这种对象
注⚠️:
关于第6点:
全部普通对象均可以经过 Object.prototype
的委托来访问 hasOwnProperty(...)
,可是对于一些特殊对象( Object.create(null)
建立)没有链接到 Object.prototype
,这种状况必须使用 Object.prototype.hasOwnProperty.call(obj, "a")
,显示绑定到 obj
上。又是一个 call
的用法。
例如🌰:
var obj = Object.create(null);
obj.name = 'objName';
console.log(Object.prototype.hasOwnProperty.call(obj5, 'name')); // true
复制代码
详细指南: 《木易杨前端进阶-深度解析 call 和 apply 原理、使用场景及实现》
问题缘由:
Array.prototype.push.apply(arr1, arr2);
// or
Array.prototype.push.call(arr1, ...arr2);
复制代码
因此为了解决第二个数组长度太大的问题, 咱们能够将参数数组切块后循环传入目标数组中:
function connectArray (arr1, arr2) {
const QUANTUM = 32768;
for (let i = 0, len = arr2.length; i < len; i += QUANTUM) {
Array.prototype.push.apply(
arr1,
arr2.slice(i, Math.min(i + QUANTUM, len))
)
}
return arr1;
}
复制代码
测试:
var arr1 = [-3, -2, -1];
var arr2 = [];
for (let i = 0; i < 100000; i++) {
arr2.push(i);
}
connectArray(arr1, arr2);
// arr1.length // 100003
复制代码
详细指南: 《木易杨前端进阶-深度解析 call 和 apply 原理、使用场景及实现》
在Object.prototype.toString()
没有被修改的状况下, 咱们能够用它结合call
来获取数据类型:
[[Class]]
是一个内部属性,值为一个类型字符串,能够用来判断值的类型。
// 手写一个获取数据类型的函数
function getClass(obj) {
let typeString = Object.prototype.toString.call(obj); // "[object Array]"
return typeString.slice(8, -1);
}
console.log(getClass(new Date)) // Date
console.log(getClass(new Map)) // Map
console.log(getClass(new Set)) // Set
console.log(getClass(new String)) // String
console.log(getClass(new Number)) // Number
console.log(getClass(NaN)) // Number
console.log(getClass(null)) // Null
console.log(getClass(undefined)) // Undefined
console.log(getClass(Symbol(42))) // Symbol
console.log(getClass({})) // Object
console.log(getClass([])) // Array
console.log(getClass(function() {})) // Function
console.log(getClass(document.getElementsByTagName('p'))) // HTMLCollection
console.log(getClass(arguments)) // Arguments
复制代码
Array.prototype.slice.call(arguments);
// 等同于 [].slice.call(arguments);
ES6:
let arr = Array.from(arguments);
let arr = [...arguments];
复制代码
Array.from()
能够将两类对象转为真正的数组:类数组对象和可遍历(iterable)对象(包括ES6新增的数据结构 Set 和 Map), 好比:
var map1 = new Map();
map1.set("key1", "value1")
map1.set("key2", "value2")
var mapArr = Array.from(map1)
console.log(map1) // Map
console.log(mapArr) // [["key1", "value1"], ["key2", "value2"]] 二维数组
复制代码
扩展一: 为何经过 Array.prototype.slice.call()
就能够把类数组对象转换成数组 🤔️?
答: 由于slice
将类数组对象经过下标操做放入了新的数组中
扩展二: 经过 Array.prototype.slice.call()
就足够了吗?存在什么问题 🤔️?
答: 在低版本的IE下不支持Array.prototype.slice.call(args)
这种写法, 由于低版本IE(IE < 9)下的DOM
对象是以 com
对象的形式实现的,js对象与 com
对象不能进行转换。
兼容的写法为:
function toArray (nodes) {
try {
return Array.prototype.slice.call(nodes);
} catch (err) {
var arr = [],
len = nodes.length;
for (var i = 0; i < len; i++) {
arr.push(nodes[i]);
}
return arr;
}
}
复制代码
扩展三: 为何要有类数组对象呢?或者说类数组对象是为何解决什么问题才出现的 🤔️?
一句话就是能够更快的操做复杂数据, 好比音频视频编辑, 访问webSockets的原始数据等.
语法:
func.bind(thisArg, arg1, arg2, ...)
复制代码
咱们知道, bind()
方法的做用是会建立一个新函数, 在这个新函数被调用时, 函数内的this
指向bind()
的第一个参数, 而其他的参数将做为新函数的参数被它使用.
因此它与apply/call
最大的区别是bind
会返回一个绑定上下文的函数, 然后二者会直接执行这个函数.
在使用场景上:
this
的指向, 好比解决隐式绑定的函数丢失
this
的状况
Function.prototype.call.bind(Object.prototype.toString)
来获取数据类型(前提是
Object.prototype.toString
方法没有被覆盖
bind
是会返回一个新函数的, 因此咱们还能够用它来实现柯里化,
bind
自己也是闭包的一种使用场景.
详细指南: 《木易杨前端进阶-深度解析bind原理、使用场景及模拟实现》
/** * 非严格模式 */
var name = 'window'
var person1 = {
name: 'person1',
show1: function () {
console.log(this.name)
},
show2: () => console.log(this.name),
show3: function () {
return function () {
console.log(this.name)
}
},
show4: function () {
return () => console.log(this.name)
}
}
var person2 = { name: 'person2' }
person1.show1()
person1.show1.call(person2)
person1.show2()
person1.show2.call(person2)
person1.show3()()
person1.show3().call(person2)
person1.show3.call(person2)()
person1.show4()()
person1.show4().call(person2)
person1.show4.call(person2)()
复制代码
空
白
格
答案:
person1.show1() // person1 隐式绑定, this指向调用者
person1.show1.call(person2) // person2 显示绑定, this指向person2
person1.show2() // window,箭头函数绑定,this指向外层做用域,即全局做用域
person1.show2.call(person2) // window, 使用call硬绑定person2也没用,this指向外层做用域,即全局做用域
person1.show3()() // window, 默认绑定, 此函数为高阶函数, 调用者是window
// 能够理解为隐性丢失,使用另外一个变量来给函数取别名: var bar = person1.show3();
person1.show3().call(person2)// person2, 显式绑定, 将 `var bar = person1.show3()` 这个函数的this 指向 person2
person1.show3.call(person2)() // window, 默认绑定, 虽然将第一层函数内的this指向了person2, 可是内层函数 `var bar = person1.show3()` 的调用者仍是window
person1.show4()() // person1, 第一层函数的this是person1, 内层为箭头函数, 指向外层做用域person1
person1.show4().call(person2) // person1, 第一层函数的this是person1, 内层为箭头函数,使用call硬绑定person2也没用,this仍是指向外层做用域person1
person1.show4.call(person2)() // person2, 改变了第一层函数的this指向, 将其指向为person2, 而内层为箭头函数, 指向外层做用域person2
复制代码
换一种方式: 使用构造函数来建立对象, 并执行4个相同的show
方法:
提示: 使用new
操做符建立的对象和直接var
产生的对象的区别在于:
使用new操做符会产生新的构造函数做用域, 这样箭头函数内的this指向的就是这个函数做用域, 而非全局
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, 与第一题的区别, 此时this指向的是外层做用域 personA函数的做用域
personA.show2.call(personB) // personA, 箭头函数使用call硬绑定也没用
personA.show3()() // window, 默认绑定, 调用者是window, 同第一题同样
personA.show3().call(personB) // personB, 显示绑定
personA.show3.call(personB)() // window, 默认绑定,调用者是window, 同第一题同样
personA.show4()() // personA, 箭头函数绑定,this指向外层做用域,即personA函数做用域
personA.show4().call(personB) // personA, 箭头函数绑定,call并无改变外层做用域,
personA.show4.call(personB)() // personB, 将第一层函数的this指向改为了personB, 此时做用域指向personB, 内存函数为箭头函数, this指向外层做用域,即personB函数做用域
复制代码
function create () {
var obj = new Object(),
Con = [].shift.call(arguments);
obj.__proto__ = Con.prototype;
var ret = Con.apply(obj, arguments);
return ret instanceof Object ? ret : obj;
}
复制代码
空
白
格
过程分析:
function create () {
// 1. 建立一个新的对象
var obj = new Object(),
// 2. 取出第一个参数, 就是咱们要传入的构造函数; 同时arguments会被去除第一个参数
Con = [].shift.call(arguments);
// 3. 将 obj的原型指向构造函数,这样obj就能够访问到构造函数原型中的属性
obj.__proto__ = Con.prototype;
// 4. 使用apply,改变构造函数this 的指向到新建的对象,这样 obj就能够访问到构造函数中的属性
var ret = Con.apply(obj, arguments);
// 5. 优先返回构造函数返回的对象
return ret instanceof Object ? ret : obj;
}
复制代码
详细指南: 《木易杨前端进阶-深度解析 new 原理及模拟实现》
ES3写法:
// 建立一个独一无二的 fn 函数名
function fnFactory(context) {
var unique_fn = 'fn';
while (context.hasOwnProperty(unique_fn)) {
unique_fn = "fn" + Math.random();
}
return unique_fn;
}
Function.prototype.call2 = function (context) {
context = context ? Object(context) : window;
var args = [];
for (var i = 1, len = arguments.length; i < len; i++) {
args.push('arguments[' + i + ']');
}
var fn = fnFactory(context)
context[fn] = this;
var result = eval('context[fn](' + args + ')');
delete context[fn];
return result;
}
复制代码
ES6写法:
Function.prototype.call3 = function (context) {
context = context ? Object(context) : window;
var fn = Symbol();
context[fn] = this;
let args = [...arguments].slice(1);
let result = context[fn](...args);
delete context[fn];
return result;
}
复制代码
空
白
格
过程分析:
// 建立一个独一无二的 fn 函数名
function fnFactory(context) {
var unique_fn = 'fn';
while (context.hasOwnProperty(unique_fn)) {
unique_fn = "fn" + Math.random();
}
return unique_fn;
}
Function.prototype.call2 = function(context) {
// 1. 如果传入的context是null或者undefined时指向window;
// 2. 如果传入的是原始数据类型, 原生的call会调用 Object() 转换
context = context ? Object(context) : window;
// 3. 建立一个独一无二的fn函数的命名
var fn = fnFactory(context);
// 4. 这里的this就是指调用call的那个函数
// 5. 将调用的这个函数赋值到context中, 这样以后执行context.fn的时候, fn里的this就是指向context了
context[fn] = this;
// 6. 定义一个数组用于放arguments的每一项的字符串: ['agruments[1]', 'arguments[2]']
var args = [];
// 7. 要从第1项开始, 第0项是context
for (var i = 1, l = arguments.length; i < l; i++) {
args.push('arguments[' + i + ']')
}
// 8. 使用eval()来执行fn并将args一个个传递进去
var result = eval('context[fn](' + args + ')');
// 9. 给context额外附件了一个属性fn, 因此用完以后须要删除
delete context[fn];
// 10. 函数fn可能会有返回值, 须要将其返回
return result;
}
复制代码
测试代码:
var obj = {
name: 'objName'
}
function consoleInfo(sex, weight) {
console.log(this.name, sex, weight)
}
var name = 'globalName';
consoleInfo.call2(obj, 'man', 100); // 'objName' 'man' 100
consoleInfo.call3(obj, 'woman', 120); // 'objName' 'woman' 120
复制代码
ES3:
// 建立一个独一无二的 fn 函数名
function fnFactory (context) {
var unique_fn = 'fn';
while (context.hasOwnProperty(unique_fn)) {
unique_fn = 'fn' + Math.random();
}
return unique_fn;
}
Function.prototype.apply2 = function (context, arr) {
context = context ? Object(context) : window;
var fn = fnFactory(context);
context[fn] = this;
var result;
if (!arr) {
result = context[fn]();
} else {
var args = [];
for (var i = 0, len = arr.length; i < len; i++) {
args.push('arr[' + i + ']');
}
result = eval('context[fn](' + args + ')');
}
delete context[fn];
return result;
}
复制代码
ES6:
Function.prototype.apply3 = function (context, arr) {
context = context ? Object(context) : window;
let fn = Symbol();
context[fn] = this;
let result = arr ? context[fn](...arr) : context[fn]();
delete context[fn];
return result;
}
复制代码
空
白
格
过程分析:
// 建立一个独一无二的 fn 函数名
function fnFactory (context) {
var unique_fn = 'fn';
while (context.hasOwnProperty(unique_fn)) {
unique_fn = 'fn' + Math.random();
}
return unique_fn;
}
Function.prototype.apply2 = function (context, arr) {
// 1. 如果传入的context是null或者undefined时指向window;
// 2. 如果传入的是原始数据类型, 原生的call会调用 Object() 转换
context = context ? Object(context) : window;
// 3. 建立一个独一无二的fn函数的命名
var fn = fnFactory(context);
// 4. 这里的this就是指调用call的那个函数
// 5. 将调用的这个函数赋值到context中, 这样以后执行context.fn的时候, fn里的this就是指向context了
context[fn] = this;
var result;
// 6. 判断有没有第二个参数
if (!arr) {
result = context[fn]();
} else {
// 7. 有的话则用args放每一项的字符串: ['arr[0]', 'arr[1]']
var args = [];
for (var i = 0, len = arr.length; i < len; i++) {
args.push('arr[' + i + ']');
}
// 8. 使用eval()来执行fn并将args一个个传递进去
result = eval('context[fn](' + args + ')');
}
// 9. 给context额外附件了一个属性fn, 因此用完以后须要删除
delete context[fn];
// 10. 函数fn可能会有返回值, 须要将其返回
return result;
}
复制代码
提示:
this
表示的就是调用的函数
this
的指向
new
操做法建立对象, 且提供的
this
会被忽略
Function.prototype.bind2 = function (context) {
if (typeof this !== "function") {
throw new Error("Function.prototype.bind - what is trying to be bound is not callable")
}
var self = this;
var args = Array.prototype.slice.call(arguments, 1);
var fBound = function () {
var innerArgs = Array.prototype.slice.call(arguments);
return self.apply(
this instanceof fNOP ? this : context,
args.concat(innerArgs)
)
}
var fNOP = function () {};
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
}
复制代码
空
白
格
Function.prototype.bind2 = function(context) {
// 1. 判断调用bind的是否是一个函数
if (typeof this !== "function") {
throw new Error("Function.prototype.bind - what is trying to be bound is not callable")
}
// 2. 外层的this指向调用者(也就是调用的函数)
var self = this;
// 3. 收集调用bind时的其它参数
var args = Array.prototype.slice.call(arguments, 1);
// 4. 建立一个返回的函数
var fBound = function() {
// 6. 收集调用新的函数时传入的其它参数
var innerArgs = Array.prototype.slice.call(arguments);
// 7. 使用apply改变调用函数时this的指向
// 做为构造函数调用时this表示的是新产生的对象, 不做为构造函数用的时候传递context
return self.apply(
this instanceof fNOP ? this : context,
args.concat(innerArgs)
)
}
// 5. 建立一个空的函数, 且将原型指向调用者的原型(为了能用调用者原型中的属性)
// 下面三步的做用有点相似于 fBoun.prototype = this.prototype 但有区别
var fNOP = function() {};
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
// 8. 返回最后的结果
return fBound;
}
复制代码
喜欢霖呆呆的小伙还但愿能够关注霖呆呆的公众号👇👇👇.
我会不定时的更新一些前端方面的知识内容以及本身的原创文章🎉
你的鼓励就是我持续创做的动力 😊.
相关推荐: