程序是怎么运行的 ?
传统的来说,编译,是一件很复杂的事情。
QString str = "hello"
;
-
先进行分词。 QString / str / = / "hello" /
-
构建 AST (抽象语法树) 给你反推出来,这句话的意思。
-
代码生成 最后,转换成为一段可以执行的机器指令。
对 JS 来说,主要分两个阶段
- 预编译阶段 这个时候,由编译器,将 JS 编译成可执行的代码。
- 进行变量声明
- 进行变量提升, 但其值, 是 undefined
- 所有非表达式的函数声明, 进行提升
- 代码执行阶段
作用域
作用域,就是根据名称,去查找变量的规则。
词法作用域 / 静态作用域。
词法作用域,就是定义在,词法阶段的作用域。
动态作用域 / 上下文。
在运行时的作用域。
什么情况下,我的 上下文 和 作用域 不一致呢?
闭包的时候。
this
this 到底指向什么?
this 的指向, 是根据我的执行上下文, 动态决定的,通常情况下,谁调用了函数,函数内部的this就指向谁。
- 在简单调用时,this 默认指向的是 window / global / undefined (浏览器/node/严格模式)
- 在对象调用时,绑定在对象上
- 在使用 call / apply / bind 时。绑定在制定的参数上
- 使用 new 关键字时,绑定到新创建的对象上 ( 以上三条优先级: new > apply/call/bind > 对象调用 )
- 使用箭头函数,根据外层的规则确定。
练习题
var number = 5;
var obj = {
number: 3,
fn1: (function () {
var number;
this.number *= 2;
number = number * 2;
number = 3;
return function () {
var num = this.number;
this.number *= 2;
console.log(num);
number *= 3;
console.log(number);
}
})()
}
var fn1 = obj.fn1;
fn1.call(null);
obj.fn1();
console.log(window.number);
// --------------答案------------
// 10、9、3、27、20
考察知识点:
- IIFE
- 静态/词法作用域(寻找变量)、动态作用域(this)
- call函数的第一个参数为null时,内部this指向谁?
- 闭包
const foo = {
bar: 10,
fn: function() {
console.log(this);
console.log(this.bar);
}
}
var fn1 = foo.fn
fn1.call(foo);
// ------------答案-------------
// foo
// 10
const student = {
name: 'luyi',
fn: function() {
return this;
}
}
console.log(student.fn() === student)
// ------------答案-------------
// true
const o1 = {
text: 'o1',
fn: function() {
return this.text
}
}
const o2 = {
text: 'o2',
fn: function() {
const m = o1.fn()
return m;
}
}
const o3 = {
text: 'o3',
fn: function() {
// var fn = o1.fn
// return fn()
var fn = function() {
return this.text
};
const res = fn();
return res;
}
}
console.log(o1.fn())
console.log(o2.fn())
console.log(o3.fn());
// ----------------答案---------------
// o1
// o1
// undefined
手写一个 bind / call
实现call/apply的关键
- 原函数有的功能call也必须有,比如原函数接收参数、有返回值。那么call函数也要能接收参数,也要有返回值。
- 除此之外就是要实现改变this指向的功能,这个我们要借助一句话:通过对象的方法调用函数时,函数内部的this会指向该对象。所以我们要把函数先挂载到我们要绑定的对象上,然后再通过该对象调用此方法,就可以实现改变this指向。
call方法的使用场景
- a对象上没有某个方法,b对象上有对应的方法。a对象想调用b对象上的方法。
比如arguments上没有数组的slice方法,但是数组的原型上面有这个方法,那就可以借助call方法让arguments也拥有数组的方法,Array.prototype.slice.call(arguments,1)
function called(context, ...rest) {
context.fn = this;
console.log('context.fn',this)
if(context) {
const result = context.fn(...rest);
delete context.fn;
return result
} else {
this(...rest);
}
}
o1.fn.called()
实现bind的关键
- bind和call/apply的区别就是,bind不会直接调用函数,而需要手动调用。即bind的返回值是一个函数。
- bind函数可以接收参数,bind函数的返回值也可以接收参数。
Function.prototype.bound = function (context, ...rest) {
// context 使我们要被绑定的对象
// this 就是我的 fn。谁调用了bound,this就指向谁。
const fn = this;
return function(...innerArgs) {
return fn.apply(context, [...rest, ...innerArgs]);
}
}
const o1 = {
text: 'o1',
fn: function(...rest) {
console.log(this.text, ...rest)
}
}
const o2 = {
text: 'o2',
}
const m = o1.fn.bound(o2, 'luyi', 'xianzao')
m('yunyin', 'daxiong');
补充
node 、 浏览器是两个环境。(宿主环境,给你提供全局对象)