一些js知识| 青训营笔记

84 阅读7分钟

这是我参与「第四届青训营 」笔记创作活动的的第21天

js运行三部曲

  • 语法分析

  • 预编译

  • 解释执行

语法分析

通篇扫描,看是否有语法错误,不执行

预编译

前奏

  1. imply global 暗示全局变量:即任何变量,如果变量未经声明就赋值,此变量就为全局对象window所有 eg:a = 123;
eg:var a = b = 123;
//b未声明,window.b可以访问
//a声明了,window.a不可访问
  1. 一切声明的全局变量,全是window的属性
var a = 123;
//相当于
window {
    a : 123
}

window 就是全局

下一次访问a时,会在 window 里找有没有a

在全局的范围内访问 a 就是访问window.a

var a = 123;
console.log(a) -->  console.log(window.a)

预编译

函数声明整体提升

系统总是会把函数声明提到程序逻辑最前面

变量 声明提升

变量声明:var a; 系统会把变量声明提到程序最前面

预编译发生在函数执行的前一刻

函数体系里的预编译:

  1. 创建AO对象(Activation Object 执行期上下文)
  2. 找形参和变量声明,将变量和形参名作为AO属性名,值为undefined
  3. 将实参值和形参统一
  4. 在函数体里面找函数声明,值赋予函数体
function fn(a) {
    console.log(a);     //out: function a() {}

    var a = 123;        //预编译时将`var a`提前,执行`a = 123;`赋值语句

    console.log(a);     //out: 123

    function a() {}

    console.log(a);     //out: 123

    var b = function () {}

    console.log(b);     //out: function () {}

    function d () {}
}

fn(1);

// 预编译过程
1.AO{

}

2.AO{
    a : undefined,
    b : undefined,
}

3.AO{
    a : 1,
    b : undefined,
}

4.AO{
    a : function a() {},
    b : undefined,
    d : function d () {}
}

//执行过程
AO{
    a : 123,
    b : undefined,
    d : function d () {}
}
AO{
    a : 123,
    b : function () {},
    d : function d () {}
}

练习

function test(a,b) {
    console.log(a);
    c = 0;
    var c;
    a = 3;
    b = 2;
    console.log(b);
    function b () {}
    function d () {}
    console.log(b);
}
test(1);
/*out: 1
       2
       2
*/
AO {
    a :undefined --> 1
    b :undefined --> function b () {}
    c :undefined
    d :undefined --> function d () {}
}
AO {
    a :1 --> 3
    b :function b () {} --> 2
    c :undefined --> 0
    d :function d () {}
}
function test (a,b) {
    console.log(a);
    console.log(b);
    var b = 234;
    console.log(b);
    a = 123;
    console.log(a);
    function a() {}
    var a;
    b = 234;
    var b = function () {}
    console.log(a);
    console.log(b);
}
test(1);
/*out:  ƒ a() {}
        undefined
        234
        123
        123
        ƒ () {}
*/

全局预编译:

  1. 创建GO对象(Global Object 执行期上下文)
  2. 找变量声明,将变量作为GO属性名,值为undefined
  3. 找函数声明,值赋予函数体

GO === window

GO {
    test : undefined --> function test () { ··· } --> 123
}

console.log(test);
function test (test) {
    console.log(test);
    var test = 234;
    console.log(test);
    function test() {}
}

AO {
    test : undefined --> 1 --> function test() {} --->234
}

test(1);
var test = 123;

/*out:  function test () { ··· } 
        function test () {}
        234
*/

练习

var global = 100;
function fn() {
    console.log(global);        //out: 100
}
fn();
GO {
    golbal : undefined ---> 100
    fn : function () { ··· }
}
global = 100;
function fn() {
    console.log(global); 
    global = 200;
    console.log(global); 
    var global = 300;
}
AO {
    global : undefined ---> 200 ---> 300
}
fn();
var global;

//out: undefined
//     200
GO {
    a :undefined ---> 10
    test : function () { ··· }
    c : 234
}

function test () {
    console.log(b);
    if(a) {
        var b = 100;
    }
    c = 234;
    console.log(c);
}
var a;
AO {
    b : undefined
}
test();
a = 10;
console.log(c);

