栈内存:做用域
javascript
1.提供一个供JS代码自上而下执行的环境(代码都是在栈中执行的) 2.因为基本数据类型值比较简单,他们都是直接在栈内存中开辟一个位置,把值直接存储进去的java
当栈内存被销毁,存储的基础值也随之销毁
面试
堆内存:引用值对应的空间
ajax
1.存储引用类型值的编程
- 对象:存储的是键值对
- 函数:代码字符串
当前堆内存释放销毁,那么这个引用值完全没了
小程序
堆内存的释放
设计模式
当堆内存没有被任何的变量或者其它东西所占用,浏览器会在空闲的时候,自主的进行内存回收,把全部不被占用的堆内存销毁(谷歌浏览器)[IE浏览器经过计数器来进行内存释放]数组
销毁方法: xxx=null
经过空对象指针null可让原始变量(或者其它东西)谁都不指向,那么原有被占用的堆内存就没有被东西占用了,浏览器会销毁它浏览器
定义
:当栈内存(做用域)造成以后,JS代码自上而下执行以前,浏览器首先会把全部带var(声明)
和function(声明并定义)
关键词的进行提早的声明
或者定义
,这种预先处理机制称为"变量提高"
性能优化
变量提高细节知识点
1.
变量提高只发生在当前做用域
(例如:开始加载页面的时候只对全局做用域下的变量进行提高,由于此时函数中存储的都是字符串而已)
2.在全局做用域下声明的函数或者变量是"全局变量",同理,在私有做用域下声明的变量是"私有变量"[带VAR/FUNCTION的才是声明]
3.浏览器很懒,作过的事情**
不会重复
**执行第二遍,也就是,当代码执行遇到建立函数这部分代码后,直接的跳过便可(由于在提高阶段就已经完成函数的赋值操做了)
私有做用域造成后,也不是当即执行代码,而是先进行变量提高(变量提高前,先进行形参赋值)
在ES3/ES5语法规范中,只有全局做用域和函数执行的私有做用域(栈内存),其它大括号不会造成栈内存
知识点补充:逻辑与&&和逻辑或||
优先级:逻辑与的优先级高于逻辑或
console.log(0||1&&2||0||3&&2||1);//=>2
复制代码
逻辑与
A&&B,验证A的真假,为真结果是B,为假结果为A
var b=1&&2//=>b=2
复制代码
逻辑与的实战
//=>只有当传递的值是函数的时候咱们才让其执行
~function (caller){
caller&&caller()
}(function (){console.log('ok');});
复制代码
逻辑或
A||B,验证A的真假,为真结果为A,为假结果为B
var a=1||2//=>a=1
复制代码
逻辑或的实战
//=>"给形参赋值默认值:验证传递的参数值,若是没有传递实参,赋值为0"
function fn(x){
x=x||0;
//=>若是x没有传递值,x=undefined=>x=undefined||0
}
复制代码
特殊状况
在全局做用域下声明一个变量,也至关于给window全局对象设置了一个属性,变量的值就是属性值(私有做用域中声明的私有变量和window没啥关系)
//=>1.先进行变量提高 var a;此时同时也给window全局设置了一个属性名为a,属性值为undefined的属性
console.log(a);//=>undefined
console.log(window.a)//=>undefined
console.log('a' in window)//=>true in:检测某个属性是否隶属于这个对象
var a=12;
console.log(a);//=>全局变量a 12
console.log(window.a);//=>window的一个属性名a 12
a=13;
console.log(window.a);//=>13
window.a=14;
console.log(a);//=>14
//=>全局变量和window中的属性存在“映射机制”,给变量从新赋值时,window下对应的属性值也会发生改变;改变属性值时,对应的变量也会被从新赋值
复制代码
形参与arguments的映射机制
一、在JS非严格模式下,函数中的形参变量和arguments存在映射机制
二、arguments和形参之间的映射是以arguments的索引为基础完成的,arguments中有这个索引,浏览器会完成和对应形参变量中的映射机制搭建,若是形参比arguments中的个数多,那么多出来的形参是没法和arguments中的对应的索引创建关联的
三、argument和形参的映射机制创建在函数执行后形参赋值的一瞬间,此时能创建映射机制的创建映射机制,不能创建起来的,之后无论怎么操做都没法再创建了
~function fn(x,y){
var arg=arguments;
arg[0]=100;
console.log(x);//=>100
console.log(arg[1]);//=>undefined
y=200;
console.log(arg[1]);//=>undefined
console.log(y);//=>200
arg[1]=300;
console.log(y);//=>200
}(10);
复制代码
在严格模式下不支持使用 arguments.callee(函数自己)/arguments.callee.caller(函数执行的suzh),并且不存在映射机制
~function(x,y){
'use strict'
var arg=arguments;
arg[0]=100;
console.log(x);//=>10
x=200;
console.log(arg[0]);//=>100
console.log(arg.callee.caller)//=>Uncaught TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them
}(10);
复制代码
在ES6新语法规范中能够直接给形参设置默认值
function fn(x=0){
//=>若是x没有传递值,默认值是零,一旦传递值,无论传递的是啥,都是按照传递的值处理的
console.log(x);
}
fn();//=>0
fn(null);//=>null
fn(undefined);//=>传递undefined,浏览器也是按照没有传递值处理的
复制代码
全局做用域下带VAR和不带VAR的区别
带var表示声明变量 不带var表示给window对象下添加属性
//=>变量提高:无
console.log(a);//=>Uncaught ReferenceError: a is not defined 因无变量提高,也没有window.a这个属性,因此此时a至关于一个没有声明过的变量,因此会出现报错的状况
a=12;
console.log(a);
复制代码
console.log(a)的查找机制
:先找有无声明过的变量a,若是没有再找有无window.a这个属性,若是尚未仍是当作没有声明过的变量
语法补充
var a=12,
b=13;
此时至关于
var a=12;
var b=13;
复制代码
var a=b=13;
至关于
var a=13;
b=13;//<=>window.b=13
复制代码
var n=m=[12,23];
/* * 1.开辟堆内存,存储键值对 * 2.var n * 3.n=AAAFFF111 * m=AAAFFF111 / 至关于 var n=[12,23]; var m=[12,23]; 复制代码
私有做用域中带VAR和不带VAR的区别
1.带VAR的在私有做用域变量提高阶段,都声明为私有变量,和外界没有任何关系
2.不带VAR不是私有变量,会向它的上级做用域查找,看是否为上级变量,不是,继续向上查找,一直找到window为止(
咱们把这种查找机制叫作:“做用域链”
),也就是咱们在私有做用域中操做的这个非私有变量,是一直操道别人的
console.log(a, b);//=>undefined undefined
var a = 12;
var b=12;
function fn() {
console.log(a, b);//=>undefined 12(这个输出的是全局中变量b的值)
var a = b = 13;
/*var a=13; b=13;*/ //至关于把全局变量b被从新赋值为13
console.log(a, b);//=>13 13(这个输出的是全局中变量b的值)
}
fn();
console.log(a, b);//=>12 13(在私有做用域中已经将变量b的值改变为13)
复制代码
function fn(){
console.log(b)//=>Uncaught ReferenceError: b is not defined
b=13;
console.log('b' in window);//=>true 在做用域链查找的过程当中,若是找到WIN也没有这个变量,至关于给WIN设置了一个B(window.b=13)
console.log(b);//=>13 <=>console.log(window.b)
}
fn();
console.log(b);//=>13
复制代码
做用域链查找的机制
:
- 1.若是往上级查找的过程当中,上级有这个变量,那直接修改上级这个变量的值
- 2.若是往上级查找的过程当中,一直找到window下都没有这个变量,那么就至关于给window这个对象添加一个属性
练习题
var n=0;
function a(){
var n=10;
function b(){
n++;
console.log(n);
}
b();
return b;
}
var c=a();
c();
console.log(n);
结果 11,12,0
复制代码
/* * 变量提高: * var fn; =>只对等号左边进行变量提高 * sum = AAAFFF111; */
sum();
fn();//=>Uncaught TypeError: fn is not a function
var fn=function (){//=>匿名函数之函数表达式
console.log(1);
}//=>代码执行到此处会把函数值赋值给FN
fn();
function sum(){
console.log(2);
}
复制代码
给匿名函数设置名字其实没有实际的用途,由于在函数外面是没法获取这个函数名的 匿名函数起的名字只能在函数体内使用
var fn=function sum() {
console.log(sum)//=>函数体自己
};
fn();
console.log(sum)//=>Uncaught ReferenceError: sum is not defined
复制代码
var fn=function sum() {
console.log(sum())//=>会陷入死循环,fn执行后输出sum执行的结果,也就是fn执行的结果,会不停的执行函数
};
fn();
复制代码
在当前做用域下,无论条件是否成立都要进行变量提高
- 带VAR的仍是只声明
- 带function的在老版本浏览器渲染机制下,声明和定义都处理,可是为了迎合ES6中的块级做用域,新版浏览器对于
(在条件判断中的函数)
,无论条件是否成立,都只是先声明,没有定义,相似于VAR
/* * 变量提高 * 无 * / console.log(a);//=>undefined 变量a只声明未定义 if("a" in window){ var a=100; } console.log(a);//=>100 变量a的值为100 复制代码
老版本浏览器下
无论条件是否成立,都会进行变量提高
- 带VAR的只
声明
- 带FUNCTION的
声明并定义
/
* 变量提高:无
/
f=function(){return true;};//=>发生修改变为f=....false
g=function(){return false;};
~function(){
//变量提高 g=AAAFFF111
if(g() && [] == ![]){//=>此时函数g已经声明及定义了,是私有变量,因此执行函数g()的结果是true
f=function(){return false};//=>非私有变量,往上找,至关于改了window.f=....false,
function g(){return true};
}
}()
console.log(f());//=>false
//至关于自执行函数:(function(){return false;})();,把该函数执行,返回值为false
console.log(g());//=>false
//至关于自执行函数:(function(){return false;})();,把该函数执行,返回值为false
复制代码
新版浏览器下
无论条件是否成立,都进行变量提高
- 带VAR的只
声明
- 带FUNCTION的只
声明
f=function(){return true;};
g=function(){return false;};
~function(){
//=>变量提高:只声明了函数g,未定义(至关于var g;)
if(g() && [] == ![]){//=>Uncaught TypeError: g is not a function 此时的g是undefined)
f=function(){return false};
function g(){return true};
}
}()
console.log(f());
console.log(g());
复制代码
特殊状况
条件中的function在变量提高阶段也是只声明,当代码执行,若是条件成立,进入条件后的第一件事情,就是把函数定义了,而后再执行判断体中的代码
console.log(a,fn);//=>undefined undefined
if(1===1){
console.log(a,fn)//=>undefined fn函数自己
//=>当条件成立,进入到判断体中(在ES6中它是一个块级做用域)第一件事情并非代码执行,而是相似于变量提高同样,先把定义了(最开始的时候已经声明了),也就是判断体中代码执行以前,FN就已经赋值了
var a=12;
function fn(){
console.log('ok');
}
}
console.log(a,fn);//=> 12 fn函数自己
复制代码
带VAR和FUNCTION关键字声明相同的名字,这种也算是重名了(实际上是一个FN,只是存储值的类型不同)
关于重名的处理:若是名字重复了,不会从新的声明,可是会从新的定义(从新赋值)[无论是变量提高仍是代码执行阶段皆是如此]
/
* 变量提高:
* fn=aaafff111
* =aaafff222
* =aaafff333
* =aaafff444
/
fn();//=>4
function fn(){console.log(1);}
fn();//=>4
function fn(){console.log(2);}
fn();//=>4
var fn =100;
fn();//<=>100() Uncaught TypeError: fn is not a function
function fn(){console.log(3);}
fn();
function fn(){console.log(4);}
fn();
复制代码
在ES6中基于let/const等方式建立变量或者函数,不存在变量提高机制
切断了全局变量和WINDOW属性的映射机制,可是ES6的全局做用域下也是有WINDOW属性,只是没有了映射机制
不带let或者const就和ES6没有关系
基于LET和基于VAR建立变量,在私有变量和私有做用域链机制上是同样的
如今项目中ES5 && ES6混合开发模式,若是当前变量是基于LET建立的,就按照ES6的新语法机制渲染,不然按照ES5的老语法机制渲染
console.log(a);//=>Uncaught ReferenceError: a is not defined
let a=12;
console.log(window.a);//=>undefined
console.log('a' in window)//=>false
console.log(a);//=>12
复制代码
在相同做用域中,基于LET不能声明相同名字的变量(无论用什么方式在当前做用域下声明了变量(例如var,或者设置形参),再次使用let建立都会报错)
let a=12;
cosole.log(a);
let a=13;//=>Uncaught SyntaxError: Identifier 'a' has already been declared
console.log(a);
//代码不会执行,直接报错
复制代码
console.log(a);
var a=12;
console.log(a);
let a=13;//=>Uncaught SyntaxError: Identifier 'a' has already been declared
console.log(a);
//代码不会执行,直接报错
复制代码
let fn=(a)=>{//=>ES6箭头函数
let a=b=c=100;
};
fn();//=>3-LET.js:22 Uncaught SyntaxError: Identifier 'a' has already been declared
复制代码
虽然没有变量提高机制,可是在当前做用域代码自上而下执行以前,浏览器会作一个重复性检测:自上而下查找当前做用域下全部变量,一旦发现有重复的,直接抛出异常,代码也不会在执行了(虽然没有把变量提早声明定义,浏览器已经记住了,当前做用于域有哪些变量)
b=12;
console.log(b);//=>12
console.log(window.b);//=>12
a=12;//=>Uncaught ReferenceError: a is not defined
//=>代码执行以前,浏览器会记住全部ES6下声明的变量而且记住,此时代码开始执行,执行到这一步的时候,浏览器知道有这个变量,可是尚未声明,因此出现a is not defined的错误
console.log(a);
let a=13;
console.log(a);
复制代码
let a=10,
b=10;
let fn=function (){
console.log(a.b)//=>Uncaught ReferenceError: a is not defined
//=>在当前私有做用域下,浏览器会先记住该做用域下会声明一个a,输出a的时候啊尚未被声明,因此会报错a is not defined
let a=b=20;//<=>let a=20; b=20;(把全局中的b改成20)
console.log(a,b)
};
//=>箭头函数,也能够这样写 let fn=(a)=>{}
fn();
console.log(a,b);
复制代码
在原有浏览器渲染机制下,基于typeof等逻辑运算符检测一个未被声明过的变量,不会报错,返回undefined
若是当前变量是基于ES6语法,在没有声明这个变量的时候,使用typeof检测会直接报错,不会是undefined,解决了原有的JS死区
//=>ES5下
console.log(typeof a);//=>'undefined'
------------------------------
//=>ES6下
console.log(typeof a);//Uncaught ReferenceError:a is not defined
复制代码
语法
let fn=(x,y)=>{};
fn(10,20);
复制代码
只有一个形参,咱们能够省略小括号
let fn=x=>{};
fn(10);
复制代码
若是函数体中只有一句操做,而且是return的,咱们能够省略大括号
let fn=function (x,y){return x+y;};
let fn =(x=0,y=0)=>x+y;
复制代码
let fn=x=>y=>x+y;
---------------
var fn=function fn(x){
return function(y){
return x+y;
};
};
复制代码
箭头函数中没有arguments
let fn=(...arg)=>{
console.log(arguments);//=>Uncaught ReferenceError: arguments is not defined
console.log(arg);//=>可使用剩余运算符代替,并且arg是一个数组
};
fn(10,20,30,40);
复制代码
箭头函数中没有本身的执行主体(this),它的this都是默认继承上下文中的this,即便经过call方法改变this指向也是没用的
let obj={
fn:(function (){//=>自执行函数中的this是window
return function(){
console.log(this);
}
})()
};
obj.fn();//=>this:obj
//=>如何让obj.fn()执行输出的this是window
复制代码
第一种解决方案
let obj={
fn:(function (){
let _this=this;
return function (){
console.log(_this);//=>this只是一个变量,不是私有的,找上级做用域中的
}
})()
};
复制代码
第二种解决方案
let obj={
fn:(function (){
return ()=>{
console.log(this);
}
})()
}
//=>箭头函数中的this和执行前面有没有点没有关系,箭头函数中没有this,使用到的this都是直接找上下文中的this来使用
//=>暂时理解为找到上级做用域中的this
复制代码
在私有做用域中,只有如下两种状况是私有变量
- A:声明过的变量(带VAR/FUNCTION/LET)
- B:形参也是私有变量
函数执行的步骤
/
* 变量提高
* var a; var b; var c;
/
var a=12,
b=13,
c=14;
function fn(a){
/
* 1.形参赋值 a=12
* 2.变量提高 var b;
/
console.log(a,b,c)//=>(12,undefined,14)
var b=c=a=20;
/
* var b=20;//=>私有变量b赋值为20
* c=20;//=>把全局中的c修改成20
* a=20;//=>私有变量a修改成20
/
console.log(a,b,c)//=>(20,20,20)
}
fn(a);//=>把FN执行(小括号中是实参:值) =>执行FN把全局变量A的值12当作实参传递给函数的形参 =>fn(12)
console.log(a,b,c);//=>(12,13,20)
复制代码
练习
var ary=[12,23];
function fn(ary){
console.log(ary);
ary[0]=100;
ary=[100];
ary[0]=0;
console.log(ary);
}
fn(ary);
console.log(ary);
复制代码
当前函数执行,造成一个私有做用域A,A的上级做用域是谁,和他在哪执行的没有关系,只和他在哪建立(定义)的有关系,在哪建立的,它的上级做用域就是谁
在传统ES5中,只有全局做用域和函数执行造成的私有做用域这两种做用域,循环或者判断等都不是做用域
arguments:实参集合 arguments.callee:函数自己FN arguments.callee.caller:当前函数在哪执行的,caller就是谁(记录的是它执行的宿主环境),在全局下执行的结果是null
var a=12;
function fn(){
console.log(arguments.callee.caller)//=>sum这个函数自己
}
function sum(){
var a=120;
fn();
}
复制代码
练习
var n=10;
function fn(){
var n=20;
function f(){
n++;
console.log(n);
}
f();
return f;
}
var x=fn();
x();
x();
console.log(n);
复制代码
存储引用数据类型值(对象:键值对 函数:代码字符串)
堆内存释放
让全部引用堆内存空间地址的变量赋值为null便可(没有变量占用这个堆内存了浏览器会在空闲的时候把它释放掉)
提供JS代码执行的环境和存储基本类型值
栈内存释放
通常状况下,当函数执行完成,所造成的私有做用域(栈内存)都会自动释放掉(在栈内存中存储的值也都会释放保证浏览器的高速运转),可是也有特殊不销毁的状况:
- 1.函数执行完成,当前造成的栈内存中,某些内容被栈内存之外的变量占用了,此时栈内存不能释放(一旦释放外面 找不到原有的内容了)
- 2.全局栈内存只有在页面关闭的时候才会被释放掉
若是当前栈内存没有被释放,那么以前在栈内存中存储的基本值也不会被释放可以一直保存下来
知识点补充
i++:自身累加1(先拿原有值计算,运算结束后,自己累加1)
++i:自身累加1(先自身累加1,拿累加后的结果进行运算)
var k=1;
console.log(5+(++k),k);//=>(7,2)
console.log(5+(k++),k);//=>(6,2)
-------------
var k=1;
console.log(5 + (++k) + (k++) + 4 + (k--) + (++k) + 3 + (--k) + (k++), k);//=>(26,3)
复制代码
return后面返回的都是值,因此无论后面跟的是什么都不会进行变量提高
var i = 1;
function fn(i) {
return function (n) {
console.log(n + (++i));
}
}
var f = fn(2);
f(3);
fn(5)(6);
fn(7)(8);
f(4);
复制代码
var i = 2;
function fn() {
i += 2;
return function (n) {
console.log(n + (--i));
}
}
var f=fn();
f(2);
f(3);
fn()(2);
fn()(3);
f(4);
复制代码
let i=1;
let fn=function(n){
i*=2;
return function (m){
i+=n+m;
console.log(i);
}
};
let f=fn(2);
f(3);//=>7
fn(2)(3);//=>19
f(4);//=>25
f(5);//=>32
复制代码
定义
函数执行造成一个私有的做用域,保护里面的私有变量不受外界的干扰,这种保护机制称之为"闭包" 部分开发者认为闭包是:造成一个不销毁的私有做用域(私有栈内存)才是闭包
//=>闭包:柯理化函数
function fn(){
return function (){
}
}
var f=fn();
------------------------------
//=>闭包:惰性函数
var utils=(function(){
return {
}
})();
复制代码
真实项目中为了保证JS的性能(堆栈内存的性能优化),应该尽量的减小闭包的使用(不销毁的堆栈内存是消耗性能的)
1、闭包的保护做用
保护私有变量不受外界的干扰
在真实项目中,尤为是团队协做开发的时候,应当尽量的减小全局变量的使用,以防止相互之间的冲突("全局变量污染"),那么此时咱们彻底能够把本身这一部份内容封装到一个闭包中,让全局变量转化为私有变量
(function(){
var n=12;
function fn(){
}
})();
复制代码
不只如此咱们封装类库插件的时候,也会把本身的程序都存放到闭包中保护起来,防止和用户的程序冲突,可是咱们又须要暴露一些方法给客户使用,这样咱们如何处理呢?
1.JQ这种方式,把须要暴露的方法抛到全局
(function(){
function jQuery(){
...
}
window.jQuery=window.$=jQuery//=>把须要供外面使用的方法,经过给window设置属性的方式暴露出去
})()
jQuery();
$();
复制代码
2.Zepto这种方式:基于RETURN把须要供外面使用的方法暴露出去
var Zepto=(function(){
//...
return {
xxx:function(){
}
};
})();
Zepto.xxx()
//=>Zepto的值是自执行函数执行后返回的值,因此是return后面的对象,至关于Zepto={xxx:function(){}}
//Zepto.xxx,至关于等于xxx属性名对应的属性值
复制代码
2、闭包的保存做用
造成不销毁的栈内存,把一些值保存下来,方便后面的调取使用
案例:选项卡
var oTab=document.getELementById('tab'),
tabList=oTab.getElementsByTagName('li'),
divList=oTab.getElementsByTagName('div');
function changeTab(curIndex){
for(var i=0;i<tabList.length;i++){
tabList[i].className="";
divList[i].className="";
}
tabList[curIndex].className="active";
divList[curIndex].className="active"
}
复制代码
一、不能够直接使用索引i的缘由
事件绑定是"异步编程",当触发点击行为,绑定的方法执行的时候,外层循环已经结束;方法执行产生私有做用域,用到变量i,不是私有的变量,按照"做用域链"的查找机制,找到的是全局下的i(此时全局的i已经成为循环最后一次的结果3)
for(var i=0;i<tabList.length;i++){
tabList[i].onclick=function{
changeTab(i);
}
}
复制代码
1.
想要触发事件以前,首先页面需加载完毕,JS代码也已经加载完成,也就是该循环已经结束,此时的i变为了3
2.
当某个li被点击的时候会触发事件,执行函数,函数执行时会造成一个私有做用域,此时i不是私有变量,基于做用域链机制,会往上级做用域(也就是window做用域)查找,此时的i已经变为33.
全部的事件绑定都是异步编程
- 同步编程:一件事一件事的作,当前这件事没完成,下一个任务不能处理
- 异步编程:当前这个事件没有完全完成,再也不等待,继续执行下面的任务
- 绑定事件后,不须要等待执行,继续执行下一个循环任务,因此当咱们点击执行方法的时候,循环早已结束(让全局的i等于循环最后的结果3)
二、使用闭包实现
利用闭包的机制,把后期须要的索引实现存储到本身的私有做用域中:"闭包有保存做用"
第一种
for(var i=0;i<tabList.length;i++){
tabList[i].onclick=(function(n){
//=>让自执行函数执行,把执行的返回值return赋值给onlick事件(此处onclick绑定的是返回的小函数,点击的时候执行的是小函数),自执行函数在给事件赋值的时候就已经执行了
var i=n;
return function(){
changeTab(i);//=>上级做用域:自执行函数造成的做用域
}
})(i);
}
复制代码
循环三次,造成三个不销毁的私有做用域(自执行函数执行),而每个不销毁的栈内存中存储了一个私有变量i,这个值分别是每一次执行传递进来的全局i的值(也就是:第一个不销毁的做用域存储的是0,第二个是1,第三个是2);当点击的时候,执行返回的小函数,遇到变量i,向它本身的上级做用域查找,找到的i值分别是:0/1/2,达到了咱们想要的效果
var btnBox = document.getElementById('btnBox'),
inputs = btnBox.getElementsByTagName('input');
for (var i = 0; i < inputs.length; i++) {
inputs[i].onclick = (function (i) {
return function () {
alert(i);
}
})(i);//=>把每一次循环时候i(全局的)的值传递给自执行函数
}
复制代码
第二种
for(var i+0;i<tabList.length;i++){
(function(n){
tabList[n].onclick=function(){
changeTab(n);
}
})(i)
}
//=>原理都是造成三个不销毁的私有做用域,分别存储须要的索引值
复制代码
三、使用ES6方法实现
ES6知识点补充
基于ES6中的LET来建立的变量是存在块级做用域的(相似于私有做用域,有私有变量和做用域链查找机制),在大括号里面也是从新检测语法规范,看一下是不是基于新语法建立的变量,若是是,按照新语法规范来解析
ES6中的做用域
- 1.全局做用域
- 2.私有做用域(函数执行)
- 3.块级做用域(通常用大括号包起来的都是块级做用域,
对象除外
)
let a=100;
{
let a=200;
{
let a=300;
{
console.log(a);//=>300
}
}
}
//=>每一个变量a是不同的
-------------------------------
let a=100;
{
let a=200;
{
a=300;//=>把上级块级做用域中的a从200改成300;
{
console.log(a);//=>300,先往上级块级做用域找,没有变量a,再往上找有变量a了可是已经变成300了
}
}
}
---------------------------
let a=100;
{
let a=200;
{
{
console.log(a);//=>Uncaught ReferenceError: a is not defined
//=>当前块级做用域没有变量a,须要往上级块级做用域找,执行到当前代码的时候,尚未声明定义a,可是浏览器知道有a这个变量,因此会报错a is not defined
}
let a=300;
}
}
复制代码
var a=12;
if(true){
console.log(a);//=>Uncaught ReferenceError: a is not defined
let a=13;
let b=12;
}
console.log(b)//=>Uncaught ReferenceError: b is not defined
//=>由于if里面是用let建立的因此是块级做用域,a和b至关于该块级做用域的私有变量,和外面是没有关系的
复制代码
var a=12;
if(true){
console.log(a);//=>Uncaught ReferenceError: a is not defined
let a=13;
var b=12;
}
console.log(b)//=>12
复制代码
if(1===1){
let fn=function (){
console.log(1);
};
console.log(fn);//=>fn是当前判断体块级做用域中私有的
}
console.log(fn);//=>Uncaught ReferenceError: fn is not defined
复制代码
var a=12;
if(true){
console.log(a);//=>Uncaught ReferenceError: a is not defined
let a=13;
b=12;//=>至关于给全局做用域下加了一个window.b的属性
}
console.log(b)//=>12
console.log("b" in window)//=>true
复制代码
解决选项卡
ES6和闭包的机制相似,ES6中使用LET建立变量,会造成块级做用域
for(let i=0;i<5;i++){
//=>循环体也是块级做用域,初始值设置的变量是当前块级做用域中的变量(造成了五个块级做用域,每一个块级做用域中都有一个私有变量i,变量值就是每一次循环i的值)
}
console.log(i)//=>Uncaught ReferenceError: i is not defined
复制代码
for(let i=0;i<tabList.length;i++){
tabList[i].onclick=function(){
changeTab(i);
}
}
第一次循环
{
let i=0;
{
tabList[i].onclick=function(){//=>i往上级块级做用域找,i=0
changeTab(i);
}
}
}
第二次循环
{
let i=1;
{
tabList[i].onclick=function(){i往上级块级做用域找,i=1
changeTab(i)
}
}
}
第三次循环
{
let i=2;
{
tabList[i].onclick=function(){i往上级块级做用域找,i=2
changeTab(i)
}
}
}
第四次循环
{
let i=3;
{
tabList[i].onclick=function(){i往上级块级做用域找,i=3
changeTab(i)
}
}
}
第五次循环
{
let i=4;
{
tabList[i].onclick=function(){i往上级块级做用域找,i=4
changeTab(i)
}
}
}
//=>每一次循环都造成一个块级做用域,并且块级做用域内都存储了当前循环的i的值
复制代码
表现形式
var obj={
xxx:xxx,
....
};
复制代码
在单例设计模型中,OBJ不只仅是对象名,它被称为"命名空间[NameSpace]",把描述事物的属性存放到命名空间中,多个命名空间是独立分开的,互不冲突
做用
:
把描述同一件事物的属性和特征进行"分组、归类"(存储在同一个堆内存空间中),所以避免了全局变量之间的冲突和污染
var parttern1={name:'xxx'};
var parttern2={name:'xxx'};
复制代码
命名由来
每一个命名空间都是JS中Object这个内置基类的实例,而实例之间是相互独立互不干扰的,因此咱们称它为"单例:单独的实例"
在给命名空间赋值的时候,不是直接赋值给一个对象,而是先执行自执行函数,造成一个私有做用域AA(不销毁的栈内存),在AA中建立一个堆内存,把堆内存地址赋值给命名空间(
惰性思想
)
这种模式的好处:咱们彻底能够在AA中创造不少内容(变量or函数),哪些须要供外面调取使用的,咱们暴露到返回的对象当中(模块化实现的一种思想)
var nameSpace=(function(){
var n=12;
function fn(){
}
function sum(){
}
return {
fn:fn,
sum:sum
}
})();
nameSpace.fn();//=>至关于在外面调取fn这个函数并执行,nameSpace.fn是fn这个属性名对应的属性值,也就是fn
//=>nameSpace的值为自执行函数执行返回的值,也就是return后面的对象
复制代码
当前方法执行的主体(谁执行的这个方法,那么this就是谁,因此this和当前方法在哪建立的或者在哪执行的都没有必然联系)
第一种状况
给当前元素的某个事件绑定方法,当事件触发方法执行的时候,方法中的this是当前操做的元素对象
oBox.onclick=function(){
//=>this:oBox
}
复制代码
第二种状况
普通函数执行,函数中的this取决于执行的主体。谁执行的,this就是谁(执行主体:方法执行,看方法名前面是否有
点
,有的话,点
前面是谁this就是谁,没有的话,this就是window)
function fn(){//=>AAAFFF000
console.log(1);
}
var obj={
fn:fn//=>fn:AAAFFF000
}
//=>执行的是相同的方法(不一样的地方在于函数执行方法中的this是不同的)
obj.fn();//=>this:obj
fn();//=>this:window
复制代码
案例一
var name="window";
var Tom={
name:"Tom",
show:function(){
console.log(this.name);
},
wait:function(){
var fun=this.show;//=>此时的this是Tom
fun();//=>前面没有点,至关于直接执行该函数,此函数的this是window
}
};
Tom.wait();//=>window
Tom.show();//=>Tom
复制代码
var length=10;
fuction fn(){
console.log(this);
console.log(this.length);
}
var obj={
length:5;
method:function(fn){
fn();
arguments[0]();
}
};
console.log(obj.method(fn,1));
window 10
arguments自己 2
复制代码
第三种状况
自执行函数,通常状况下this是window
~function(){
//=>this:window
}();
复制代码
练习题
var n = 2;
var obj={
n:3,
fn:(function (n) {
n*=2;
this.n+=2;
var n=5;
return function (m) {
this.n*=2;
console.log(m + (++n));
}
})(obj.n)//=>Uncaught TypeError: Cannot read property 'n' of undefined
};
var fn = obj.fn;
fn(3);
obj.fn(3);
console.log(n, obj.n);
报错的缘由:
obj处是先开辟一个堆内存存储键值对,fn对应的属性值是自执行函数执行后返回的结果,而此时键值对存储尚未完成,也就是尚未和变量obj关联在一块儿,obj至关于undefined(obj在全局做用域下只声明未定义),undefined是基本数据类型没有属性,因此会报错
复制代码
var n = 2;
var obj={
n:3,
fn:(function (n) {
n*=2;
this.n+=2;
var n=5;
return function (m) {
this.n*=2;
console.log(m + (++n));
}
})(n)
};
var fn = obj.fn;
fn(3);//=>this:window 9
obj.fn(3);//=>this:obj 10
console.log(n, obj.n);//=>(8,6)
复制代码
var num=1,
obj={
num:2,
fn:(function(num){
this.num*=2;
num+=2;
return function (){
this.num*=3;
num++;
console.log(num);
}
})(num)
};
var fn =obj.fn;
fn();//=>4
obj.fn();//=>5
console.log(num,obj.num);//=>(6,6)
复制代码
第四种状况
构造函数执行,方法体中的this是当前类的一个实例
function Fn(){
this.name="zhufeng";
this.age=18;
}
var f=new Fn();
console.log(f);//{name:"zhufeng",age:18}
复制代码
第五种状况
箭头函数中没有本身的this,this是上下文中的this
let obj={
fn:function(){
//=>this:obj
setTimeout(()=>{
//=>this:obj,继承了fn里面的this
},1000);
}
}
复制代码
第六种状况
小括号表达式中,会影响this的指向
let obj={
fn:function(){
console.log(this);
}
};
obj.fn();//=>this:obj
(12,obj.fn)();//=>this:window
复制代码
第七种状况
使用call/apply/bind能够改变this指向
fn.call(obj);//=>this:obj
fn.call(12);//=>this:12
fn.call();//=>this:window
复制代码
非严格模式下call/apply/bind第一个参数不写或者写null和undefined,this都是window,严格模式下写谁this就是谁,不写是undefined
开启严格模式:在当前做用域的第一行加上
'use strict'
(快捷键 us+[TAB]),那么当前做用域下再执行的JS代码都是按照严格模式处理的
'use strict';
//=>当前JS代码都开启了严格模式(包含了函数中的代码)
复制代码
~function(){
'use strict';
//=>只是把当前私有做用域开启了严格模式(对外面全局没有影响)
}();
复制代码
第一种状况
在JS严格模式下,若是执行主体不明确,this指向的是undefined(非严格模式下指向的是window)
function fn(){
console.log(this);
}
fn();//=>window
window.fn();//=>window
复制代码
"use strict";
function fn(){
console.log(this);
}
fn();/=>undefined
window.fn();//=>window
复制代码
第二种状况
自执行函数中的this为undefined
function fn(){
console.log(this);//=>window
}
document.body.onclick=function (){
console.log(this)//=>document.body
fn();
}
复制代码
1.团队协做开发的时候,会把产品按照功能板块进行划分,每个功能板块有专人负责开发
//=>搜索板块
var searchModel={
submit:function(){
untils.aa();
//=>能够直接调取公共板块的方法
}
}
//=>频道板块
var channelModel={
setChannel:function(){}
translateChannel:function(){}
show:function(){
secrchModel.submit();
//=>在当前的命名空间下调取其它命名空间的方法:制定好对应的空间名字便可,使用[nameSpace].[property]就能够操做
this.setChannel();
//=>调取本模块中的一些方法,能够直接使用this处理便可:此方法中的this通常都是当前模块的命名空间
}
}
channelModel.show();//=>表明调取channelModel下的show方法
//=>天气板块
var weatherRender=(function(){
var fn=function(){};
var f=function(){};
//=>想暴露在外面的方法就写在return里面
return {
init:function(){
fn();//=>调取本身板块中的方法直接调取使用便可
channelModel.show();
},
fn:fn,
aa:channnelModel.show
}
})();
weatherRender.init();
复制代码
2.把各个板块之间公用的部分进行提取封装,后期再想实现这些功能,直接的调取引用便可
//=>公共板块
var utils=(function(){
return {
aa:function(){
}
}
})();
//=>搜索模块
var searchModel={
submit:function(){
untils.aa();
//=>能够直接调取公共板块的方法
}
}
复制代码
``
1.把实现相同功能的代码进行"封装",以此来实现"批量生产"(后期想要实现这个功能,咱们只须要执行函数便可) 2."低耦合高内聚":减小页面中的冗余代码,提升代码的重复使用率
function createPerson(name,age){
var obj={};
obj.name=name;
obj.age=age;
return obj;
}
var p1=createPerson('xxx',25);
var p2=createPerson('xxx',25);
复制代码
JS是一门编程语言(具有编程思想)
- [面向对象]
- JS\JAVA\PHP\C#\Ruby\Python\C++
- [面向过程]
- C
对象、类、实例
对象
:万物皆对象(咱们所要研究学习以及使用的都是对象)
类
:对象的具体细分(按照功能特色进行分类:大类、小类)
实例
:类中具体的一个事物(拿出类别中的具体一个实例进行研究,那么当前类别下的其它实例也具有这些特色和特征)
整个JS就是基于面向对象设计和开发出来的语言,咱们学习和实战的时候也要按照面向对象的思想去体会和理解
为何getElmentById只能经过document获取?
由于getElementById这个方法只有Document这个类才有,其它类没有,document是Document这个类的一个实例,Document这个类同时也提供getElementsByTagName/getElementsByClassName等方法。
页面中的其余元素都是Element类的一个实例,Element只提供getElementsByTagName/getElementsByClassName等方法,没有getElementById这个方法。
建立某个类的一个实例的方法
var a=new 类名();
var num=new Number(1);
//=>num是Number类的一个实例,值为1
---------------------------------
var ary=new Array();
//=>ary是Array类的一个实例
1.new Array(10):建立一个长度为10的数组,数组中的每一项都是空
2.new Array("10"):若是只传递一个实参,而且实参不是数字,至关于把当前值做为数组的第一项存储进来
3.new Array(10,20,30):若是传递多个实参,不是设置长度,而是把传递的内容当作数组中的每一项存储起来
------------------
var obj=new Object();
//=>通常只用于建立空对象,若是须要增长键值对,建立完成后本身依次添加("对象.属性名"或者"对象[属性名]"的方式)
复制代码
基于构造函数建立自定义类
一、在普通函数执行的基础上"new xxx()",这样就不是普通函数执行了,而是构造函数执行,当前的函数名称之为"类名",接收的返回结果是当前类的一个实例 二、本身建立的类名,最好第一个单词首字母大写 三、这种构造函数设计模式执行,主要用于组件、类库、插件、框架等的封装,平时编写业务逻辑通常不这样处理
JS中引用数据类型建立值的两种方式
无论是哪种方式创造出来的都是Object类的实例,而实例之间是独立分开的,因此 var xxx={},这种模式就是JS中的单例模式
一、
字面量表达式
var obj ={};//=>这种模式是JS的单例模式
复制代码
二、
构造函数模式
var obj = new Object();
复制代码
JS中基本数据类型建立值的两种方式
一、基于字面量方式建立出来的值是基本类型值 二、基于构造函数建立出来的值是引用类型(对应的实例都是对象类型的)
var num1=12;
var num2=new Number(12);
console.log(typeof num1);//=>"number"
console.log(typeof num2);//=>"object"
//=>num1是数字类的实例,num2也是数字类的实例,num2只是JS表达数字的方式之一,均可以使用数字类提供的属性和方法
复制代码
一、造成私有做用域(栈内存)
- 形参赋值
- 变量提高
二、
【构造函数执行独有】
在JS代码自上而下执行以前,首先在当前造成的私有栈内存中建立一个对象(建立一个堆内存:暂时不存储任何的东西),而且让函数中的执行主体(THIS)
指向这个新的堆内存
三、代码自上而下执行 ,
this.xxx=xxx这类操做都是在给建立的这个对象(this)增长属性名和属性值
四、
【构造函数独有】
代码执行完成,把以前建立的堆内存地址返回(浏览器默认返回),那么开始建立的对象其实就是当前这个类的一个实例。
function Fn(){
var n=10;
this.name=name;
this.age=age+n;
}
var f=new Fn();//=>在构造函数执行的时候,若是Fn不须要传递实参,咱们能够省略小括号,意思仍是建立实例(和加小括号没有区别)
复制代码
若是构造函数中,写了RETURN
1.return后面的代码都不执行
2.return后面什么也不写,返回的仍是this指向的那个对象了
3.return后面是一个基本数据类型值,返回的结果依然是this指向的对象
4.return后面是一个引用数据类型值,则会把默认返回的实例覆盖,此时接收到的结果就不在是当前类的实例了
function Fn(){
var n=10;
this.m=n;
return [12,23];
this.a=20;
}
var f=new Fn();
console.log(f)//=>[12,23]
------------------
function Fn(){
var n=10;
this.m=n;
return "珠峰";
this.a=20;
}
var f=new Fn();
console.log(f)//=> f={m:10}
复制代码
instanceof
检测某一个实例是否隶属于这个类(不能检测基本数据类型只能检测对象类型)
in
检测当前对象是否存在某个属性(无论当前这个属性是对象的私有属性仍是公有属性,只要有结果就是TRUE)
in能够检测到本身添加的属性和浏览器内置的私有属性以及原型上的内置的属性及手动添加的属性
let obj={name:"zhufeng",age:18};
"toString" in obj;//=>true
必定要注意添加双引号,属性名都是字符串或者数字,不加,浏览器会当作变量来处理
复制代码
hasOwnProperty
检测当前属性是否为对象的私有属性(不只要有这个属性,并且必须仍是私有的才能够)
浏览器内置的那些属性是不可枚举的,本身手动添加的是可枚举的
hasOwnProperty只能找到可枚举的私有属性
特殊状况:JS的类状图,除了第一排的那些类以及window,Winow(例如:Array,Number,Function,EvenTarget),使用hasOwnProperty·能够检测到浏览器内置的私有属性,其余是不行的
let box=document.getElementById("tabBox");
box.aa=200;
box.hasOwnProperty("className");//=>flase
box.hasOwnProperty("aa");//=>true
//className是box元素对象的内置属性
//aa是咱们本身手动给box元素对象添加的内置属性
-------------------
let obj={name:"zhufeng",age:9};
obj.hasOwnProperty("name");//=>true
obj.hasOwnProperty("toString");//=>false
//=>toString是原型上的公有属性
复制代码
练习:封装hasPubProperty方法
function hasPubProperty(obj,attr){
while(attr in obj){
if(obj.hasOwnProperty(attr)){
return false;
}
return true;
}
return false;
}
复制代码
函数和对象的分类
[函数] 普通函数、
类
(全部的类:内置类、本身建立的类、Date)
[对象] 普通对象、数组、正则、Math、arguments...
实例是对象类型的
(除了基本数据类型的字面量建立的值)prototype的值也是对象类型的
函数也是对象类型的
一、全部的函数数据类型都天生自带一个属性:prototype(原型),这个属性的值是一个
对象
,这个对象中存储了当前类供其实例调取使用的公有属性和方法,浏览器会默认给它(prototype)开辟一个堆内存
二、在浏览器给prototype开辟的堆内存中有一个天生自带的属性:
constructor
,这个属性存储的值是当前函数自己三、每个对象都有一个
_proto_
(原型链)的属性,这个属性指向当前实例所属类的prtototype(若是不能肯定它是谁的实例,都是Object的实例)全部函数数据类型都天生自带name和length两个属性,name存储的是该函数的函数名(字符串),length存储的是形参的数量
function fn(x,y,z){
console.log(fn.name);//=>"fn"
console.log(fn.length);//=>3
}
fn(10);
--------------
var f=function (x,y,z){
console.log(f.name);//=>"f"
console.log(fn.length);//=>3
};
f(10);
------------------
function fn(){
return function(x,y,z){
}
}
var a=fn();
console.dir(a);
//=>name:""
//=>length:3
复制代码
function Fn(){
var n=100;
this.AA=function(){
console.log("AA[私]")
};
this.BB=function(){
console.log("BB[私]")
};
}
Fn.prototype.AA=function(){
console.log("AA[公]")
};
var f1=new Fn;
var f2=new Fn;
复制代码
它是一种基于__proto__向上查找的机制。当咱们操做实例的某个属性或者方法的时候,首先找本身空间中私有的属性或者方法
1.找到了,则结束查找,使用本身私有的便可
2.没有找到,则基于__proto__找所属类的prototype,若是找到就用这个公有的,若是没有找到,基于原型上的__proto__继续向上查找,一直找到Object.prototype的原型为止,若是尚未,则操做的属性或者方法不存在,返回undefined
function Fn() {
this.x = 100;
this.y = 200;
this.getX = function () {
console.log(this.x);
}
}
Fn.prototype.getX = function () {
console.log(this.x);
};
Fn.prototype.getY = function () {
console.log(this.y);
};
var f1 = new Fn;
var f2 = new Fn;
console.log(f1.getX === f2.getX);//=>false
console.log(f1.getY === f2.getY);//=>true
console.log(f1.__proto__.getY === Fn.prototype.getY);//=>true
console.log(f1.__proto__.getX === f2.getX);//=>false
console.log(f1.getX === Fn.prototype.getX);//=>false
console.log(f1.constructor);//=>Fn
console.log(Fn.prototype.__proto__.constructor);//=>Object
f1.getX();//=>this:f1 =>console.log(f1.x); =>100
f1.__proto__.getX();//=>this:f1.__proto__ =>console.log(f1.__proto__.x); =>undefined
f2.getY();//=>this:f2 =>console.log(f2.y); =>200
Fn.prototype.getY();//=>this:Fn.prototype =>console.log(Fn.prototype.y); =>undefined
复制代码
私有属性
本身堆内存中存储的属性相对本身来讲是私有的
公有属性
本身基于__proto__找到的属性,相对本身来讲是共有的
function fun(){
this.a=0;
this.b=function(){
alert(this.a);
}
}
fun.prototype={
b:function (){
this.a=20;
alert(this.a);
},
c:function (){
this.a=30;
alert(this.a)
}
};
var my_fun=new fun();
my_fun.b();
my_fun.c();
复制代码
在实际项目基于面向对象开发的时候(构造原型设计模式),咱们根据须要,不少时候会重定向类的原型(让类的原型指向本身开辟的堆内存)
存在的问题
一、
本身开辟的堆内存中没有constructor属性,致使类的原型构造函数缺失(解决:本身手动在堆内存中增长constructor属性)
二、
当原型重定向后,浏览器默认开辟的那个原型堆内存会被释放掉(在没有被占用的状况下),若是以前已经存储了一些方法或者属性,这些东西都会丢失,因此内置类的原型不容许重定向到本身开辟的堆内存
,由于内置类原型上自带不少属性方法,重定向后都没了,这样是不被容许的,浏览器默认不让修改,就算修改了也没用。
function Fn(){
console.log("ok");
}
var f1=new Fn();
Fn.prototype={
name:"zhufeng",
age:18
}
var f2=new Fn();
复制代码
当咱们须要给自定义类的原型批量设置属性和方法的时候,通常都是让原型重定向到本身建立的对象中
function Fn={
}
Fn.prototype={
constructor:Fn,
aa:function(){
}
}
复制代码
练习题
function Fn() {
var n = 10;
this.m = 20;
this.aa = function () {console.log(this.m);}
}
Fn.prototype.bb = function () {console.log(this.n);};
var f1=new Fn;
Fn.prototype={
aa:function(){console.log(this.m+10);}
};
var f2=new Fn;
console.log(f1.constructor);//=>f Fn
console.log(f2.constructor);//=>f Object
f1.bb();//=>undefined
f1.aa();//=>20
f2.bb();//=>报错
f2.aa();//=>20
f2.__proto__.aa();//=>NaN
复制代码
设置内置方法,能够供它的实例调取使用
1.
手动增长的方法最好设置"my"前缀(本身定),防止把内置方法重写
//=>方法中的this通常都是当前类的实例(也就是咱们要操做的数组)
//=>操做this至关于操做ary,方法执行完成会改变原有数组
Array.prototype.myUnique=function myUnique(){
var obj={};
for(var i=0;i<this.length;i++){
var item=this[i];
if(item in obj){
this[i]=this[this.length-1];
this.length--;
i--;
continues;
}
obj[item]=item;
}
obj=null;
};
ary.myUnique();//=>this:ary
复制代码
练习:给Number类增长自定义方法plus和minus
plus:加一个数 minus:减一个数
Number.prototype.plus=function plus(n){
this=this+Number(n)//=>一、this是不能被赋值 二、此时左边this至关于变量,变量也不能使用关键字
return this+Number(n);
}
Number.prototype.minus=function minus(m){
return this-Number(m)
}
复制代码
保证每个方法执行返回的结果依然是当前类的实例,这样就能够继续调取方法使用了
ary.sort(function (a,b){return a-b};).reverse().pop();
//=>pop方法的返回值是删除的那一项
---------------------------------
ary.sort(function (a,b){return a-b;}).reverse().slice(2,7).join("+").split("+").toString().substr(2).toUpperCase();
复制代码
一、
普通函数
- 堆栈内存释放
- 做用域链
二、
类
- prototype:原型
- _proto_:原型链
- 实例
三、
普通对象
function Fn(){
var n=10;
this.m=100;
}
Fn.prototype.aa=function (){
console.log("aa");
}
Fn.bb=function (){
console.log("bb");
}
var f=new Fn();
复制代码
JQ这个类库中提供了不少方法,其中有一部分写在原型上,有一部分是把它当作普通对象来设置的
~function(){
function jQuery(){
return [JQ实例]
}
jQuery.prototype.animate=function(){};
jQuery.ajax=function(){};
window.jQuery=window.$=jQuery;
}();
$()//=>是JQ的一个实例,只能调取jQuery原型上的属性
$().ajax()//=>调取不了,那是jQuery类自己的属性
$().animate()//=>能够调取
$.ajax()//=>直接的对象键值对操做
$.animate()//=>对象上没有animate这个属性,这个属性和实例相关的原型上
复制代码
函数三种角色的运行机制图
function Fn() {
this.n = 100;
}
Fn.prototype.getN = function () {
console.log(this.n);
};
Fn.AA = 200;
var f=new Fn();
复制代码
function Foo() {
getName = function () {
console.log(1);
};
return this;
}
Foo.getName = function BBB() {
console.log(2);
};
Foo.prototype.getName = function AAA() {
console.log(3);
};
var getName = function () {
console.log(4);
};
function getName() {
console.log(5);
}
Foo.getName();//=>2 把Foo当作一个对象,找Foo的私有方法执行
getName();//=>4 执行全局下的GET-NAME
Foo().getName();//=>1 先把FOO当作普通函数执行,执行返回的结果(this为window)在调取GET-NAME执行
getName();//=>1 执行的依然是全局下的GET-NAME
new Foo.getName();//=>2 成员访问的优先级高于无参数列表new =>Foo.getName([F->2])=>new [F->2]()
new Foo().getName();//=>3 成员访问和有参数列表new优先级都是19,按照从左到右依次执行便可 =>创造Foo的一个实例f =>f.getName()=>3
new new Foo().getName();//=>3 先算new Foo(),创造一个实例f =>new f.getName() =>继续执行优先级高的成员访问 =>f.getName =>[F->3] =>new [F->3]()=>构造函数执行这个方法
复制代码
call/apply/bind用来改变某一个函数中的this关键字指向的 只要是函数均可以调取这三个方法 ####call方法
语法
:[fn].call([this],[param].....) fn.call():当前实例(函数Fn)经过原型链的查找机制,找到Function.prototype上的call方法,把call方法执行
call方法执行的步骤
一、
把call方法中的this的"this关键字"改成arguments[0]二、
把arguments中除了第一项之外的实参传递给this,执行this
fn.call();
Function.prototype.call=function (){
let param1=arguments[0],
paramOther=[];//=>把arguments中除了第一个之外的实参获取到
//=>call方法中的this:fn 当前要操做的函数(函数类的一个实例)
//把Fn中的关键字修改成param1=>把this(call中)的this关键字修改成param1
this.toString().replace("this",param1);
this(paramOther);
//=>把fn执行,把paramOther分别传递给fn
}
复制代码
细节
1.非严格模式下,若是参数不传,或者第一个传递的是null/undefined,this都指向window 2.在严格模式下,第一个参数是谁,this就指向谁(包括null/undefined),不传this是undefined
let fn=function (a,b){}
fn.call(obj,10,20);//=>this:obj a=10 b=20
fn.call(10,20);//=>this:10 a=20 b=undefined
fn.call();//=>this:window a=undefined b=undefined
fn.call(null);//=>this:window
fn.call(undefined);//=>this:window
复制代码
function fn1(){console.log(1);console.log(this);}
function fn2(){console.log(2);console.log(this);}
fn1.call(fn2);
fn1.call.call(fn2);
//fn1.call至关于Function类原型上的call方法
//call方法也是一个函数,因此也能够调取Function类原型上的call方法,那么至关于call方法(先命名为A)调取了call方法并执行
//call方法执行的第一步先把this中的“this”关键字改成fn2,也就是把A(call方法中的this改成fn2)
//call方法执行的第二步把this执行,也就是A(call方法)执行
//A执行也就是call方法又执行一遍,无实参,此时call方法中的this已经变为fn2了,那么第一步把this中的“this关键字”改成实参第一项(undefined),第二步把this执行也就是把fn2执行,无传参,输出2
非严格模式下执行,控制台会输出this是window,由于没有传参数,因此this指向window,切换到严格模式下控制台就会输出undefined了
Function.prototype.call(fn1)
//=>线找到call把它执行,call中的this是Function.prototype=>让F.P中的this变为FN1,而后让F.P执行,F.P是一个匿名函数也是一个空函数,执行没有返回值,也就是没有任何输出
Function.prototype.call.call(fn1);
//=>先找到CALL-AA把它执行,它中的THIS是F.P.CALL =>把F.P.CALL中的THIS修改成FN1,让F.P.CALL执行 =>F.P.CALL(CALL-AA)第二次把它执行(此时它里面的THIS已是FN1) =>这一次其实在CALL-AA中是让FN1执行 =>1
复制代码
规律
1.一个call,执行的是call前面的函数 2.两个及两个以上call,执行的是括号中传的第一个实参
语法和call基本如出一辙,惟一的区别在于传参方式,apply是把这些值放到一个数组或者类数组中,可是也至关于一个个的传递给函数(语法不同可是做用如出一辙)
fn.call(obj,10,20)
fn.apply(obj,[10,20])
//=>apply把须要传递给fn的参数放到一个数组(或者类数组)中传递进去,虽然写的是一个数组,可是也至关于给fn一个个的传递
复制代码
案例:获取数组中的最大值
第一种
给数组先排序(由大到小排序),第一项就是最大值
let ary=[12,23,4,5,2,67,89];
let max=ary.sort(function (a,b){return b-a;})[0];
console.log(max);
复制代码
第二种
假设法:假设第一个值时最大值,依次遍历数组中后面的每一项,和假设的值进行比较,若是比假设的值要大,把当前项赋值给MAX
let ary=[12,3,4,2,32,34,12,45,23,4,5,34];
let max=ary[0];
for(let i=1;i<ary.length;i++){
let item=ary[i];
item>max?max=item:null;
}
console.log(max);
复制代码
第三种
基于Math.max和eval方法实现
let ary=[12,3,4,2,32,34,12,45,23,4,5,34];
ary=ary.toString()
let max=eval("Math.max("+ary+")");
console.log(max);
//=>Math.max("12,23,1,24"),此时会把"12,23,1,24"当作一项,而后调用Number()方法,转换为数字,因此会输出NaN
//=>Math.max(12,23,45,"89")=>89
复制代码
知识点扩充:括号表达式
用小括号包起来,里面有不少项(每一项用逗号分隔),最后只获取最后一项的内容(可是会把其余的项也都过一遍)
(function(){console.log(1);},function(){console.log(2);})();//=>2
-----------------------
let a=1===1?(12,23,14):null;//=>14
复制代码
不建议你们过多使用括号表达式,由于会改变this
let fn=function(){console.log(this);}
let obj={fn:fn};
(fn,obj.fn)();//=>执行的是第二个obj.fn,可是方法中的this是window而不是obj
(obj.fn)();//=>this:obj
复制代码
第四种
基于Math.max和apply方法实现
let ary=[12,3,4,2,32,34,12,45,23,4,5,34];
let max=Math.max.apply(null,ary);
console.log(max);
//=>Math.max是一个函数,因此能够调取Function.prototype上的apply方法
//=>apply方法:
//1.把this中的“this关键字”改成arguments[0]
//2.把this执行,并把arguments[1]...这些实参传递给this(以数组的形式传参),虽然是数组的形式传参,可是也是一个一个的传递
复制代码
第五种
基于ES6中的展开运算符
let max=Math.max(...ary);
console.log(max);
复制代码
语法和call如出一辙,惟一的区别在于当即执行仍是等待执行
fn.call(obj,10,20)
//=>改变fn中的this,而且把fn当即执行
fn.bind(obj,10,20)
//=>改变fn中的this,此时的fn并无执行
复制代码
案例
需求:点击的时候执行fn,让fn的this是obj
let fn=function (a,b){
console.log(this);
};
let obj={name:"OBJ"};
document.onclick=fn;//=>this:document
document.onclick=fn.call(obj);//=>虽然this确实改成obj了,但过后绑定的时候就把fn执行了(call是当即执行函数),点击的时候执行的是fn的返回值undefined
document.onclick=fn.bind(obj);//=>bind属于把fn中的this预处理为obj,此时fn没有执行,当点击的时候才会把fn执行
复制代码
案例:定时器
//=>设置一个定时器,把一个方法赋值给定时器,设定等待的时间,当到达时间后,把方法执行(1000MS)
setTimeout(function(){console.log(1);},1000);
------------------
setTimeout(fn.call(obj,10,20),1000);
//=>call是当即执行,在设置定时器的时候,就把fn执行了(此时也基于call改变了this),把fn执行的返回值赋值给定时器,1000ms后执行的是fn的返回值
--------------------
setTimeout(fn.bind(obj,10,20),1000);
复制代码
按照一个数据值的结构,快速解析获取到其中的内容,真实项目中通常都是针对于数组或者对象进行解构赋值
让等号左边出现和右边相同的数据结构,左边能够建立一些变量快速获取到右侧对应位置的值
let ary=[1,2,3];
let [a,b,c]=ary;
console.log(a,b,c);
复制代码
//=>a和b互换位置
let a=12,
b=13;
[a,b]=[b,a];
console.log(a,b);
复制代码
得到数组中的某一项
let ary=[1,2,3,4,5];
let [,b,,c,d=0]=ary;
console.log(b,c,d)//=>2,4,0
//=>d默认赋值为0,若是当前项没有解构到任何值,给一个初始值;若是d不赋值默认值的话获得的应该是undefined
-------------
let ary=[1,2,3,4,5];
let [a]=ary;
console.log(a);//=>1
复制代码
拓展运算符
"..."称之为拓展运算符:除了前面之外的项,都放在一个数组中 剩余运算符只能处于结构中最后的位置
//=>需求:获取第一项,把剩下的项做为一个数组返回
let ary=[12,23,44,54,34];
let [a,...b]=ary;
console.log(a,b);//=>12 [23,44,54,34]
--------------------
let ary=[12,23,44,54,34];
let [a,...b,c]=ary//=>Uncaught SyntaxError: Rest element must be last element
复制代码
function fn(context,...arg){
console.log(context,arg);
//=>arg是个数组,arguments是类数组,这样arg就能够用数组的方法
}
fn(obj,10,20,30);
复制代码
展开运算符
把数组(对象/类数组)中的每一项展开
let ary=[1,23,12,45]
let max=Math.max(...ary);
console.log(max)//=>45
----------------
let ary=[1,2,3,4,5];
let [...newAry]=ary;//=>数组克隆
let newAry=ary.slice();
-----------------
let ary=[1,2,3,4,5];
let b=[...ary,1,2,3];
console.log(b);//=>[1,2,3,4,5,1,2,3]
复制代码
对象解构赋值默认状况下要求:左边
变量名
和对象中的属性名
一致才能够
let obj={name:"xxx",age:12,sex:0};
let {name,age}=obj;
console.log(name,age);//=>"xxx",12
let {sex}=obj;
console.log(sex);//=>0
复制代码
let data={
name:"xxx",
age:27,
score:[110,130,20]
};
let {name,score:[chinese,,english=0]}=data;
console.log(name,chinese,english);//=>("xxx",110,20)
复制代码
起别名
给解构的属性名起别名做为咱们使用的
变量
let obj={name:"xxx",age:12,sex:0};
let {age:ageAA}=obj;
console.log(ageAA);//=>12
console.log(age);//=>Uncaught ReferenceError: age is not defined
复制代码
给不存在的属性设置默认值
let obj={name:"xxx",age:12,sex:0};
let {friend=0}=obj;
console.log(friend);//=>0
--------------
let obj={name:"xxx",age:12,sex:0};
let {hobby:myHobby="lanqiu"}=obj;
let {name:myName="aa"}=obj;
console.log(myName, myHobby);//"xxx",12
复制代码
剩余运算符
剩余运算符只能处于结构中最后的位置
let obj={name:"zhuefn",age:19,sex:0,hobby:"bas"};
let {name:a,age:b,...c}=obj;
console.log(a, b, c);
//=>"zhuefn",19,{sex:0,hobby:"bas"}
----------------
let obj={name:"zhuefn",age:19,sex:0,hobby:"bas"};
let {name:a,age:b,...c,d}=obj;
console.log(a, b, c,d);//=>Uncaught SyntaxError: Rest element must be last element
复制代码
//=>把传递的对象解构了(不传递值,默认赋值为空对象),解构的时候若是某个属性不存在,咱们赋值默认值
let fn=function ( { name:myName="zhufeng", age:myAge=0 }={} ){
console.log(myName, myAge);
console.log(name,age);//=>name至关于window.name(name是window这个实例自带的属性,属性值为空)
//=>报错:age is not defined
};
fn({name:"xxx",age:18});
复制代码
展开运算符
let obj={name:"xxx",age:20};
let newObj={...obj,sex:0};//=>{name:"xxx",age:20,sex:0} 把原有的对象克隆一份
复制代码
案例:任意数求平均数
需求:编写一个方法fn,实现任意数求平均数(去除数字中的最大和最小,而后在算平均数,保留小数点后面两位)
let fn=function(){
let ary=[];
for(let i=0;i<arguments.length;i++){
let item=arguments[i];
ary.push(item);
}
ary.sort(function(a,b){return a-b});
ary.pop();
ary.shift();
let average=eval(ary.join("+")/ary.length);
return average.toFixed(2);
}
复制代码
重写slice方法
mySlice方法,克隆数组
Array.prototype.mySlice=function mySlice(){
let curary=[];
for(let i=0;i<this.length;i++){
let item=this[i];
curary.push(item);
}
return curary
}
复制代码
arguments借用数组方法
前提类数组和数组相似,都有length和索引
let fu=function (){
let ary=[].slice.call(arguments,0);
ary.sort(function(a,b){return a-b});
ary.pop();
ary.shift();
let average=eval(ary.join("+")/ary.length);
return average.toFixed(2);
}
复制代码
let fn=function (){
let arg=arguments;
[].sort.call(arg,function(a,b){return a-b;});
[].pop.call(arg);
[].shift.call(arg);
return eval([].join.call(arg,"+"))/arg.length;
}
复制代码
let fn=function (){
let arg=arguments;
arg.__proto__=Array.prototype;
//=>在IE浏览器中屏蔽开发者使用__proto__
arg.sort(function (a,b){return a-b});
arg.pop();
arg.shift();
return eval(arg.join("+"))/arg.length;
}
复制代码
使用展开运算符
let fn=function (...ary){
ary.sort(function (a, b) {
return a - b;
}).pop();
ary.shift();
return (eval(ary.join('+')) / ary.length).toFixed(2);
}
复制代码
let fn=function(){
let ary=[...arguments];
ary.sort(function (a, b) {
return a - b;
}).pop();
ary.shift();
return (eval(ary.join('+')) / ary.length).toFixed(2);
}
复制代码