来看下call的原生表现形式:
function test(arg1, arg2) {
console.log(arg1, arg2)
console.log(this.a, this.b)
}
run.call({
a: 'a',
b: 'b'
}, 1, 2)
好了,开始手动实现我们的call2。在实现的过程有个关键:
如果一个函数作为一个对象的属性,那么通过对象的.运算符调用此函数,this就是此对象
let obj = {
a: 'a',
b: 'b',
test: function (arg1, arg2) {
console.log(arg1, arg2)
// this.a 就是 a; this.b 就是 b
console.log(this.a, this.b)
}
}
obj.test(1, 2)
知道了实现关键,下面就是我们模拟的call:
Function.prototype.call2 = function(context) {
if(typeof this !== 'function') {
throw new TypeError('Error')
}
// 默认上下文是window
context = context || window
// 保存默认的fn
const { fn } = context
// 前面讲的关键,将函数本身作为对象context的属性调用,自动绑定this
context.fn = this
const args = [...arguments].slice(1)
const result = context.fn(...args)
// 恢复默认的fn
context.fn = fn
return result
}
// 以下是测试代码
function test(arg1, arg2) {
console.log(arg1, arg2)
console.log(this.a, this.b)
}
test.call2({
a: 'a',
b: 'b'
}, 1, 2)
apply和call实现类似,只是传入的参数形式是数组形式,而不是逗号分隔的参数序列。
因此,借助es6提供的...运算符,就可以很方便的实现数组和参数序列的转化。
Function.prototype.apply2 = function(context) {
if(typeof this !== 'function') {
throw new TypeError('Error')
}
context = context || window
const { fn } = context
context.fn = this
let result
if(Array.isArray(arguments[1])) {
// 通过...运算符将数组转换为用逗号分隔的参数序列
result = context.fn(...arguments[1])
} else {
result = context.fn()
}
context.fn = fn
return result
}
/**
* 以下是测试代码
*/
function test(arg1, arg2) {
console.log(arg1, arg2)
console.log(this.a, this.b)
}
test.apply2({
a: 'a',
b: 'b'
}, [1, 2])
bind的实现有点意思,它有两个特点:
Function.prototype.bind2 = function(context) {
if(typeof this !== 'function') {
throw new TypeError('Error')
}
const that = this
// 保留之前的参数,为了下面的参数拼接
const args = [...arguments].slice(1)
return function F() {
// 如果被new创建实例,不会被改变上下文!
if(this instanceof F) {
return new that(...args, ...arguments)
}
// args.concat(...arguments): 拼接之前和现在的参数
// 注意:arguments是个类Array的Object, 用解构运算符..., 直接拿值拼接
return that.apply(context, args.concat(...arguments))
}
}
/**
* 以下是测试代码
*/
function test(arg1, arg2) {
console.log(arg1, arg2)
console.log(this.a, this.b)
}
const test2 = test.bind2({
a: 'a',
b: 'b'
}, 1) // 参数 1
test2(2) // 参数 2
实现一个对象的深拷贝函数,需要考虑对象的元素类型以及对应的解决方案:
/**
* 数组的深拷贝函数
* @param {Array} src
* @param {Array} target
*/
function cloneArr(src, target) {
for(let item of src) {
if(Array.isArray(item)) {
target.push(cloneArr(item, []))
} else if (typeof item === 'object') {
target.push(deepClone(item, {}))
} else {
target.push(item)
}
}
return target
}
/**
* 对象的深拷贝实现
* @param {Object} src
* @param {Object} target
* @return {Object}
*/
function deepClone(src, target) {
const keys = Reflect.ownKeys(src)
let value = null
for(let key of keys) {
value = src[key]
if(Array.isArray(value)) {
target[key] = cloneArr(value, [])
} else if (typeof value === 'object') {
// 如果是对象而且不是数组, 那么递归调用深拷贝
target[key] = deepClone(value, {})
} else {
target[key] = value
}
}
return target
}
这段代码是不是比网上看到的多了很多?因为考虑很周全,请看下面的测试用例:
// 这个对象a是一个囊括以上所有情况的对象
let a = {
age: 1,
jobs: {
first: "FE"
},
schools: [
{
name: 'shenda'
},
{
name: 'shiyan'
}
],
arr: [
[
{
value: '1'
}
],
[
{
value: '2'
}
],
]
};
let b = {}
deepClone(a, b)
a.jobs.first = 'native'
a.schools[0].name = 'SZU'
a.arr[0][0].value = '100'
console.log(a.jobs.first, b.jobs.first) // output: native FE
console.log(a.schools[0], b.schools[0]) // output: { name: 'SZU' } { name: 'shenda' }
console.log(a.arr[0][0].value, b.arr[0][0].value) // output: 100 1
console.log(Array.isArray(a.arr[0])) // output: true
看到测试用例,应该会有人奇怪为什么最后要输出Array.isArray(a.arr[0])。这主要是因为网上很多实现方法没有针对array做处理,直接将其当成object,这样拷贝后虽然值没问题,但是array的元素会被转化为object。这显然是错误的做法。
而上面所说的深拷贝函数就解决了这个问题。
要想实现,就要先看看什么是“双向数据绑定”,它和“单向数据绑定”有什么区别?这样才能知道要实现什么效果嘛。
双向绑定:视图(View)的变化能实时让数据模型(Model)发生变化,而数据的变化也能实时更新到视图层。
单向数据绑定:只有从数据到视图这一方向的关系。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script>
const obj = {
value: ''
}
function onKeyUp(event) {
obj.value = event.target.value
}
// 对 obj.value 进行拦截
Object.defineProperty(obj, 'value', {
get: function() {
return value
},
set: function(newValue) {
value = newValue
document.querySelector('#value').innerHTML = newValue // 更新视图层
document.querySelector('input').value = newValue // 数据模型改变
}
})
</script>
</head>
<body>
<p>
值是:<span id="value"></span>
</p>
<input type="text" onkeyup="onKeyUp(event)">
</body>
</html>
随着,vue3.0放弃支持了IE浏览器。而且Proxy兼容性越来越好,能支持13种劫持操作。
因此,vue3.0选择使用Proxy来实现双向数据绑定,而不再使用Object.defineProperty。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script>
const obj = {}
const newObj = new Proxy(obj, {
get: function(target, key, receiver) {
return Reflect.get(target, key, receiver)
},
set: function(target, key, value, receiver) {
if(key === 'value') {
document.querySelector('#value').innerHTML = value
document.querySelector('input').value = value
}
return Reflect.set(target, key, value, receiver)
}
})
function onKeyUp(event) {
newObj.value = event.target.value
}
</script>
</head>
<body>
<p>
值是:<span id="value"></span>
</p>
<input type="text" onkeyup="onKeyUp(event)">
</body>
</html>
instanceof是通过原型链来进行判断的,所以只要不断地通过访问__proto__,就可以拿到构造函数的原型prototype。直到null停止。
/**
* 判断left是不是right类型的对象
* @param {*} left
* @param {*} right
* @return {Boolean}
*/
function instanceof2(left, right) {
let prototype = right.prototype;
// 沿着left的原型链, 看看是否有何prototype相等的节点
left = left.__proto__;
while(1) {
if(left === null || left === undefined) {
return false;
}
if(left === prototype) {
return true;
}
left = left.__proto__;
}
}
/**
* 测试代码
*/
console.log(instanceof2([], Array)) // output: true
function Test(){}
let test = new Test()
console.log(instanceof2(test, Test)) // output: true
实现思路:这里涉及了“订阅/发布模式”的相关知识。参考addEventListener(type, func)和removeEventListener(type, func)的具体效果来实现即可。
// 数组置空:
// arr = []; arr.length = 0; arr.splice(0, arr.length)
class Event {
constructor() {
this._cache = {};
}
// 注册事件:如果不存在此种type,创建相关数组
on(type, callback) {
this._cache[type] = this._cache[type] || [];
let fns = this._cache[type];
if (fns.indexOf(callback) === -1) {
fns.push(callback);
}
return this;
}
// 触发事件:对于一个type中的所有事件函数,均进行触发
trigger(type, ...data) {
let fns = this._cache[type];
if (Array.isArray(fns)) {
fns.forEach(fn => {
fn(...data);
});
}
return this;
}
// 删除事件:删除事件类型对应的array
off(type, callback) {
let fns = this._cache[type];
// 检查是否存在type的事件绑定
if (Array.isArray(fns)) {
if (callback) {
// 卸载指定的回调函数
let index = fns.indexOf(callback);
if (index !== -1) {
fns.splice(index, 1);
}
} else {
// 全部清空
fns = [];
}
}
return this;
}
}
// 以下是测试函数
const event = new Event();
event
.on("test", a => {
console.log(a);
})
.trigger("test", "hello");
很多童鞋可能年后有自己的一些计划,比如换份工作环境,比如对职业目标有了新的打算。当然面试这一关不得不过,大概又不可能系统性的复习,这里罗列一些 重点 面试的知识点和文章,
什么是webpack和grunt和gulp有什么不同?什么是bundle,什么是chunk,什么是module?什么是Loader?什么是Plugin?如何可以自动生成webpack配置?webpack-dev-server和http服务器如nginx有什么区别?
多问问应聘者高层次的知识点,如果能讲清楚这些概念,就说明即使应聘者没怎么接触过 JavaScript,也能够在短短几个星期之内就把语言细节和语法之类的东西弄清楚。
面试比棘手的技术问题要多,这篇文章整理了37个JavaScript基本面试问题和解答,这些仅仅是作为指导。希望对前端开发的你有所帮助!
React常见面试题:React中调用setState之后发生了什么事情?React中Element与Component的区别?优先选择使用ClassComponent而不是FunctionalComponent?React中的refs属性的作用是什么?React中keys的作用是什么?
题目大意为:JS 环境下,如何让 a == 1 && a == 2 && a == 3 这个表达式返回 true ?这道题目乍看之下似乎不太可能,因为在正常情况下,一个变量的值如果没有手动修改,在一个表达式中是不会变化的。
10道JavaScript题目:累加函数addNum、实现一个Person类、实现一个arrMerge 函数、实现一个toCamelStyle函数、setTimeout实现重复调用、实现一个bind函数、实现一个Utils模块、输出一个对象自身的属性
面试开场白总缺少不了自我介绍,一方面是面试官想听听你对自己的介绍,顺便有时间看看简历上的描述,是否与口述一致。另一方面就是看看你简历上做过什么项目,用到了哪些技术栈,一会儿好提问你。
把面试当做学习,这个过程你会收益很大。前端知识很杂,可能实际工作中用到的技术,像框架都是跟着公司的要求走的,像我最近也在看React啦,Vue和React都对比着再学习
vue上手可以说是比较轻松而且简单,如果你用过angular,react,你也会很喜欢vue。vue的核心思想依旧是:构建用户界面的渐进式框架,关注视图的变化。这也是为什么新建的文件是结构是template script style
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!