//out:undefined
//     234
//     234
	GO {
        a : undefined --> 100
        demo : function demo () {}
        f : 123
    }
    a = 100;
    function demo(e) {
        function e () {}
        arguments[0] = 2;
        console.log(e);
        if(a) {
            var b = 123;
            function c () {
                //猪都能做出来
            }
        }
        var c;
        a = 10;
        var a;
        console.log(b);
        f = 123;
        console.log(c);
        console.log(a);
    }
    var a;
    
    AO {
        a : undefined --> 10
        b : undefined
        c : undefined
        e : undefined --> 1 --> function e () {} --> 2
    }
    demo(1);
    console.log(a);
    console.log(f);

/*	2
	undefined
	undefined (function c() {}) ----if里不能声明function
	10
	100
	123
*/	

作用域

  1. 作用域定义:变量(变量作用域又称上下文)和函数生效(能被访问)的区域

  2. 全局、局部变量 函数里的是局部变量,外面的是全局变量 函数内部可以使用函数外面的变量

  3. 作用域的访问顺序 函数里面可以访问函数外的变量,但函数外部无法访问函数内部的变量

作用域精解 运行期上下文:当函数执行时,会创建一个称为执行期上下文的内部对象。 一个执行期上下文定义了一个函数执行时的环境,函数每次执行时对应的执行上下文都是独一无二的,所以多次调用一个函数会导致创建多个执行上下文,当函数执行完毕,它所产生的执行上下文被销毁。

查找变量:从作用域链的 顶端 依次向下查找

[[scope]]:每个JavaScript函数都是一个对象,对象有属性,对象中有些属性我们可以访问,但有些不可以,这些属性仅供JavaScript引擎存取,[[scope]]就是其中一个。[[scope]]指的就是我们所说的作用域,其中存储了运行期上下文的集合。

作用域链

[[scope]]中所存储的执行期上下文对象的集合,这个集合呈链式连接,我们把这种链式连接叫做作用域链

    function a () {
        function b () {
            var b = 234;
        }
        var a = 123;
        b();
    }
    var global = 100;
    a();

    // a被定义时    a.[[scope]] --> 0 : GO
    // a执行时      a.[[scope]] --> 0 : aAO
    //                              1 : GO

    // b被定义时      b.[[scope]] --> 0 : aAO
    //                               1 : GO
    // b执行时        b.[[scope]] --> 0 : bAO
    //                                1 : aAO
    //                                2 : GO

在这里插入图片描述 在这里插入图片描述 在这里插入图片描述在这里插入图片描述

function a() {
    function b() {
        function c() {

        }
        c();
    }
    b();
}
a();

在这里插入图片描述

闭包

当内部函数被保存到外部时,将会生成闭包。闭包会导致原有作用域链不释放,造成内存泄漏

    function a() {
        function b() {
            var bbb = 234;
            document.write(aaa);
        }
        var aaa = 123;
        return b;
    }
    var glob = 100;
    var demo = a();
    demo();
// out: 123

在这里插入图片描述

    function test () {
        var arr = [];
        for(var i = 0;i < 10;i ++){
            arr[j] = function() {
                console.log(j);
            }
            
        }
        return arr;
    }
    var myArr = test();
    for(var j = 0;j < 10;j ++){
        myArr[j]();
    }
    // 因为i在test函数的AO里,arr里的函数共用一个i,所以执行后打印10个10
    // 解决方法 :用闭包解决闭包,在函数自己的AO里可以取到想要的值
    // 使用立即执行函数,将i的值传参进去
        for(var i = 0;i < 10;i ++){
            (function (j) {
                arr[j] = function() {
                console.log(j);
                }
            }(i))
        }

不用return实现

    var demo;
    function test () {
        var x = 123;
        function a () {
            console.log(x)
        }
        demo = a;
    }
    test();
    demo();
    

闭包的作用

  1. 实现共有变量 eg:函数累加器
