目录
3、创建10个标签。点击的时候弹出来对应的序号
一、题目
1、说一下对变量提升的理解
- 变量的定义
- 函数的声明(注意和函数表达式的区别)
2、说明this几种不同的使用场景
作为普通函数执行 (默认绑定)
作为对象属性执行 (隐式绑定)
call、apply、bind(强绑定) (显示绑定)
作为构造函数 (new绑定)
优先级:new绑定>显示绑定>隐式绑定>默认绑定
3、创建10个<a>标签。点击的时候弹出来对应的序号
var i
for(i=0;i<10;i++){
(function(){ //自执行函数 不用调用,只要定义完成,立即执行
var a=document.createElement('a');
a.innerHTML=i+'<br>'
a.addEventListener('click',function(){
e.preventDefault();
alert(i) //自由变量 去父级作用域寻找
})
document.body.appendChild(a)
})(i)
}
错误写法,每次点击输出的都是10,因为点击的时候循环已经结束,页面上已经有0,1,2,3,4,5,6,7,8,9,此时i==10
var i,a
for(i=0;i<10;i++){ //全局作用域 会覆盖
a=document.createElement('a');
a.innerHTML=i+'<br>'
a.addEventListener('click',function(){
e.preventDefault();
alert(i)
})
document.body.appendChild(a)
}
俩种方法实现创建10个li标签,点击显示对应的i
//创建10个li 点击显示对应的i
function add(){
var ul=document.getElementById("ul");
var str=""
for(var i=0;i<10;i++){
str +="<li>li</li>"
}
ul.innerHTML=str
// var li=document.getElementsByTagName("li")
// var liLength=li.length
// for(let i=0;i<liLength;i++){
// li[i].onclick=function (){
// alert(i)
// }
// }
ul.onclick=function (event){
var target = event.target || event.srcElement;
var li=document.getElementsByTagName("li")
var liLength=li.length
if(target.nodeName.toLowerCase() == "li"){
for(var i=0;i<liLength;i++){
if(li[i]===target){
alert(i)
}
}
}
}
}
add()
4、如何理解作用域
- 自由变量
- 作用域链,即自由变量的查找
- 闭包的俩个场景
5、实际开发中闭包的应用
闭包在实际应用中主要用于封装变量,收敛权限。
function isFirstLoad(){
var _list=[]; //变量前加下划线 表示变量是私有的
return function(id){
if(_list.indexOf(id)>=0){
return false;
}else{
_list.push(id);
return true;
}
}
}
//使用
var firstLoad=isFirstLoad();
firstLoad(10);//true
firstLoad(10);//false
firstLoad(20);//true
firstLoad(20);//false
在isFirstLoad函数外面无法修改 _list的值
二、知识点
PS:代码的注释也很重要哦 一定要阅读,有知识点的讲解
2.1 执行上下文
- 范围:一段<script>或者一个函数声明,都是执行上下文环境。
- 全局:在一段脚本代码中,变量定义、函数声明前置。
- 函数:在一段函数中,变量定义、函数声明、this、arguments前置。
console.log(a); //undefined 占位
var a=100;
//前两行代码运行的时候会把变量提前,提前之后代码相当于:
var a=undefined;
console.log(a);
a=100;
//当console.log输出a的时候,a还没有赋值,所以输出的是undefined
fn(zhangsan); //zhangsan 20
function fn (name){
console.log(this)
console.log(arguments)
age=20;
console.log(name,age)
var age
}
//上面六行代码运行的时候会把函数声明提前,函数里面会把变量提前,提前之后代码相当于:
function fn(name){
var age;
age=20;
console.log(name,age);
}
fn("zhangsan");
//当console.log输出name和age的时候,name参数和age都进行赋值了,所以输出的是"zhangsan" 20。
注意:变量前置的时候只能把变量名称前置,函数声明前置的时候是整个函数前置。
PS:注意“函数声明”和“函数表达式”的区别
函数声明在代码运行的时候前置,函数表达式不前置。
fn() //正确
function fn (){
//函数声明
}
fn1() //错误 fn1相当于一个变量
var fn1=function(){
//函数表达式
}
2.2 this
- this 要在执行时才能确认,定义时无法确认
也就是说,一个对象或者函数你只定义了,没有执行,this永远都不知道它指向谁。记住最核心的一句话:哪个对象调用函数,函数里面的this指向哪个对象。
var a={
name:'A',
fn:function (){
console.log(this.name)
}
};
a.fn(); //当fn作为一个对象的属性执行的时候,this指向对象a本身,即this===a //作为对象属性执行
a.fn.call({name:'B'}); //当用call转移对象之后,this指向转移后的对象this==={name:"B"}
var fn1 =a.fn;
fn1(); //当在一个普通函数中执行时,this指向的是window即this===window //普通函数执行
- this的使用场景
1、作为构造函数执行 (构造函数中生成一个隐式的this引用变量,来记录当前这个构造函数是被哪一个对象调用。)
function Foo(name){
this.name=name;
//相当于这里有个this空对象,然后给对象加属性,然后再返回this
//this={};
//this.name=name;
//return this;
}
var f=new Foo("zhangsan");
console.log(f.name) //zhangsan
用new来调用Foo()时,我们会构造一个新对象f并把它绑定到Foo()调用中的this上
回忆下构造函数运行的几个步骤
1、实例化对象,一个新对象被创建,新对象的隐式原型指向构造函数的显示原型
2、构造函数被执行,同时this被指定为新实例
3、如果构造函数返回一个对象,这个对象会取代整个new出来的结果,如果构造函数没有返回对象,new出来的结果就是1创建的对象),就能得出正确的答案了。
注意:构造函数有一个坑,这里必要提一下,在构造函数里面返回一个对象,this会指向返回的这个对象,而不是执行构造函数后创建的对象,这个,回忆下构造函数运行的步骤,也能得出正确的答案。
2、作为对象属性执行
var obj ={
name:"A",
printName:function (){
console.log(this.name)
}
};
obj.printName();
//把函数作为对象属性执行,this就指向的是这个对象
//很明显,上面代码输出的结果是A,调用函数的是obj,因此this指向obj。
虽然obj1的fn是从obj2的fn赋值而来,但是调用函数的是obj1,所以this指向obj1。
3、作为普通函数执行 函数体是严格模式this会被绑定到undefined,否则就是指向全局对象window
function fn(){
console.log(this) //this===window
}
fn()
4、call apply bind
简单来说,call和apply有三个主要用途,1、改变this的指向,this从obj2指向了obj1。2、借用方法,obj1没有fn方法,只是借用obj2的方法。3、根据这个this和call还可以实现继承。
call和apply的作用,完全一样,唯一的区别就是在参数上面,call接收的参数不规定,第一个参数是函数体内this的指向,第二个参数以后是依次传入的参数。apply接收两个参数,第一个参数也是函数体内this的指向。第二个参数是一个集合对象(数组或者类数组)
function fn1(name,age){
alert(name)
console.log(this)
}
fn1.call({x:100},'zhangsan',20)
//用call转移上下文,这个this就是指向第一个参数{x:100},
//这个写法第一个参数表示this,第二个参数就是函数里传递的参数,如果有第三个继续往后写
n1. apply({x:100},["zhangsan",20]);
//apply作用一样,写法跟call稍有区别,就是传递参数的方式不一样,它把函数里的参数放在数组里传递
bind的写法跟上面两个写法不太一样,用bind的时候必须是一个函数表达式,它是在函数的后面直接写上bind,然后再调用函数
var fn2=function (name,age){
alert (name)
console.log(this)
}.bind({y:200})
fn2('zhangsan',20)
注意:
- 在jquery一些常用框架脚本里最常用的是用call改变this的值。
- 如果把null或者undefined作为this的绑定对象传入call,apply或者bind,这些值调用时会被忽略,实际应用的是默认绑定规则
- bind不能与new绑定同时使用
5、箭头函数
箭头函数不会创建自己的this,它只会从自己的作用域链的上一层继承this
箭头函数不使用this的四种标准规则,而是根据外层(函数或者全局)作用域来决定this
function foo(){
//返回一个箭头函数
return (a)=>{
//this 继承自foo()
console.log(this.a)
};
}
var obj1={
a:2
};
var obj2={
a:3
};
var bar =foo.call(obj1);
bar.call(obj2) //2 不是3
foo()内部创建的箭头函数会捕获调用时foo()的this。 箭头函数的绑定不会被修改
箭头函数最常用于回调函数中,例如事件处理器或者定时器
function foo(){
setTimeout(()=>{
console.log(this.a)
},100)
}
var obj={
a:2
};
foo.call(obj); //a
2.3 作用域
- JS没有块级作用域 (ES6中有块级作用域)
if(true){
var name='zhangsan' //因为js没有块级作用域,所以这一行也可以看成是name声明在if上面
}
console.log(name);
//在java等高级语言中会报错,变量会受块级作用域的影响,在js中没有块级作用域
//js中为了方便阅读,不建议这样写,这样写容易使程序不易读,建议写成下面这种方式:
var name;
if(true){
name="zhangsan";
}
console.log(name);
- 只有函数和全局作用域
var a=100 //全局作用域
function fn () { //函数作用域
var a = 200 //定义在函数里,外面不能修改也不能使用
console.log('fn', a) //200
}
console.log('global',a) //100
fn()
2.4 作用域链
- 自由变量:当前作用域没有定义的变量 ,只关注在哪被定义,不关注在哪被使用
var a=100 ;
function fn (){
var b=200;
console.log(a);
//当前作用域(即函数作用域)没有定义a变量,然后就去函数作用域的父级作用域中寻找a,这个a就属于一个“自由变量”
console.log(b);
}
fn()
注意:函数的父级作用域,不是函数调用时候的父级而是函数声明(或者函数定义)时候的父级作用域。
- 什么叫作用域链(即自由变量的查找)?
就是一个自由变量一个自由变量一直不断的往父级作用域找,就是作用域链
var a=100 ;
function F1 (){
var b=200;
function F2(){
var c=300
console.log(a); //自由变量,a向上找,再向上找,就是个作用域链
console.log(b); //自由变量
console.log(c);
}
F2()
}
F1()
2.5 闭包
理解闭包 参考本文章http://blog.sina.com.cn/s/blog_c112a2980102xl89.html
- 闭包的使用场景
函数作为返回值
function F1(){
var a=100
return function (){
console.log(a) //自由变量 父作用域中查找
}
}
//f1 得到一个函数
var f1 =F1()
var a=200;
f1(); //100
函数作为参数传递
function F1(){
var a=100
return function (){
console.log(a)
}
}
var f1 =F1()
function F2(fn){
var a=200
fn()
}
F2(f1) //100
- 闭包的作用
避免全局变量暴露在外面(可以读取函数内部的变量),造成全局变量的污染。(在函数内部封装变量)
这些变量的值始终保存在内存中。
- 使用闭包的注意点
1、由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页性能问题,在IE中可能导致内存泄漏。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
2、闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的共用方法,把内部变量当作它的私有属性,这时一定要小心,不要随便改变父函数内部变量的值。