js代码执行分为两个阶段:预编译和代码执行
**预编译:**
声明提升:加var的变量以及function声明的函数都会提升,提升到代码段的最前面。
函数内部的局部变量,提升到函数体的最前面。
注意:1、变量的提升仅仅提升声明,函数是提升整个函数体。
代码执行:自上而下执行
全局变量和局部变量的分界点是函数!
没有加var的变量都是全局变量 不管是函数里面还是函数外面
1.形参和变量声明
2.实参值赋给形参
3.寻找函数声明
4.执行函数
**全局执行上下文:**
当全局代码执行时,就会产生一个全局的执行上下文,EC(G);数据保存在VO中(变量对象)
**局部执行上下文:**
当函数代码执行时,就会产生一个局部的执行上下文,EC(Fn)。只要调用一个函数,就会产生一个局部执行上下文。调用100个函数,就会产生100个执行上下文。执行上下文里会保存一些数据
内存分为栈堆,基本数据类型存在栈 引用数据类型存在堆,栈里存的引用类型的地址
形参就是函数内部加var的变量
当执行上下文出栈后就被销毁(数据存储所占用的内存空间都要被释放)
var a=b=2--->var a=2;b=2;
##加var的变量和不加var的变量区别
1.加var的变量在预编译期间会提升,不加var的变量不会提升。
2.不管有没有var,创建的变量都会放在GO中,都可以通过window.XX 拿到变量。
3.加var的变量可能是全局变量也可能是局部变量,不加var的只能是全局变量。
4.加var的局部变量不会作为window的属性
let不允许重复声明 const不允许修改
##作用域
局部:函数内部 AO
全局:函数外部
- GO 全局对象
- ECS 执行上下文栈 先进后出(杯子)
- ES(G)全局执行上下文,当执行全局代码时就会产生全局执行上下文(鸡蛋,放入杯子中)
- EC(fn)当函数代码执行时,就会产生一个局部的执行上下文
如果全局函数在if中,仅仅提升声明,不提升赋值。
如果条件成立,进来的第一件事就是赋值
```
//预编译:提升
//首先var fn提升了 fn的值是undefined
//下面的fn函数整体(函数名+函数体)也要提升
//如果提升过了,就不在提升,函数名不在提升
//函数体提升 fn的值由undefined变成函数体
console.log(fn); //打印函数
var fn=100;
function fn(){
console.log("fn...")
}
```
函数没有返回值,默认返回undefined
形成闭包的条件:是一个不会释放栈内存空间的EC(局部的执行上下文)
闭包指的是栈空间
当内部函数被返回到外部并保存时,一定会产生闭包,闭包会产生原来的作用域链不释放,过度的闭包可能会导致内存泄漏或加载过慢.
在一个函数里面嵌套一个函数,并且里面的函数引用了外部函数的变量,这样就形成了闭包
闭包的前提是函数嵌套
一个不会释放内存的栈空间就是一个闭包
闭包好处
1.保存变量的值(延长一个函数内部变量的生命周期)
2.由于x是函数内部的变量,外部不能访问。对x有保护作用
```
// 闭包实现累加器
function test(){
var num=0;
function add(){
console.log(++num);
}
return add;
}
var add=test();
add(); //1
add(); //2
//闭包实现缓存机制
function myClass(){
var students=[];
var operations={
join:function(name){
students.push(name);
console.log(students);
},
leave:function(name){
var idx=students.indexOf(name);
if(idx>-1){
students.splice(idx,1);
}
console.log(students);
}
};
return operations;
}
var obj=myClass();
obj.join('1111');
obj.join('2222');
obj.leave('1111');
```
作用域链:数据的查找机制
IIFE:
立即调用函数表达式
(function(){})();
(function(){}());
```
var f=function(){};//普通函数表达式
function g(){};//函数声明、定义
```
一定是表达式才能被执行符号执行
var num=(1,2);返回2 ()里面有, 只返回,后面的值
(function b(){}) 带()代表是表达式,忽略函数名字(b)
this:
1.如果this出现在普通函数中,通过window.f调用函数,那么this就表示window,主要是看.前面是谁
2.事件绑定中,监听器中的this表示事件源
3.对象是属性的无序集合,在一个对象中也可以有函数,如果一个函数出现在了对象中(方法),那么this表示这个对象
4.在IIFE中,this表示window
5.只有代码执行时才知道this指向
in:
也是一个运算符,判断一个属性是否属于某个对象,不管是共有属性还是私有属性
new的原理
1.内部创建一个空对象
2.内部的this指向这个空对象
3.返回这个空对象
原型和原型链
1.每一个对象中必定有一个属性叫__proto__,指向构造器的原型函数
2.每一个构造器上,都有一个属性叫prototype,也是指向原型对象上,原型对象上通常会放有公共属性
3.每一个原型对象上,都有一个constructor属性,指向他所对应的构造器
4.当我们在查找一个对象上的属性时,先在自己的私有属性中找,如果找不到,就沿着__proto__去原型对象中找,如果原型对象中找不到,就再沿着__proto__去它的原型对象的原型对象中找,直到null。如果找不到就报错undefined。(原型链)
当我们修改了原型对象,需要手动修改constructor的指向
不是所有的对象都继承于object.prototype Object.create(null)
原型链的顶端是object.prototype
==会自动调用toString方法
.的优先级高于new
call:改变函数中this的指向
方法.call(obj) 1.让this指向obj 2.让方法执行
apply:和call的作用是一摸一样的,只是穿参的形式不一样
当函数的参数大于3个时,使用call性能更好
callee在哪个函数里面值得是哪个函数本身,通过arguments.callee调用函数自身
caller返回当前被调用函数的函数引用
```
function say(a,b){
console.log(a,b)
};
let obj1={name:"aaa"};
let obj2={name:"bbb"};
say.call(obj1,1,2);
say.apply(obj2,[2,3]);
```
typeof:
object/string/number/boolean/function/undefined
优点:对基本数据类型和函数判断很准确;是运算符 简单
缺点:对引用数据类型判断不准确,结果全是object
使用call优化
```
function type(data){
return Object.prototype.toString.call(data)
}
```
继承:
原型继承 :
1.继承父类的共有属性和私有属性作为子类的共有属性;
2.核心代码:Son.prototype=new Father(); Son.prototype.constructor=Son;
call继承:
1.继承父类的私有属性作为子类的私有属性
2.function Son(){
Father.call(this)
}
组合继承:call+原型
1.继承父类的共有和私有属性,作为子类的共有属性 原型
2.继承父类的私有属性,作为子类的私有属性 call
3.父类的私有属性被继承了两次,当我们去访问一个属性时,会先访问私有属性
只继承父类的共有属性,作为子类的共有属性
var Fn=function(){}; //空函数 中介作用
fn.prototype=Father.prototype; //改变Fn的原型对象,和Father共享一个原型对象
Son.prototype=new Fn();
```
//圣杯模式继承
function Teacher(){
this.name='Mr.Li';
this.tSkill='JAVA';
}
Teacher.prototype={
pSkill:'JS'
}
var t=new Teacher();
console.log(t);
function Student(){
this.name='Mr.Wang';
}
//中间缓冲构造函数,作用是原型继承的时候与原本的原型隔离开
// function Buffer(){}
// Buffer.prototype=Teacher.prototype;
// var buffer=new Buffer();
// Student.prototype=buffer;
// //此处只改变student的原型,不影响teacher的原型
// Student.prototype.age=18;
inherit(Student,Teacher);
var s=new Student();
console.log(s);
//函数封装
function inherit(Target,Origin){
function Buffer(){};
Buffer.prototype=Origin.prototype;
Target.prototype=new Buffer();
Target.prototype.constructor=Target;
Target.prototype.super_class=Origin;
}
```
类数组
把{}变成[] 需要在对象中加入splice,并且继承数组原型的splice方法,还要有length属性
Object.prototype.toString.call('') 增强版typeof
```
//类数组面试题以及原理解析
var obj={
'2':3,
'3':4,
'length':2,
'splice':Array.prototype.splice,
'push':Array.prototype.push
}
obj.push(1);
obj.push(2);
console.log(obj);
// {
// '2': 1,
// '3': 2,
// length: 4
// }
//push原理解析
// Array.prototype.push=function(elem) {
// this[this.length]=elem;
// this.length++;
// }
```
Array.from:把伪数组变成真实数组
数组的下标 从前面开始数是从0开始的 从后面开始是从-1开始的
可迭代对象: 可以使用for of 进行遍历
数组 字符串 Set Map
不可迭代:
object
push/unshift(最后/最前面 添加元素) 返回值,是执行了方法以后的数组长度 修改原数组
pop/shift(删除最后/最前面一位元素,没有参数)返回值 删除剪切的元素 修改原数组
reverse (倒序) 返回值,改变后的数组 修改原数组
splice(开始项的下标,剪切长度,剪切以后的最后一位添加的元素) 返回值 剪切的元素 修改原数组
sort(按照ascii码排序)返回值 排序以后的结果 修改原数组
concat(合并数组) 返回值改变后的数组 新建数组,不影响原数组(新建)
slice(截取起始下标,截取结束下标之前一位)返回值是截取的数组,不影响原数组(新建)
join(数组转成字符串)返回值是转换之后的字符串,不影响原数组(新建)
split(按什么分隔的,分隔的长度) 返回值转换之后的数组,不影响原数组(新建)
```
//1.参数a,b
//2.返回值:正值,b排前面
// 负值,a排前面
// 0,保持不动
var arr=[27,39,5,7];
arr.sort(function(a,b) {
//return a>b?1:-1
return a-b //升序 b-a降序
})
console.log(arr);
//随机排序
//Math.random()->0-1之间的数,不包含0和1 开区间
var arr=[1,2,3,4,5,6,7,8];
arr.sort(function(a,b){
//var rand=Math.random();
//return rand-0.5>0?1:-1;
return Math.random()-0.5;
})
console.log(arr);
```
find方法:
在一个数组中,找出第一个符合条件的元素。
返回值:返回找到的元素 如果说没有找到,就返回und
let arr = [1,2,3,4,5]
let res = arr.find(item=>{ //在arr中,找出大于2的元素
return item>100
})
console.log(res)
--------------------------------------------------------
findIndex方法:
在一个数组中,找出第一个符合条件的元素的索引。
返回值:返回找到的元素的索引 如果说没有找到,就返回-1
let arr = [1,2,3,4,5]
let res = arr.find(item=>{ //在arr中,找出大于2的元素
return item>100
})
console.log(res)
--------------------------------------------------------
includes方法:
判断一个数组中是否包含某个值,如果包含,返回true。
--------------------------------------------------------
forEach方法:
遍历数组 声明式编程
--------------------------------------------------------
map方法:
对数组中的每一项元素进行加工,返回一个加工后的新数组。
let arr = [1,2,3,4,5]
let res = arr.map(item=>{
return item*100
})
console.log(res)
--------------------------------------------------------
filter方法:
filter本意是过滤的意思。过滤出满足我们条件的元素,形成一个新的数据。
--------------------------------------------------------
some方法:
遍历数组中每一项,有一项返回true,那么就停止遍历,整体的结果就是ture
--------------------------------------------------------
every方法:
遍历数组中每一项,所有项返回true,整体结果才为true
每个函数作用域链上都有GO,函数自己的AO排在第一位,GO排第二位
var i=1;
var a=i++; //a=5 解析:var a=i;i=i+1;
var b=++i; //b=2 解析:i=i+1;var b=i;
window.a||(window.a='1');console.log(a);
//结果是a 有()先看(),a=1,然后再去看或者window.a
1.形参和变量声明
2.实参值赋给形参
3.寻找函数声明
4.执行函数
undefined=>false
数组截断方法
```
var arr=[1,2,3,4,5];
arr.length=3;
console.log(arr);// [1,2,3]
```
原始值没有属性和方法
```
;(function(){
var Compute=function(opt){
this.x=opt.firstNum || 0;
this.y=opt.secondNum || 0;
}
Compute.prototype={
plus:function(){
return this.x+this.y;
},
minus:function(){
return this.x-this.y;
},
mul:function(){
return this.x*this.y;
},
div:function(){
return this.x/this.y;
}
}
window.Compute=Compute;
})();
var compute=new Compute({
firstNum:1,
secondNum:2
});
var res=compute.plus();
```
```
function Car(){};
var car=new Car();
function Person(){};
var p=new Person();
console.log(car instanceof Car);//true
console.log(car instanceof Object);//true
console.log([] instanceof Array);//true
console.log([] instanceof Object);//true
console.log({} instanceof Object);//true
```
```
window.onload=function(){
init();
}
function init(){
console.log(initFb(10));
console.log(initDiv(100));
}
var initFb=(function(){
function fb(n){
if(n<=0){
return 0;
}
if(n<=2){
return 1;
}
return fb(n-1)+fb(n-2);
}
return fb;
})();
var initDiv=(function(){
function div(n){
var arr=[];
for (let i = 0; i < n; i++) {
if(i%3==0||i%5==0||i%7==0){
arr.push(i);
}
}
return arr;
}
return div;
})();
```
```
function deepClone(origin,target){
var target=target||{},
toStr=Object.prototype.toString,
arrType='[object Array]';
for (const key in origin) {
if (origin.hasOwnProperty(key)) {
if(typeof(origin[key])==='object'&&origin[key]!==null){
if(toStr.call(origin[key])===arrType){
target[key]=[];
}else{
target[key]={};
}
deepClone(origin[key],target[key]);
}else{
target[key]=origin[key];
}
}
}
return target;
}
```
```
var name ='222';
var a={
name:'111',
say:function(){
console.log(this.name);
}
}
var fun=a.say;
//var fun=function(){
// console.log(this.name);
// }
fun();//this指向window 打印222
a.say();//this指向a 打印111
var b={
name:'333',
say:function(fun){
fun();
//此处是fun函数调用,fun函数是全局的,所以this还是指向window->222
//+function(){
// console.log(this.name);
// }()
}
}
b.say(a.say);//222
b.say=a.say;
b.say(); //333
```
```
function Foo(){
getName=function(){
console.log(1);
}
return this;
}
//相当于给函数对象增加属性
Foo.getName=function(){
console.log(2);
}
Foo.prototype.getName=function(){
console.log(3);
}
var getName=function(){
console.log(4);
}
//预编译的时候会变量提升
function getName(){
console.log(5);
}
Foo.getName(); //2
getName();//4
Foo().getName();//1
getName();//1
new Foo.getName();//.的优先级高于new 相当于Foo.getName() new2没有意义 还是2
new Foo().getName();//()的优先级比.大 实例化了函数,函数本身没有getName属性,故去找原型 3
new new Foo().getName(); //同上 new3没有意义 还是3
```