十一、 面试题 | 8道题带你巩固【变量提升】
第一题
console.log(a, b, c);
var a = 12,
b = 13,
c = 14;
function fn(a) {
console.log(a, b, c);
a = 100;
c = 200;
console.log(a, b, c);
}
b = fn(10);
console.log(a, b, c);
△ 第一题
△ 图1.1_第一题
第二题
var i = 0;
function A() {
var i = 10;
function x() {
console.log(i);
}
return x;
}
var y = A();
y();
function B() {
var i = 20;
y();
}
B();
△ 第二题
△ 图1.2_第二题
第三题
var a=1;
var obj ={
name:"tom"
}
function fn(){
var a2 = a;
obj2 = obj;
a2 =a;
obj2.name ="jack";
}
fn();
console.log(a);
console.log(obj);
△ 第三题
△ 图1.3_第三题
第四题
var a = 1;
function fn(a){
console.log(a)
var a = 2;
function a(){}
}
fn(a);
△ 第四题
△ 图1.4_第四题
第五题
//---第一小题
console.log(a);
var a=12;
function fn(){
console.log(a);
var a=13;
}
fn();
console.log(a);
//---第二小题
console.log(a);
var a=12;
function fn(){
console.log(a);
a=13;
}
fn();
console.log(a);
//---第三小题
console.log(a);
a=12;
function fn(){
console.log(a);
a=13;
}
fn();
console.log(a);
△ 第五题
△ 图1.5_第五题
第六题
var foo='hello';
(function(foo){
console.log(foo);
var foo=foo||'world';
console.log(foo);
})(foo);
console.log(foo);
△ 第六题
△ 图1.6_第六题
问:1||2&&3||4结果是?
第七题
//--- 第一问
{
function foo() {}
foo = 1;
}
console.log(foo);
//--- 第二问
{
function foo() {}
foo = 1;
function foo() {}
}
console.log(foo);
//--- 第三问
{
function foo() {}
foo = 1;
function foo() {}
foo = 2;
}
console.log(foo);
△ 第七题
let/const/function 会形成块级作用域
能形成块级作用域的大括号有:
if(1=1){
// 块级
}
for(let i=0; i<3; i++){
// 块级
}
while(1!==1){
// 块级
}
switch(1) { // 块级
case 1:
break;
}
switch(1) {
case 1:{ // 块级
console.log(1);
break;
}
}
{ // 块级
}
// …… 等
△ let/const/function 可以形成块级作用域
① 第一问
//--- 第一问
{
function foo() {}
foo = 1;
}
console.log(foo);
△ 第一问
△ 图1.7.1_第一问
老版本浏览器:没有块级上下文,function该怎么执行还怎么执行
新版本浏览器:为了兼容ES3/EC5,同时也要兼容ES6,产生了一些奇奇怪怪的机制
除了函数/对象的大括号,其他的大括号(判断体、循环体、代码块.....)中有:let/const/function声明的变量,会单独出现全新的私有的块级执行上下文
这里为啥单独说function? 是因为:let/const没有变量提升,在私有块级执行上下文中就是自己的私有变量
但是function,有个性!变量提升+块级私有上下文它都要小孩子才做选择题,大人啥都要
全局上下文和块级私有上下文,两个大佬,function都惹不起、惹不起、惹不起的,所以……两边都爱~
把第一问改一改题目:
console.log(A1);
{
console.log(A1);
function A1(){}
A1 = 1;
console.log(A1);
}
console.log(A1);
△ function在块级作用域中的情况
/*
EC(G) 全局执行上下文
VO(G) 变量对象
--------
变量提升:function A1;[只声明]
【函数A1出现在了块级上下文中,此时:只声明】
代码执行:
*/
console.log(A1); //=> undefined
{ //=>【进入到:块级私有执行上下文】
/*
EC(Block) 块级私有执行上下文
AO(Block) 变量对象
A1 = 0xB00001 [[scope]]:EC(Block)
= 1
--------
作用域链:<EC(Block), EC(G)>
【没有this\arguments\形参赋值....】
变量提升:
[函数:声明+定义]
A1 = 0xB00001 [[scope]]:EC(Block)
代码执行:
*/
console.log(A1); //=> 输出函数 0xB000001 function A1(){}
function A1() { } //【此处:函数的声明+定义,在“变量提升”阶段已经完成了。】
//--> 【但是,此处有特殊性:由于函数在全局和块级上下文中都有“提前声明”】
//--> 【它会把对函数A1做的操作,映射到全局上下文上(两边大佬都不得罪)】
A1 = 1; //-> 【只给块级私有上下文的A1赋值,不会给全局上下文】
console.log(A1); //=> 1
}
console.log(A1); //=> 函数 function A1(){}
△ 块级上下文+function
△ 图1.7.1_debugger
② 第二问
//--- 第二问
{
function foo() {}
foo = 1;
function foo() {}
}
console.log(foo);
△ 第二问
/*
EC(G) 全局执行上下文
VO(G) 变量对象
------
变量提升:function foo; [只声明]
【函数A1出现在了块级上下文中,此时:只声明】
代码执行:
*/
{//=> 【进入到块级上下文】
/*
EC(Block) 块级私有上下文
AO(Block) 变量对象
-----
作用域链:<EC(Block), EC(G)>
【没有this\arguments\形参赋值....】
变量提升:
[函数:声明+定义]
foo = 0xB00001 [[scope]]:EC(Block)
= 0xB00002 [[scope]]:EC(Block)
代码执行:
*/
function foo() {} //--> 把在块级上下文对foo做过的处理,会同步到全局上下文
foo = 1; //=> 给块级上下文的私有变量赋值foo=1
function foo() {} //--> 把在块级上下文对foo做过的处理,会同步到全局上下文
}
console.log(foo); //=> 1
△ 第二问分析流程
console.log(A1); //=>undefined
{
console.log(A1); //=> function A1(m){}
function A1(n){}
A1 = 1;
function A1(m){}
console.log(A1); //=> 1
}
console.log(A1); //=> 1
△ 改造第二问,新版浏览器
③ 第三问
//--- 第三问
{
function foo() {}
foo = 1;
function foo() {}
foo = 2;
}
console.log(foo);
△ 第三问
/*
EC(G) 全局执行上下文
VO(G) 变量对象
-----
变量提升:
function foo;[只声明]
代码执行:
*/
{//=> 【开启块级私有上下文】
/*
EC(Block) 块级私有上下文
AO(Block) 变量对象
foo = 0xB00001 [[scope]]:EC(Block)
= 0xB00002 [[scope]]:EC(Block)
= 1
= 2
-----
作用域链:<EC(Block), EC(G)>
【没有this\arguments\形参赋值....】
变量提升:
foo = 0xB00001 [[scope]]:EC(Block)
= 0xB00002 [[scope]]:EC(Block)
代码执行:
*/
function foo() {}
foo = 1;
function foo() {}
foo = 2;
console.log(foo); //=> 2
}
console.log(foo); //=> 1
△ 第三问,新版浏览器
判断体中的function
console.log(A1);
if (1 == 1) {
console.log(A1);
function A1(m) { };
A1 = 2;
function A1(n) { }
console.log(A1);
}
console.log(A1);
△ 条件成立+function
console.log(A1);
if (1 !== 1) {
console.log(A1);
function A1(m) { };
A1 = 2;
function A1(n) { }
console.log(A1);
}
console.log(A1);
△ 条件不成立+function
解析:条件成立
/*
EC(G) 全局执行上下文
VO(G) 变量对象
---------
变量提升:function A1;
代码执行:
*/
console.log(A1); //=> undefined
if (1 == 1) {
/*
EC(Block) 块级执行上下文
AO(Block) 变量对象
A1 = 0xB00001 [[scope]]:EC(Block)
= 0xB00002 [[scope]]:EC(Block)
= 2
--------
【没有this/arguments/形参赋值...】
变量提升:
A1 = 0xB00001 [[scope]]:EC(Block)
= 0xB00002 [[scope]]:EC(Block)
代码执行:
*/
console.log(A1); //=> function A1(n) { } ->0xB00002
function A1(m) { };
A1 = 2;
function A1(n) { }
console.log(A1);//=>2
}
console.log(A1); //=> 2
△ 条件成立,function,新版浏览器
解析:条件不成立
console.log(A1);//=>undefined
if (1 !== 1) { // 条件不成立,不进入判断体
console.log(A1);
function A1(m) { };
A1 = 2;
function A1(n) { }
console.log(A1);
}
console.log(A1); //=>undefined
△ 条件不成立,function,新版浏览器
第八题
//---第一问
var x = 1;
function func(x, y = function anonymous1(){x=2}){
x = 3;
y();
console.log(x);
}
func(5);
console.log(x);
//---第二问
var x = 1;
function func(x, y = function anonymous1(){x=2}){
var x = 3;
y();
console.log(x);
}
func(5);
console.log(x);
//---第三问
var x = 1;
function func(x, y = function anonymous1(){x=2}){
var x = 3;
var y = function anonymous1(){x=4}
y();
console.log(x);
}
func(5);
console.log(x);
△ 第八题
考察点:形参有默认值+函数体中有声明
① 第一问
var x = 1;
function func(x, y = function anonymous1(){x=2}){
x = 3;
y();
console.log(x);
}
func(5);
console.log(x);
△ 第一问,只有形参有默认值的情况
△ 图1.8.1_第一问,形参有默认值
② 第二问
var x = 1;
function func(x, y = function anonymous1(){x=2}){
var x = 3;
y();
console.log(x);
}
func(5);
console.log(x);
△ 第二问
函数执行:
1、有形参赋值的默认值(不管是否传递了实参,不管默认值的类型)
2、函数体中有变量声明
① 必须是let/const/var
② 注意let/const 不允许重复声明,不能和形参变量名一致
此时,除了默认形成的“函数私有上下文”,还会多创建一个“块级私有上下文”【把函数体到括号的都包起来了】
在这个“块级私有上下文”中:
① 在这里声明的变量都是块级上下文的私有变量,跟函数的私有上下文没半毛钱关系了
② 作用域链<EC(Block), EC(FN)> ,块级上下文的上级上下文就是这个函数私有上下文
③ 会把在函数私有上下文中,把传递的形参赋值中,映射给块级上下文相同名字的变量
△ 图1.8.2_第二问,形参有默认值,函数体内有var声明变量
改一改:
var x = 1;
function func(x, y = function anonymous1(){console.log(x);x=2;}){
console.log(x);
var x = 3;
y();
console.log(x);
}
func(5);
console.log(x);
△ 结果是?
逐步分析:
/*
EC(G) 全局执行上下文
VO(G) 变量对象
func = 0x000001 [[scope]]:EC(G)
x = 1
-----
变量提升:
var x;
func = 0x000001 [[scope]]:EC(G)
代码执行:
*/
var x = 1;
function func(x, y = function anonymous1(){console.log(x);x=2;}){
/*
func(5) 调用函数 0x000001(5)
EC(func) 私有执行上下文
AO(func) 变量对象【跟着代码执行会改变值】
x = 5
= 2【在y()是改变的】
-----
作用域链:<EC(func), EC(G)>
形参赋值:
x = 5
y = 0xB00001 [[scope]]:EC(func)
① 形参有默认值
② 函数体内有变量声明:var/let/const
则--->
① 形成一个全新的私有上下文
② 传递的形参赋值,同名的变量同步过去
*/
//======================
/*
EC(Block) 私有上下文
AO(Block) 变量对象
x = 5【同名的变量同步过来的】
= 3
----
变量提升:var x;
代码执行:
*/
console.log(x); //=>输出 5
var x = 3;
y(); //=> EC(Y)
/*
y() --> 0xB00001()
EC(Y) 私有执行上下文
AO(Y) 变量对象
-----
作用域链:<EC(Y), EC(func)>
...省略
代码执行:
console.log(x);
【=> x不是自己的,是EC(func)的】
【=> 输出 5】
x=2;
*/
// 继续执行EC(Block)的代码
console.log(x);//=> 是自己的
//=> 输出 3
}
func(5);
console.log(x); //=> 输出 1
△ 分析步骤
③ 第三问
var x = 1;
function func(x, y = function anonymous1(){x=2}){
var x = 3;
var y = function anonymous1(){x=4}
y();
console.log(x);
}
func(5);
console.log(x);
△ 第三问
△ 图1.8.3_第三问
改一改:
var x = 1;
function func(x, y = function anonymous1(){console.log(x);x=2;}){
var x = 3;
y();
var y = function anonymous1(){console.log(x);x=4;}
y();
console.log(x);
}
func(5);
console.log(x);
△ 结果是?
var x = 1;
function func(x, y = function anonymous1(){console.log(x);x=2;}){
var x = 3;
y(); //=> y[[scope]]:EC(func)
//=> 作用域链:<EC(Y1), EC(func)>
//=> x是EC(func)中的 5
//=> 输出 5
//=> 然后 EC(func) 中的 x=2
var y = function anonymous1(){console.log(x);x=4;}
y(); //=> y[[scope]]:EC(Block)
//=> 作用域链:<EC(Y2), EC(Block)>
//=> x是EC(Block)中的 3
//=> 输出 3
//=> 然后 EC(Block) 中的 x=4
console.log(x); //=> 4
}
func(5);
console.log(x); //=> 1
△ 分析图就自己画吧
Tips
1、let/const/function 会形成块级上下文,let/const没有变量提升,但是function会有一些奇奇怪怪的事情发生,尽量多使用函数表达式声明let fun = function (){};
2、函数的形参默认值+函数体内使用let/const/var声明变量,会产生一些奇奇怪怪的事情:尽量不使用形参默认值
十二、 面试题 | 4道题带你巩固【数据类型】
第一题
let result = 100 + true + 21.2 + null + undefined + "Tencent" + [] + null + 9 + false;
console.log(result);
△ 结果是?
从左到右计算:
① 100+true => 100 + 1 = 101
② 101 + 21.2 = 121.2
③ 121.2 + null => 121.2 + 0
④ 121.2 + undefined => 121.2 + NaN = NaN
⑤ NaN + "Tencent" => "NaNTencent" 字符拼接
⑥ "NaNTencent" + [] + null + 9 + false
=> "NaNTencentnull9false"
请问:[]==false和![]==false结果是?
第二题
{}+0?alert('ok'):alert('no');
0+{}?alert('ok'):alert('no');
△ 第二题
△ 图1_大括号的计算_浏览器控制台输出
{}+0?alert('ok'):alert('no');
=> {}+0 => +0 => 0
=> 0?alert('ok'):alert('no');
=> 弹出'no'
0+{}?alert('ok'):alert('no');
=> 0+{} => "0[object Object]"
=> "0[object Object]" ? alert('ok'):alert('no')
=> 弹出'ok'
第三题
let res = Number('12px');
if(res===12){
alert(200);
}else if(res===NaN){
alert(NaN);
}else if(typeof res==='number'){
alert('number');
}else{
alert('Invalid Number');
}
△ 第三题
这道题比较简单了~
let res = Number('12px'); //=> res = NaN
if(res===12){
alert(200);
}else if(res===NaN){
alert(NaN);
}else if(typeof res==='number'){
// typeof NaN === 'number'
// 弹出 字符串number
alert('number');
}else{
alert('Invalid Number');
}
△ 第三题
第四题
let arr = [27.2,0,'0013','14px',123];
arr = arr.map(parseInt);
console.log(arr);
△ 第四题
△ 图_parseInt的用法,radix:2~36,0或者不写:十进制
parseInt(27.2, 0)
radix:0 基数是十进制
=> 27
parseInt(0, 1)
radix:1 不在范围内
=> NaN
parseInt('0013', 2)
radix:2 找到有效字符为二进制的
'001'
0*2^2+0*2^1+1*2^0
=>1
parseInt('14px', 3)
radix:3 找到有效字符为三进制的
'1'
1*3^0
=>1
parseInt(123, 4)
radix:4 找到有效字符为四进制的
'123'
1*4^2+2*4^1+3*4^0
=> 16+8+3
=> 27
结果是:[27, NaN, 1, 1, 27]
十三、 面试题 | 11道题带你巩固【闭包作用域】
第一题
var a = 10,
b = 11,
c = 12;
function test(a) {
a = 1;
var b = 2;
c = 3;
}
test(10);
console.log(a, b, c);
△ 第一题
△ 图3.1_第一题
第二题
var a = 4;
function b(x, y, a) {
console.log(a);
arguments[2] = 10;
console.log(a);
}
a = b(1, 2, 3);
console.log(a);
△ 第二题
△ 图3.2_第二题
arguments 类数组对象,实参集合 {0:1, 1:2, 2:3, length3}
在JS的“非严格模式”下,初始完成arguments和形参赋值结束后
1、会建立arguments和形参之间的映射机制:一一对应
2、只有在这个阶段才会建立映射机制,代码执行的时候,则不再处理这件事情了
function fn(x,y,z){
arguments[0] = 10;
console.log(x);
y=20;
console.log(arguments[1]);
z=30;
console.log(arguments[2]);
}
fn(1,2);
△ 形参赋值,arguments
function fn(x,y,z){
/*
fn(1,2)
EC(FN) 私有上下文
AO(FN) 变量对象
x=1
y=2
----
初始化arguments:{0:1, 1:2, length:2}
【arguments与形参有映射机制】
形参赋值:x=1,y=2
变量提升:——
代码执行:
*/
arguments[0] = 10;
console.log(x);//=> 10
y=20;
console.log(arguments[1]);//=>20
// 在初始化时,没有z,所以没有关联
z=30;
console.log(arguments[2]);//=> undefined
}
fn(1,2);
△ arguments实参集合
现在写代码,需要基于webpack编译后的结果都是严格模式:"use strict";
第三题
var a = 9;
function fn() {
a = 0;
return function (b) {
return b + a++;
}
}
var f = fn();
console.log(f(5));
console.log(fn()(5));
console.log(f(5));
console.log(a);
△ 第三题
△ 图3.3_第三题
第四题
var test = (function (i) {
return function () {
alert(i *= 2);
}
})(2);
test(5);
△ 第四题
△ 图3.4_第四题
第五题
var x = 4;
function func() {
return function(y) {
console.log(y + (--x));
}
}
var f = func(5);
f(6);
func(7)(8);
f(9);
console.log(x);
△ 第五题
△ 图3.5_第五题
第六题
var x = 5,
y = 6;
function func() {
x += y;
func = function (y) {
console.log(y + (--x));
};
console.log(x, y);
}
func(4);
func(3);
console.log(x, y);
△ 第六题
△ 图3.6_第六题
第七题
function fun(n, o) {
console.log(o);
return {
fun: function (m) {
return fun(m, n);
}
};
}
var c = fun(0).fun(1);
c.fun(2);
c.fun(3);
△ 第七题
△ 图3.7_第七题
第八题
简述你对闭包的理解,以及其优缺点?
【第一个维度】:基本介绍:ECStack、EC、VO、AO、GO、SCOPE、SCOPE-CHAIN、GC(垃圾回收机制)
【第二个维度】:优缺点,保存和保护、性能消耗(可能会引发内存泄漏不用具体说泄漏)
【第三个维度】:实战应用
1、 循环事件绑定——【突出:事件委托】、let 和 var
2、插件组件应用:JS高程编程技巧【单例设计模式、惰性函数、柯理化函数、compose函数】
3、源码阅读应用: Lodash源码[函数的防抖和节流]、JQ的源码、redux[createStore\combineReducers]、react-redux【高阶组件】……
4、……
【第四个维度】:自己的思想和理解【一句话概括】
闭包的理解
在函数的私有执行上下文中,有一些事物(一般是堆内存地址)被此上下文以外的事物占用了(比如:事件绑定、变量赋值等),使得此上下文不能出栈释放。这种机制称为“闭包”。
市面上,很多人认为形成不销毁的作用域才是形成了闭包,比如,大家认为一个大函数返回一个小函数,就是闭包。
这是形成闭包的一种情况。
其实,在函数执行时,就形成了闭包,把里面的变量保护起来,只是有的会在执行完后销毁。大家觉得它太短暂了,就没算在闭包的情况里面。
闭包的优点
闭包会形成一个不被释放的私有执行上下文,在此上下文中的变量和值得以 保存下来,并且会 保护 此上下文中的变量不会被外界的内容干扰到。
1、保护:函数执行开辟一个全新的、私有的执行上下文,保护这里面的变量不受外界的干扰
2、保存:当这个执行上下文中的一些事物(一般是堆内存地址)被外界的事物占用了,那么此上下文不会被浏览器回收销毁。此时,该上下文中的所有变量都被保存下来了
闭包的缺点
1、大量使用闭包,会形成很多不被释放的栈内存,导致页面渲染变慢,性能受到影响。在实际开发中,应该合理利用闭包
2、有些代码会导致栈溢出或内存泄漏,需要注意:比如,死递归
第九题
简述let和var的区别?
声明变量
① var function
② let const import
let VS var 区别:
① 用var关键字声明的变量,在代码执行之前,会把提前声明变量declare。而let则不允许提前声明了
② 使用var关键字声明的变量,会“映射”一份到window上。两边相互影响,一边改变了,另一边也会改变。而let则不会出现这种情况了
③ 使用var关键字声明的变量,在其后面重复用var再声明一次,也是允许的。而let声明的变量,不论用任何关键字声明都会在代码执行之前检测出来,并报错
④ 暂存死区
console.log(n); 未被声明过的变量直接使用会报错
console.log(typeof n) 但是在此时就输出 undefined了
而,console.log(typeof n); let n; 就会报错,修复了这个问题
⑤ let/const/function 会形成块级作用域,var 不会
第十题
下面代码输出的结果是多少,为什么?如何改造一下,就能让其输出 20 10
var b = 10;
(function b() {
b = 20;
console.log(b);
})();
console.log(b);
△ 第十题
第一问:输出结果是 function b(){b=20;console.log()b;} 10
匿名函数“具名化”:这样的写法是符合规范的
document.body.onclick = function bodyClickHandle(){}
匿名函数具名化,设置的名字不属于当前函数所在上下文中的变量
函数名只能在函数内部使用
[好处]:后期匿名函数也可以实现递归调用
解决了:arguments.callee 在严格模式下会报错
在函数内部直接修改它的值也是无效的
除非函数内部重新声明这个变量,就可以修改了:let/const/function/var
第二问:如何改造,能输出 20 10
匿名函数具名化:
1、在函数外面是不能调用这个名字的,可以在里面使用这个函数名。优化的是:严格模式下,arguments.caller 报错
2、函数内部,默认是不能修改的,函数名代表的是函数体。
3、重新声明后,就可以修改了
改造:变量声明
var b = 10;
(function b() {
var b = 20;
console.log(b);
})();
console.log(b);
△ 在函数体内部重新声明变量
第十一题
实现函数fn,让其具有如下功能
let res = fn(1,2)(3);
console.log(res); //=>6 1+2+3
△ 第十一题
柯理化函数:预先处理一些事情
function fn(...outArgs){
// outArgs = [1,2]
return function anonyous(...innerArgs){
// innerArgs = [3]
return [...outArgs, ...innerArgs].reduce((result, item)=>result+item,0)
}
}
let res = fn(1,2)(3);
console.log(res);
△ 第十一题
十四、 面试题图解 | 6+1道题带你巩固【判断THIS】
△ 图_在csdn上看到的题目:ask.csdn.net/questions/1…
第一题
var num = 10;
var obj = {
num: 20
};
obj.fn = (function (num) {
this.num = num * 3;
num++;
return function (n) {
this.num += n;
num++;
console.log(num);
}
})(obj.num);
var fn = obj.fn;
fn(5);
obj.fn(10);
console.log(num, obj.num);
△ 第一题
△ 图4.1_第一题
第二题
let obj = {
fn: (function () {
return function () {
console.log(this);
}
})()
};
obj.fn();
let fn = obj.fn;
fn();
△ 第二题
//=>【第二题解析】
let obj = {
fn: (function () {
return function () {
console.log(this);
}
})()
};
obj.fn(); //=> 点前面是:obj
//=> this:obj
//=> 输出:obj {fn:function (){console.log(this)}}
let fn = obj.fn;
fn();//=> 没有点:this:window
//=> 输出:window
△ 第二题解析
第三题
var fullName = 'language';
var obj = {
fullName: 'javascript',
prop: {
getFullName: function () {
return this.fullName;
}
}
};
console.log(obj.prop.getFullName());
var test = obj.prop.getFullName;
console.log(test());
△ 第三题
//=>【第三题解析】
var fullName = 'language';
var obj = {
fullName: 'javascript',
prop: {
getFullName: function () {
return this.fullName;
}
}
};
/*
getFullName 的点前面是 obj.prop
this:obj.prop
this.fullName => obj.prop.fullName
=> return undefined
*/
console.log(obj.prop.getFullName()); //=> 输出 undefined
var test = obj.prop.getFullName;
/*
test 前面没有点
this:window
=> this.fullName => window.fullName
=> return "language"
*/
console.log(test()); //=> 输出 "language"
△ 第三题解析
第四题
var name = 'window';
var Tom = {
name: "Tom",
show: function () {
console.log(this.name);
},
wait: function () {
var fun = this.show;
fun();
}
};
Tom.wait();
△ 第四题
//=>【第四题解析】
var name = 'window';
var Tom = {
name: "Tom",
show: function () {
console.log(this.name);
},
wait: function () {
// this:Tom
// fun = Tom.show
var fun = this.show;
/*
fun前面没有点,this:window
console.log(window.name);
输出:"window"
*/
fun();
}
};
/*
wait 的点前面是Tom
this:Tom
*/
Tom.wait(); //=> 输出"window"
△ 第四题解析
第五题
window.val = 1;
var json = {
val: 10,
dbl: function () {
this.val *= 2;
}
}
json.dbl();
var dbl = json.dbl;
dbl();
json.dbl.call(window);
alert(window.val + json.val);
△ 第五题
//=>【第五题解析】
window.val = 1;
var json = {
val: 10,
dbl: function () {
this.val *= 2;
}
}
json.dbl(); // this:json ; json.val = 10*2=20
var dbl = json.dbl;
dbl(); // this:window ; window.val = 1*2 =2
/*
json.dbl.call(window)
1、把json.dbl中的this指向 window
2、调用json.dbl
window.val = 2*2 =4
*/
json.dbl.call(window);
alert(window.val + json.val); // 4+20 【弹出"24"】
△ 第五题解析
第六题
(function () {
var val = 1;
var json = {
val: 10,
dbl: function () {
val *= 2;
}
};
json.dbl();
alert(json.val + val);
})();
△ 第六题
//=>【第六题解析】
(function () {
// 自执行函数:this:window
var val = 1;
var json = {
val: 10,
dbl: function () {
// [[scope]]:EC(AN)
// val 是EC(AN)中的 1
// val = 1*2 =2
val *= 2;
}
};
json.dbl(); // this:json
alert(json.val + val); // 10+2 【弹出"12"】
})();
△ 第六题解析
△ 图_对话框2
5种判断函数调用中的THIS:
① 普通函数,函数名前面是否有点
=> 有点xxx.fn(),点前面是谁,this就是谁
=> 没有点fn(),非严格模式下是window,严格模式下是undefined
② 构造函数,new 函数名()
③ 事件绑定
④ 箭头函数里面没有this
⑤ 基于call/apply/bind,改变调用函数中的this
函数内部的arguments是一个类数组对象,实参集合。比如:{0:1, 1:2, length:2} 在函数调用时,与形参变量有对应关系
对象访问属性:
let obj = {
name:'朝霞的光影笔记',
id:'zhaoxiajingjing',
say:function(){
console.log(this.name);
}
};
obj.name;
obj['name'];
obj.say();
obj['say']();
∴ 对象属性访问有两种:
① 点表示法
② 括号表示法
所以,要注意括号表示法时候的this值,跟点表示法是一样的
好啦咱们看看那道题为啥是 20 3 吧
十五、 面试题 | 请实现sum(1)(2,3)(4,5)的函数 | 柯理化函数应用
sum(1,2,3,4)(5); //=> 15
sum(1)(2,3)(4,5); //=> 15
sum(1)(2)(3)(4,5); //=> 15
sum(1)(2)(3)(4)(5); //=> 15
△ 实现sum函数:① 调用次数不固定,② 传参个数不固定 ③ 最后输出结果
(1)第一种方案
sum(1,2,3,4)(5);
sum(1)(2,3)(4,5);
sum(1)(2)(3)(4,5);
sum(1)(2)(3)(4)(5);
△ 实现sum函数:① 调用次数不固定,② 传参个数不固定 ③ 最后输出结果
① 函数可以一直被调用
function sum(...params){
const proxy = (...args) => {
return proxy;
};
return proxy;
}
sum();
sum()()();
△ 为了让sum()()()可以一直调用下去
② 收集参数
function fn(){}
console.log(fn + 1); //=> "function fn(){}1"
△ 函数名做运算,会调用toString方法
fn+1 加号做运算:fn先转成数字,会依次调用fn的Symobl.toPrimitive/valueOf/toString这三个属性,加号遇到字符串就变成字符串拼接了
基于函数进行运算时或者输出时,一般都会调用到函数的toString属性
function fn(){}
fn.toString = function (){
console.log('hello');
};
console.log(fn);
△ 在输出fn时,会调用到fn.toString属性。API: developer.mozilla.org/en-US/docs/…
function sum(...params){
const proxy = (...args)=>{
// 把每一次传递的信息都保存起来
params = params.concat(args);
return proxy;
};
proxy.toString = ()=>{
// 需要计算的值
return params.reduce((result, item) => result + item);
};
return proxy
}
sum(1,2,3,4)(5);
sum(1)(2,3)(4,5);
sum(1)(2)(3)(4,5);
sum(1)(2)(3)(4)(5);
△ 可以一直计算下去
(2)第二种方案
function currying(){
let params = [];
let sum = (...args) =>{
params = params.concat(args);
return sum;
}
sum.toString = () => {
return params.reduce((result, item)=>result + item);
};
return sum;
}
let sum = currying();
sum(1,2,3,4)(5);
// 注意:每次调用前都要重新调用currying
sum = curring();
sum(1)(2,3)(4,5);
△ 闭包会保存下来上次的值,每次调用前都要清空
请问:为什么每次都需要重新调用curring这个方法?
let sum = currying();
sum(1,2,3,4)(5);
sum(1)(2,3)(4,5);
△ 结果是:30
let sum = currying() 执行时,全新的私有执行上下文EC(curring),形成一个不被销毁的闭包。
=> 私有变量params 是对象数据类型,会被保存下来
=> 私有变量 let sum = 函数[[scope]]:EC(curring),函数数据类型
=> return 函数[[scope]]:EC(curring) ,赋值给sum
sum(1,2,3,4)(5) 执行时,会形成一个全新的私有执行上下文EC(s5)
=> 作用域链[[scope-chain]]:<EC(s5), EC(currying)>
=> 用到变量params,不是自己的,向上级作用域查找是EC(currying)里面的
=> params 的值:[1,2,3,4,5]
=> …………
紧接着执行sum(1)(2,3)(4,5)时,会形成一个全新的私有执行上下文EC(s45)
=> 作用域链[[scope-chain]]:<EC(s45), EC(currying)>
=> 用到变量params,不是自己的,向上级作用域查找是EC(currying)里面的
=> params 的值:[1,2,3,4,5,1,2,3,4,5]
=> …………
十六、 面试题 | $使用权限冲突了,如何解决?| 跟着 jQuery 大佬学编程思想
0 / jQuery源码部分解析
$npm init -y
$npm install jquery
node_modules\jquery\dist\jquery.js 查看jQuery的源码
这是我摘取后,改了一下:
var A = typeof window !== "undefined" ? window : this;
var B = function (window, noGlobal){};
(function (global, factorys){
"use strict";
if(typeof module === 'object' && typeof module.exports === 'object'){
// 当前运行JS的环境是支持CommonJS模块规范的
// nodejs/webpack支持CommmonJS模块规范
// 浏览器不支持CommonJS模块规范的
} else {
// 浏览器或者webview环境
factory(global);//=> B(window)
}
})(A, B);
△ jquery源码分析
L1:
利用JS的暂时性死区 : 基于typeof检测一个未被声明的变量,结果是undefined
如果是在浏览器或者webview环境下运行JS,则A=>window
在nodejs下运行JS,则A=>global或者当前模块
在浏览器环境下是把B函数执行factory(global)
var B = function (window, noGlobal){
// 浏览器环境下
// window => window
// noGlobal => undefined
"use strict";
var jQuery = function (selector, context){};
// ...CODE
// 在外面用到这个jQuery方法
if(typeof noGlobal === 'undefined'){
// 把私有的方法暴露到全局对象上
window.jQuery = window.$ = jQuery;
}
};
△ 把jQuery暴露在全局上:window.jQuery = window.$ = jQuery
$() 就是jQuery() 就是让闭包中的jQuery方法执行
依葫芦画瓢,在我们自己封装组件时:
1、利用闭包的保护作用,把它们都包起来,这样里面写的变量都是私有,防止全局变量污染
2、暴露的API:支持浏览器 和 CommonJS规范
(function (){
function ModulePlugin(){}
// 防止冲突
var _M = window.M;
if(typeof window !== 'undefined'){
window.MP = window.ModulePlugin = ModulePlugin;
}
if(typeof module === 'object' && typeof module.exports === 'object'){
// COMMONJS规范
}
})();
△ 自己写组件时:① 闭包 ② 支持浏览器和CommonJS规范
1 / 释放$和jQuery的使用权限
<script src="../node_modules/jquery/dist/jquery.js"></script>
<script>
(function (){
window.$$ = jQuery.noConflict(); //=> 只释放$的使用权限
window.$jq = jQuery.noConflict(true); //=> ① 释放$的使用权限 ② 释放jQuery的使用权限
})();
</script>
△ jQuery.noConflict的作用
conflict 英 [ˈkɒnflɪkt , kənˈflɪkt] 美 [ˈkɑːnflɪkt , kənˈflɪkt]
n. 冲突;争执;争论;(军事)冲突;战斗;抵触;矛盾;不一致 v. (两种思想、信仰、说法等)冲突,抵触
jQuery.noConflict的作用:
① 释放$的使用权限
② 释放jQuery的使用权限
var jQuery = function (selector, context) {
return new jQuery.fn.init(selector, context);
};
//...CDOE
var _jQuery = window.jQuery,
_$ = window.$;
jQuery.noConflict = function (deep) {
if (window.$ === jQuery) {
window.$ = _$;
}
if (deep && window.jQuery === jQuery) {
window.jQuery = _jQuery;
}
return jQuery;
};
// 在浏览器中 noGlobal => undefined
if (typeof noGlobal === 'undefined') {
window.jQuery = window.$ = jQuery;
}
△ 从jQuery.js 中粘贴出来的源码,jQuery.noConflict方法释放$的使用权限
代码自上而下执行时:
① var _jQuery = window.jQuery; var _$ = window.$;
此时window.jQuery 没有这个属性,则:_jQuery = undefined
此时window.没有这个属性,则:_= undefined
② jQuery.noConflict = function ....
在jQuery对象上定义了一个方法noConflict
③ window.jQuery = window.$ = jQuery;
在web浏览器上 noGlobal是undefined,能进入判断体
此时:window.jQuery 和 window.$ 都赋值为 jQuery了
那么,可以直接调用 $ 和 jQuery 了 作用域链查找机制
④ window.$$ = jQuery.noConflict();
调用 jQuery.noConflict 方法,把返回值赋值给 window.$$
=> 形参赋值 deep = undefined
=> 代码执行:
=> window.$ === jQuery --> true 进入判断体
=> window.$ = _$; 其中 _ 不是自己私有的变量,查找到:_=undefined,即:window.$=undefined 使用权被释放了
=> deep && window.jQuery === jQuery 其中deep=undefined,不能进入判断体
=> return jQuery; 即:window.$$ = jQuery
⑤ window.$jq = jQuery.noConflict(true);
调用 jQuery.noConflict 方法,把返回值赋值给 window.$jq
=> 形参赋值 deep = true
=> 代码执行:
=> window.$ === jQuery --> true 进入判断体
=> window.$ = _$; 其中 _ 不是自己私有的变量,查找到:_=undefined,即:window.$=undefined 使用权被释放了
=> deep && window.jQuery === jQuery 其中deep=true, window.jQuery === jQuery 也是true,进入判断体
=> window.jQuery = _jQuery; 其中_jQuery 不是自己私有的变量,查找到:_jQuery = undefined,即:window.jQuery = undefined 使用权被释放了
=> return jQuery; 即:window.$jq = jQuery
jQuery.noConflict 使用场景:
① 同时引入zepto和jQuery
② 引入不同版本的jQuery
③ 咱自己写了个库,占用了jQuery这个属性名
2 / 以同时引入Zepto和jQuery为例
<script src="./zepto.min.js"></script> <!-- window.$ = Zepto -->
<script src="./jquery.min.js"></script><!-- window.$ = jQuery -->
<script>
(function (){
window.$$ = jQuery.noConflict(); //=> 只释放$的使用权限
})();
</script>
△ 项目同时引入zepto和jquery
window.Zepto = Zepto
window.$ === undefined && (window.$ = Zepto)
△ 从zepto.js中粘贴出来的源码
var jQuery = function( selector, context ) {
return new jQuery.fn.init( selector, context );
};
//...CDOE
var _jQuery = window.jQuery,
_$ = window.$;
jQuery.noConflict = function( deep ) {
if ( window.$ === jQuery ) {
window.$ = _$;
}
if ( deep && window.jQuery === jQuery ) {
window.jQuery = _jQuery;
}
return jQuery;
};
// 在浏览器中 noGlobal => undefined
if(typeof noGlobal === 'undefined'){
window.jQuery = window.$ = jQuery;
}
△ 从jQuery.js 中粘贴出来的源码,jQuery.noConflict方法释放$的使用权限
0、window.$ = zepto 在jQuery引入之前,已经把zepto赋值给$了
1、在导入JQ的时候,把现有全局的$是谁记录下来
var _$ = window.$; 即: _$= Zepto
var _jQuery = window.jQuery 这个时候_jQuery的值是undefined
导入jQuery结束以后,window.jQuery = window.$ = jQuery了
2、如果发现$使用权和别的类库冲突了,则转让使用权
window.$jq = jQuery.noConflict() 在调用此方法之前:_ = jQuery
① window.$ === jQuery => true,进入判断体
window.$ = _$; 即:window.的使用权限让出去了
② deep && window.jQuery === jQuery 形参deep没有接收到实参值,即:deep 为 undefined,不进入判断体
③ return jQuery;, 即:window.$$= jQuery 之后使用 $$ 即表示jQuery了
3 / 咱自己写的模块
(function (){
var utils = {};
var _utils = window.utils,
_temp = window._;
utils.noConflict = function (deep){
if(window._ === utils){
window._ = _temp;
}
if(deep && window.utils === utils){
window.utils = _utils;
}
return utils;
};
if(typeof module === 'object' && typeof module.exports === 'object'){
// ... CommonJS 规范下的操作
} else if(typeof window !== 'undefined'){
window._ = window.utils = utils;
}
})();
△ ① 区分web浏览器还是CommonJS规范的node环境 ② 在web浏览器中转让_和utils的使用权限
十七、JS第1块知识点,汇总
十八、下一个知识点:面向对象