题
- this的不同应用场景,如何取值?
- 手写bind函数
- 闭包应用场景,举例说明
- 创建10个 标签,点击的时候弹出对应的序号
// 点击每个都打印10
let i, a;
for(i=0; i<10; i++){
a = document.createElement('a');
a.innerHTML = i + '<br>';
a.addEventListener('click', function(e){
e.preventDefault();
alert(i);
})
document.body.appendChild(a)
}
// 这样就点击每个都打印对应的i了
let a;
for(let i=0; i<10; i++){
a = document.createElement('a');
a.innerHTML = i + '<br>';
a.addEventListener('click', function(e){
e.preventDefault();
alert(i);
})
document.body.appendChild(a)
}
知识点
- 作用域和自由变量
- 闭包
- this
作用域
作用域:变量可以合法使用的范围
分为:
- 全局作用域:代码中直接写一个变量,变量不受函数的约束,在全局中都可以使用,比如window、document
- 函数作用域:函数中定义的对象,只能在当前函数内使用
- 块级作用域(ES6新增):块 例如if for while大括号,大括号中就是块
if(true){
let x = 100;
}
console.log(x) // 会报错,let const定义的变量,都遵循块级作用域的规则
自由变量
- 一个变量在当前作用域没有定义,但被使用了
- 向上级作用域,一层一层依次寻找,注意是定义的上级作用域去寻找,直至找到
- 如果到全局作用域都没找到,则报错xx is not defined
闭包
闭包实际上是作用域应用的特殊情况,有两种表现:
跟上方四个红方的函数不一样(函数定义在什么地方,就会在什么地方调用)
- 函数作为参数被传递 (函数在一个地方定义好后,传递到另一个地方去执行)
- 函数作用返回值被返回(函数在一个地方定义好后,被返回到另一个地方去执行)
- 总之,函数定义的地方和执行的地方不一致
// 函数作为返回值
function create(){
let a = 100;
return function(){
console.log(a)
}
}
let fn = create();
let a = 200;
fn() // 100
// 函数作为参数
function print(fn){
let a = 200
fn()
}
let a = 100;
function fn(){
console.log(a)
}
print(fn) // 100
上方代码小结(重点)
所有的自由变量的查询,是在函数定义的地方,向上级作用域查找,不是在执行的地方!!!
面试题一:this 有几种赋值情况
this取什么值,是在函数执行的时候决定的,不是在函数定义的时候决定的
- 作为普通函数: this指向window
function fn1(){
console.log(this)
}
fn1() // window
- 使用call,apply,bind: this指向第一个参数
fn1.call({x: 100}) // {x: 100}
const fn2 = fn1.bind({x: 200})
fn2() // {x: 200}
// bind是返回一个新的函数,执行下这个新的函数就好;call和apply是直接被执行的
- 作为对象方法被调用:this指向对象
const zhangsan = {
name: '张三',
sayHi(){
// this 即当前对象
console.log(this)
},
wait(){
console.log(this) // zhangsan
setTimeout(function(){
// this === window
// 这句被执行,是setTimeout本向触发的执行,作为普通函数被执行;而不是作为zhangsan的方法被执行
console.log(this)
});
}
}
- 在class方法中调用:在class中this值的就是class创建出来的实例对象
class People {
constructor(name){
this.name = name;
this.age = 20;
}
sayHi(){
console.log(this);
}
}
const zhangsan = new People('张三');
zhangsan.sayHi() // zhangsan对象
- 箭头函数中调用:this永远指向上级作用域的this
const zhangsan = {
name: '张三',
sayHi(){
// this 即当前对象
console.log(this)
},
waitAgain(){
console.log(this) // zhangsan
setTimeout(()=>{
// this即当前对象
console.log(this)
})
}
}
面试题二:手写bind
Function.prototype.bind1 = function(){
// 1 将参数拆解为数组
let args = Array.prototype.slice.call(arguments); // arguments是个列表,其实是个类数组,通过这行代码将arguments转为数组
// 2 获取 this (数组第一项)
const t = args.shift(); // shift挖出第一项,影响原数组
// 3 fn1.bind(...) 中的 fn1
const self = this;
// 4 返回一个函数
return function(){
self.apply(t, args)
}
}
function fn1(a, b){
console.log('this', this);
console.log(a, b);
return 'this is fn1';
}
const fn2 = fn1.bind1({x: 100}, 10, 20)
const res = fn2();
console.log(res)
面试题三:实际开发中闭包的应用
隐藏数据(比如做一个简单的catch工具)
- 我们通过 createCache中 return的函数,闭包的一种形式,jQuery中很常见
- 执行c.set,c.get时,在createCache的作用域里的,能找到 data, 对data做修改
function createCache(){
const data = {}; // 闭包中的数据,被隐藏,不被外界访问
return {
set: function(key, val){
data[key] = val
},
get: function(key){
return data(key)
}
}
}
const c = createCache();
c.set('a', 100);
console.log(c.get('a'));
// 不通过set get直接改 data的值,没法改,
// 因为 data定义在 createCache作用域里面的,不会被外界访问到;
// 也就是说 data的合法作用域, 只是createCache里,不会被外界访问到
data.a = 200 // 会报错, data 作用域没有定义, 所以不会找到 createCache里
面试题四:点击每个a,打印结果是什么
每次点击都是10
解释:for循环在很短的时间内就循环完了,i的值就变成了10,因为i的作用域是全局,这个时候点击事件还没触发,所以再点击时弹出来的就是10
- 1 for遍历10遍,遍历每一遍时,创建a标签,然后绑定click事件,click还没有开始执行
- 2 click是什么时候点击,什么时候执行,不点击永远不执行,点击再执行
- 3 等执行时,console.log(i)中的i是自由变量 关于自由变量.md(见上方也有自由变量的名词解释),往上级作用域找,
- 4 点击时,这个for循环已经完事了,因为i 是全局作用域定义的,i就变成10了,所以每次点击都是10
let a, i;
for(i=0; i<10; i++){
a = document.createElement('a');
a.innerHTML = i+ '<br/>';
a.addEventListener('click', function(e){
e.preventDefault();
console.log(i)
})
document.body.appendChild(a)
}
每次点击都是相应的i值
解释:每次for循环时,形成了块级作用域,click在块级作用域中寻找i
- 1 let i定义在for中,是定义的for里面的块作用域
- let i在for中定义时,每次for循环执行的时候,都会形成一个新的块,块是针对 每一个块的**,产生一个块级作用域**,i就会不一样
- 也就是说从0~9,i寻找的时候,就会从块级作用域去找, 就会打印对应的i
- i 是全局作用域还是 块级作用域,是不一样的
- let i在上方定义时,全局作用域针对所有的块的
重点:不会立马执行的函数,要注意,可能会对里面的自由变量,可能对外面的作用域里的数据改变所误导
let a;
for(let i=0; i<10; i++){
a = document.createElement('a');
a.innerHTML = i+ '<br/>';
a.addEventListener('click', function(e){
e.preventDefault();
console.log(i)
})
document.body.appendChild(a)
}
小结
- 作用域和自由变量
- 作用域(几个红框)和自由变量(当前作用域中没有定义的变量,但是使用了,向上级作用域中查找)
- 闭包:两种常见方式(返回函数、函数做为参数传递) && 自由变量查找规则(函数定义的时候查找)
- this:函数执行的时候查找(this只有在执行的时候值才确定)
原型中的this
xiaoluo.proto.sayHi() // 姓名 undefined,学号 undefined
//类
//class + 名称 模板
class Student {
//当前构建的实例
constructor(name,number){
this.name = name;
this.number = number;
}
sayHi( ) {//方法
console.log(
`姓名 ${this.name},学号 ${this.number}`//反引号
)
}
}
//通过类 new 对象/实例
const xiaoluo = new Student('夏洛',100 );
xiaoluo.sayHi() // 姓名 夏洛,学号 100
xiaoluo.__proto__.sayHi() // 姓名 undefined,学号 undefined
- obj.proto.sayHi()的时候 this指向了obj.proto,而这个对象里没有name和number,故为undefined。
- 实际上obj.sayHi()执行原理类型于obj.prototype.sayHi.call(obj),用到了call,把this指向了obj,obj里有name和number,所以能打印出来。