1.原型和原型链
2.作用域和闭包
3.异步和单线程
被称为JavaScript的三座大山
原型和原型链:
在JavaScript中,数组,对象和函数被称为引用类型,他们都有一个__proto__属性,该属性是一个对象(我们称之为隐式原型)
arr数组的构造函数是Array,Array构造函数中有一个prototype属性,(我们暂时称之为显式原型)
arr是构造函数的实例对象,arr中的__proto__对象指向构造函数中的prototype对象
接下来是一个简单的demo
1 //创建一个构造函数 2 function Animal(name){ 3 this.name = name 4 } 5 Animal.prototype.eat = function(){ 6 console.log('Animal--eat') 7 } 8 // 用new初始化一个Animal的实例对象 9 var dog = new Animal('xiaohuang') 10 11 console.log(dog.name) 12 console.log(dog.eat())
输出结果为
看一下dog对象中有什么属性
调用dog的属性和方法时,会先从dog本身去查找,如果dog本身没有那个属性或方法,就会去dog的__proto__原型中去查找,而__proto__又指向Animal的prototype(看第二个constructor对象,指向Animal),这就是原型链
再来一个demo
1 //创建一个构造函数 2 function Animal(name){ 3 this.name = name 4 } 5 //创建一个Hashiqi的构造函数 6 function Hashiqi(){ 7 } 8 Animal.prototype.eat = function(){ 9 console.log('Animal--eat') 10 } 11 Hashiqi.prototype.color = function(){ 12 console.log('Hashiqi---color') 13 } 14 //改变prototype的指向 15 Animal.prototype = new Hashiqi() 16 // 用new初始化一个Animal的实例对象 17 var dog = new Animal('xiaohuang') 18 19 console.log(dog.name) 20 console.log(dog.eat()) 21 console.log(dog.color())
这个demo中改变了Animal的prototype,将其指向一个Hashiqi的实例对象,我们来看结果。此时dog.eat()会报错,dog.color正常输出
第三个demo,比较上档次一点
1 <div id="wrapper">this is wrapper</div> 2 <script> 3 //创建一个Elem的构造函数 4 function Elem(id){ 5 //获取dom元素 6 this.id = document.getElementById(id) 7 } 8 Elem.prototype.html = function(val){ 9 //如果val为空,则打印dom元素的innerhtml值 10 if(val == null){ 11 console.log(this.id.innerHTML) 12 //返回this,可以用来进行链式操作 13 return this 14 }else{ 15 this.id.innerHTML = val 16 return this 17 } 18 } 19 //绑定事件 20 Elem.prototype.on = function(type, fn){ 21 this.id.addEventListener(type,fn) 22 } 23 24 </script>
首先new一个实例对象: var el = new Elem('wrapper')
el.html(' ookook ').on('click', function(){ console.log('this is ook') } )
接下来点击ookook就会打印this is ook
作用域和闭包:
JavaScript中有函数作用域和全局作用域(es6中用let声明的变量具有块作用域)
函数作用域是在一个函数中有效,全局作用域在全局都有效
*函数内部如果变量和全局变量重名了,则在该函数内部,以函数变量为准
*函数外部无法访问函数内部定义的变量(该变量是函数私有的),不过函数内部可以访问函数外部的全局变量
1.变量提升
javascript中声明并定义一个变量时,会把声明提前,以下会先打印出undefined,再打印出10
1 console.log(a) 2 var a = 10 3 console.log(a)
相当于
1 var a 2 console.log(a) 3 a = 10 4 console.log(a)
函数声明也是,以下函数相当于把整个fn提到作用域的最上面,所以调用fn时会正常打印jack
1 fn('jack') 2 function fn (name){ 3 console.log(name) 4 }
不过函数表达式不行,以下是一个函数表达式,JavaScript会把var fn提到作用域最上面,没有吧函数提上去,所以会报错
1 fn("jack"); 2 3 var fn = function(name) { 4 console.log(name); 5 };
2.闭包
使用场景:1.函数作为参数传递 2.函数作为返回值传递
三言两语说不清楚,我们来看一个demo
1 function F1(){ 2 var a = 100 3 //返回一个函数 4 return function(){ 5 console.log(a) 6 } 7 } 8 9 var a = 200; 10 var f1 = F1(); //将f1指向F1 11 f1()
第11行输出结果是100
第11行调用f1的时候要打印a变量,return的函数中没有a变量,所以a是个*变量,要去声明该函数(不是调用)的父级作用域去查找,即functin F1中,a=100
闭包在开发中的应用
以下函数的目的是:当_list中没有val值时,返回true并把val添加到_list中
这个demo使用了闭包,在外部无法直接访问_list这个Fn函数的私有变量,这样可以保证数据不被污染,提高了安全性
1 function Fn(){ 2 var _list = [] 3 4 return function(val){ 5 if(_list.indexOf(val) < 0){ 6 _list.push(val) 7 return true 8 }else{ 9 return false 10 } 11 } 12 } 13 14 var f1 = Fn() 15 console.log(f1(10)) //true 16 console.log(f1(10)) //false 17 console.log(f1(20)) //true 18 console.log(f1(20)) //false
异步和单线程
异步和单线程是相辅相成的,js是一门单线程脚本语言,所以需要异步来辅助
异步和同步的区别: 异步会阻塞程序的执行,同步不会阻塞程序的执行,
比如只有在执行完alert后才会打印100,如果你不去点击弹框的确定键,console.log就永远不会执行,这就是同步的阻塞
第二个demo中,会马上就打印100,两秒后再打印setTimeout,这就是异步不会阻塞程序执行
1 alert('ook') 2 console.log(100)
1 setTimeout(function(){ 2 console.log('setTimeout') 3 },2000) 4 console.log(100)
在哪些场景中会异步执行
JavaScript中以下三种情况会异步执行 1.定时任务:setTimeout, setInterval 2.网络请求:ajax请求,动态加载<img>图标 3.事件绑定:比如‘click’等
看一个demo
以下函数的目的是在页面中创建5个a标签,点击第一个a标签打印1,点击第二个a标签打印2,以此类推。可执行结果都是5,为什么呢?
因为click是一个异步事件,计算机不知道用户什么时候会点击,所以不能够是同步,不然用户不点击,程序就永远无法往下执行。
异步事件会先拿出来放到一个队列里,等同步事件执行完了,再来执行异步事件
所以当你点击a标签的时候,i已经循环到5了(i是全局变量。这也涉及到了js作用域:该函数没有定义i变量,就要去声明该函数的父级作用域中去找,而不是调 用)
1 for(var i = 0; i < 5; i++){ 2 //创建一个a标签 3 var a = document.createElement('a') 4 a.innerHTML = i + 1 + '<br>' 5 //给a标签绑定click事件 6 a.addEventListener('click', function(e){ 7 e.preventDefault() 8 console.log(i) 9 }) 10 //将a标签添加到wrpper中 11 document.querySelector('#wrapper').appendChild(a) 12 }
解决这个问题就可以用闭包,用一个function把给a添加时间的地方包起来,(function(i){})(i) 这是函数的自调用。注意:自调用前要加分号,也就是第4行结束要加分号,不然js会分不清何时开始自调用,会报错
1 for(var i = 0; i < 5; i++){ 2 //创建一个a标签 3 var a = document.createElement('a') 4 a.innerHTML = i+1 + '<br>'; 5 //给a标签绑定click事件 6 (function(i){ 7 a.addEventListener('click', function(e){ 8 e.preventDefault() 9 console.log(i+1) 10 }) 11 })(i) 12 //将a标签添加到wrpper中 13 document.querySelector('#wrapper').appendChild(a) 14 }