作用域
面试题
// 1. 执行顺序
let a = 'global';
console.log(a);
function course() {
let b = 'js';
console.log(b);
session();
function session() {
let c = 'this';
console.log(c);
teacher();
// 2. 函数提升 - 作用域之内
function teacher() {
// 2.1 let不支持提升
// 2.2 变量通过var支持提升,声明提升
// var e = undefined
console.log(e);
let d = 'yunyin';
var e = 'yy';
// e = 'yy';
console.log(d);
console.log('test1', b) // 3. 作用域向上查找,向下传递
}
}
}
course();
// 提升优先级
console.log('yunyin', yunyin);
function yunyin() {
this.course = 'js'
}
yunyin = 'course';
// 变量优先 => 函数需要变量
// 块级作用域
if (true) {
let e = 111;
var f = 222;
}
console.log(f);
console.log(e);
-
- 对于作用域链我们直接通过创建态来定位作用域链 —— 静态
-
- 手动取消全局
this上下文context
- this是在执行时动态读取上下文决定的,而不是创建时
- 考察重点—各使用态的指针指向
函数直接调用中 —— this指向的window
function foo() {
console.log('函数内部', this)
}
foo()
隐式绑定 —— this指代调用堆栈上一级 => 对象、数组等引用关系逻辑
function fn() {
console.log('隐式绑定', this.a)
}
const obj = {
a: 1,
fn
}
obj.fn = fn;
obj.fn();
面试题1
const foo = {
bar: 10,
fn: function() {
console.log(this.bar);
console.log(this);
}
}
// 取出
let fn1 = foo.fn;
// 独立执行
fn1();
面试题2——追问1 如何改变属性指向?
const o1 = {
text: 'o1',
fn: function(){
// 直接使用上下文 - 传统派活
console.log('o1fn', this);
return this.text;
}
}
const o2 = {
text: 'o2',
fn: function() {
// 呼叫领导执行 —— 部门协作
return o1.fn();
}
}
const o3 = {
text: 'o3',
fn: function() {
// 直接内部构造 —— 公共人
let fn = o1.fn;
return fn();
}
}
console.log('o1fn', o1.fn());
console.log('o2fn', o2.fn());
console.log('o3fn', o3.fn());
基于上面的结果我们能看到: o2指向的是o1; o3指向的是window。
面试题3——追问2 现在我要将console.log('o2fn', oa.fn())的结果是o2。
//1. 人为干涉,改变this - bind / call / apply
const o1 = {
text: "o1",
fn: function () {
// 直接使用上下文 - 传统派活
console.log("o1fn", this);
return this.text;
},
};
const o2 = {
text: "o2",
fn: function () {
// 呼叫领导执行 —— 部门协作
return o1.fn();
},
};
o1.fn.apply(o2);
o1.fn.call(o2);
o1.fn.bind(o2)();
// 2. 不需人为改变
const o1 = {
text: 'o1',
fn: function(){
// 直接使用上下文 - 传统派活
console.log('o1fn', this);
return this.text;
}
}
const o2 = {
text: 'o2',
fn: o1.fn
}
console.log('o2fn', o2.fn());
显式绑定
function foo(a,b) {
console.log('函数内部', this);
console.log(a,b,'参数')
}
foo();
// 使用
foo.call({
a: 1
},4,5);
foo.apply({
a: 2
},[6,7]);
const bindFoo = foo.bind({
a: 3
},8,9);
bindFoo();
面试题1——call/apply/bind的区别
call与apply的唯一区别
传给fun的参数写法不同:
apply是第2个参数,这个参数是一个数组:传给fun参数都写在数组中。call从第2~n的参数都是传给fun的。
call/apply与bind的区别
执行:
- call/apply改变了函数的this上下文后马上执行该函数
- bind则是返回改变了上下文后的函数,不执行该函数
返回值:
- call/apply 返回
fun的执行结果 - bind返回fun的拷贝,并指定了fun的this指向,保存了fun的参数。
面试题2——手写bind/apply/call
Function.prototype.newBind = function () {
//this指当前函数
const _this = this;
//Array.prototype.slice.call()方法能够将一个具有length属性的对象转换为数组。
const args = Array.prototype.slice.call(arguments);
//shift() 方法用于把数组的第一个元素从其中删除,并返回第一个元素的值。此方法改变原数组
const newThis = args.shift();
//返回一个函数
return function () {
return _this.apply(newThis, args);
};
};
Function.prototype.newApply = function (context) {
context = context || window;
// 挂载执行函数
// context.fn = this;
context.__proto__.fn = this;
let result = arguments[1] ? context.fn(...arguments[1]) : context.fn();
delete context.fn;
return result;
};
Function.prototype.newCall = function (context) {
context = context || window;
context.__proto__.fn = this;
const args = Array.prototype.slice.call(arguments);
args.shift();
const result = args.length>0 ? context.fn(...args) : context.fn();
delete context.fn;
return result;
};
闭包——一个函数和他周围状态的引用捆绑在一起的组合
// 函数作为返回值的场景
function mail() {
let content = '信';
return function() {
console.log(content);
}
}
const envelop = mail()
envelop()
// 函数作为参数的时候
let content;
function envelop(fn) {
content = 1;
fn();
}
function mail() {
console.log(content);
}
envelop(mail);
// 函数嵌套
let counter = 0;
function outerFn() {
function innerFn() {
counter++;
console.log(counter);
}
return innerFn;
}
outerFn()();
// 立即执行函数 => js模块化的基石
(function immediate(args) {
console.log(++args)
})(1);
// 实现私有变量
function createStack() {
return {
items: [],
push(item) {
this.item.push(item);
}
}
}
const stack = {
items: [],
push: function() {}
}
function createStack() {
const items = [];
return {
push(item) {
items.push(item);
}
}
}