这是我参与「第四届青训营 」笔记创作活动的的第21天
js运行三部曲
-
语法分析
-
预编译
-
解释执行
语法分析
通篇扫描,看是否有语法错误,不执行
预编译
前奏
- imply global 暗示全局变量:即任何变量,如果变量未经声明就赋值,此变量就为全局对象window所有 eg:a = 123;
eg:var a = b = 123;
//b未声明,window.b可以访问
//a声明了,window.a不可访问
- 一切声明的全局变量,全是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;
系统会把变量声明提到程序最前面
预编译发生在函数执行的前一刻
函数体系里的预编译:
- 创建AO对象(Activation Object 执行期上下文)
- 找形参和变量声明,将变量和形参名作为AO属性名,值为undefined
- 将实参值和形参统一
- 在函数体里面找函数声明,值赋予函数体
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
ƒ () {}
*/
全局预编译:
- 创建GO对象(Global Object 执行期上下文)
- 找变量声明,将变量作为GO属性名,值为undefined
- 找函数声明,值赋予函数体
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
*/
作用域
-
作用域定义:变量(变量作用域又称上下文)和函数生效(能被访问)的区域
-
全局、局部变量 函数里的是局部变量,外面的是全局变量 函数内部可以使用函数外面的变量
-
作用域的访问顺序 函数里面可以访问函数外的变量,但函数外部无法访问函数内部的变量
作用域精解 运行期上下文:当函数执行时,会创建一个称为执行期上下文的内部对象。 一个执行期上下文定义了一个函数执行时的环境,函数每次执行时对应的执行上下文都是独一无二的,所以多次调用一个函数会导致创建多个执行上下文,当函数执行完毕,它所产生的执行上下文被销毁。
查找变量:从作用域链的 顶端 依次向下查找
[[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();
闭包的作用
- 实现共有变量 eg:函数累加器
function add() {
var num = 0;
function a () {
console.log(++ num);
}
return a;
}
var myAdd = add();
myAdd();
myAdd();
myAdd();
- 可以做缓存(存储结构 eg:
- 可以实现封装,属性私有化 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');
- 模块化开发,防止污染全局变量
闭包的防范
闭包会导致多个执行函数共用一个公有变量,如果不是特殊需要,应尽量防止这种情况发生
原型
定义
原型是function对象的一个属性,它定义了构造函数制造出的对象的公共祖先。通过该构造函数产生的对象,可以继承该原型的属性和方法。 原型也是对象
//Person.prototype ----原型 产生时就已定义好
//Person.prototype = {} 是祖先
Person.prototype.name = 'hehe';
function Person() {
}
var person = new Person();
自己和原型的属性撞了,用自己的
-
利用原型特点和概念,可以提取共有属性
-
对象如何查看原型 —— > 隐式属性__proto__
xxx:隐式命名规则,系统命名 _xxx :私人的属性,提醒他人尽量别碰这个属性
Person.prototype.name = 'abc';
function Person() {
//var this = {
// __proto__ : Person.prototype 存放队形的原型,将原型和自己连接到一起
//};
}
var person = new Person();
person.name; //当对象没有这个属性时,就会在__proto__里找原型里是否有该属性
构造函数内部原理
- 在函数体最前面隐式的加上this = {}
- 执行this.xxx = xxx;
- 隐式的返回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;
- 对象如何查看对象的构造函数 —— > 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";
}
原型链
- 如何构成原型链
原型还有原型
原型链的连接点是__proto__
原型链的终端为Object.prototype
- 原型链上属性的增删改查
本人修改,后代无法更改
特例:修改
引用值可以修改
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就是指向谁
-
绝大多数对象的最终都会继承自Object.prototype
Object.creat(null)无原型 -
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);