function add() {
    var num = 0;
    function a () {
        console.log(++ num);
    }
    return a;
}
var myAdd = add();
myAdd();
myAdd();
myAdd();
  1. 可以做缓存(存储结构 eg:

在这里插入图片描述

  1. 可以实现封装,属性私有化 eg:Person();
function Deng(name,wife) {
    var prepareWife = 'xiaozhang';

    this.name = name;
    this.wife = wife;
    this.divorce = function () {
        this.wife = prepareWife;
    }
    this.changPrepareWife = function (target) {
        prepareWife = target;
    }
    this.sayPrepareWife = function() {
        console.log(prepareWife);
    }
}
var deng = new Deng ('deng','xiaoliu');
  1. 模块化开发,防止污染全局变量

闭包的防范

闭包会导致多个执行函数共用一个公有变量,如果不是特殊需要,应尽量防止这种情况发生

原型

定义

原型是function对象的一个属性,它定义了构造函数制造出的对象的公共祖先。通过该构造函数产生的对象,可以继承该原型的属性和方法。 原型也是对象

//Person.prototype ----原型  产生时就已定义好
//Person.prototype = {}   是祖先
Person.prototype.name = 'hehe';
function Person() {

}
var person = new Person();

自己和原型的属性撞了,用自己的

  1. 利用原型特点和概念,可以提取共有属性

  2. 对象如何查看原型 —— > 隐式属性__proto__

xxx:隐式命名规则,系统命名 _xxx :私人的属性,提醒他人尽量别碰这个属性

Person.prototype.name = 'abc';
function Person() {
    //var this = {
    //      __proto__ : Person.prototype    存放队形的原型,将原型和自己连接到一起
    //};
}
var person = new Person();
person.name;    //当对象没有这个属性时,就会在__proto__里找原型里是否有该属性

构造函数内部原理

  1. 在函数体最前面隐式的加上this = {}
  2. 执行this.xxx = xxx;
  3. 隐式的返回this this不为空
var this = {
    //      __proto__ : Person.prototype  (可以修改
Person.prototype.name = 'sunny';
function Person () {

}
var person = new Person();
Person.prototype = {
    name : 'cherry';
}
//person.name : sunny;
//
//Person.prototype.name 是新加属性
//而 这种写法改了原型

var obj = {name : 'a'};
var obj1 = obj;
obj = {name : 'b'};
//obj1.name = 'a';
//obj.name = 'b';


 
Person.prototype.name = 'sunny';
function Person () {

}
Person.prototype = {
    name : 'cherry';
}
var person = new Person();

//person.name : cherry;
  1. 对象如何查看对象的构造函数 —— > constructor (可以自己更改

原型的增删改查

Person.prototype.lastName = "Deng";
function Person (name) {
    this.name = name;
}
var person = new Person('xiaoming');

person.lastName = 'Zhang';  //只能在person的属性上改,会给它加一个属性lastName
Person.prototype.lastName = 'Wang'; //可修改原型属性

//删除
delete Person.prototype.lastName;

//
Person.prototype = {
    name : "BMW";
}

原型链

  1. 如何构成原型链

原型还有原型 原型链的连接点是__proto__ 原型链的终端为Object.prototype

  1. 原型链上属性的增删改查

本人修改,后代无法更改

特例:修改 引用值可以修改 在这里插入图片描述

son.num ++;
//101
//son.num = son.num + 1;
father.num 
//100
    Person. prototype = {
        name : "a",
        sayName : function () {
            console.log (this.name) ;
        }
    }
    function Person ( ) {
    
    }
    var person = new Person() ;
    person.sayName()    // a
    // a.sayName( )
    // sayName里面的this指向是,谁调用的这个方法,this就是指向谁
    
  1. 绝大多数对象的最终都会继承自Object.prototype Object.creat(null)无原型

  2. Object.create(原型)

//var obj = Object.create(原型)
Person.prototype.name = 'sunny';
function Person() {

}
var person = Object.create(Person.prototype);

Math.ceil()向上取整 Math.floor()向下取整 eg:

Math.ceil(123.234) ----> 124
Math.floor(123.99) ----> 123

Math.random()随机产生0-1之间的数 随机产生0-100之间的数 Math.floor(Math.random()*100); Math.random().toFixed(2)*100会有精度问题

  • 可正常计算的范围 : 小数点 前16位,后16位

call/apply

作用:改变this指向

区别:后面传的参数形式不同

call:需要把实参按照形参的个数传进去 apply:需要传一个arguments

还有一种改变this指向 :bind bind 会将改变this后的函数传递回来,不立即执行,在执行的时候传递参数

    function test () {

    }
    test() ---->  test.call();


    function Person (name,age) {
        //this == obj
        this.name = name;
        this.age = age;
    }
    var person = new Person('Deng',100);
    var obj = {};

    Person.call(obj,'Deng',100);    // 将Person里所有的this都改为obj,默认指向window

    // obj :{name: "Deng", age: 100}
  • 使用call的对象必须是new出来的
    function Person (name,age,sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }

    function Student (name,age,sex,grade) {
        Person.call(this,name,age,sex);
        //Person.apply(this,[name,age,sex]);
        this.grade = grade;
    }
    var student = new Student('aaa',30,'male',6);
    

在这里插入图片描述