看代码说输出

489 阅读13分钟
  1. 构造函数、原型链、静态方法
function Foo() {
    Foo.a = function() {
        console.log(1)
    }
    this.a = function() {
        console.log(2)
    }
}
// 以上是Foo的构造函数,没有产生实例,也没有执行
Foo.prototype.a = function() {
    console.log(3)
}
// 以上是在Foo上挂载了实例方法
Foo.a = function() {
    console.log(4)
}
// 以上是Foo的静态方法

Foo.a(); // 4, 直接调用Foo()的静态方法,因为Foo()没有执行,所以执行的是后面的静态方法
// 执行了Foo(),实现了Foo.a()的替换,内部属性a也初始化
let obj = new Foo();   // 如果将这句放在开头,那输出 1 2 1
obj.a();  // 2,obj实例上有两个a(),自身的和原型链上的,先找自身的
Foo.a();  // 1,因为执行 new Foo() 的时候对 Foo.a 进行了替换

只有对象内部不存在该属性时,才去它的原型链查找!赋值undefined也是存在的,不需要去原型对象中查找!

function Ctor(x){
  this.x = x
}
Ctor.prototype.x = '12345'
const a = new Ctor(4)
a.x = undefined
console.log(a.x)  // undefined,赋值什么就是什么
const b = new Ctor(5)
delete b.x
console.log(b.x)  // '12345'
var a = 0;
function A() {
  this.a = 1;
  return this;
}
A.a = 2;
A.prototype = {
  a: 3,
  setA(value) {
    this.a = value;
    return this;
  },
};
console.log(new A().a);  // 1,构造函数正常返回,this指向新生成的实例
console.log(A().a);  // 1,构造函数的普通函数this指向window,this.a就是window.a,覆盖 var a
console.log(a);  // 1
console.log(new A().setA(10).a);  // 10,new A().setA(10)中setA()中的this指向的是new A(),所以原型的a没有变,实例的a变了
console.log(A().setA);  // undefined
console.log(A().setA);  // error,setA不是function
  1. 事件循环、Promise
setTimeout(function () {  // 宏任务
  console.log('three');
}, 0);
 // 具有then方法的对象,Promise.resolve后会将其转为promise对象,然后执行它内部的then方法,微任务
let thenable = { 
  then: function(resolve, reject) {
    resolve(42);
  }
};
// 对象内部的then方法执行完后,p1的状态就变成resolved,再执行最后的then方法,微任务,而且排在微任务的第二轮
let p1 = Promise.resolve(thenable);
p1.then(function (value) {  
  console.log(value);  
});
// 当参数是原始值,会直接返回一个resolved的promise对象,微任务
const p = Promise.resolve('Hello');
p.then(function (s) {
  console.log(s)
});
console.log('one');
// one hello 42 three

在执行微任务队列过程中新产生的微任务,也会在这一轮微任务中执行。

setTimeout(_ => console.log(1)) 
new Promise(resolve => {
    resolve()
    console.log(2);  // resolve或reject不会终结promise参数函数的执行
}).then(_ => {
  setTimeout(_ => console.log(3))
  console.log(4)
  Promise.resolve().then(_ => {  // 第1轮微任务
    console.log(5)
  }).then(_ => {
    Promise.resolve().then(_ => {  // 第2轮微任务
      console.log(6)
    })
  })
})
console.log(7);
// 2 7 4 5 6 1 3 
new Promise(resolve=>{
  console.log(1)
  setTimeout(()=>{
    resolve()
  },0)
})
.then(()=>{
  console.log(4)
})
.then(()=>{
  console.log(5)
});
setTimeout(() => {
  console.log(2)
}, 0);
console.log(3)

// 1 3 4 5 2
console.log('begin') 
setTimeout(() => {  
  console.log('setTimeout 1')  
  Promise.resolve()
  .then(() => {  
    console.log('promise 1')  
    setTimeout(() => {  
      console.log('setTimeout2')  
    })  
  })
  .then(() => {  
    console.log('promise 2')  
  }) 
}, 0); 
console.log('end')
// begin, end, setTimeout 1, promise 1, promise 2, setTimeout2
  1. 函数参数的传递方式
  • 函数参数如果是原始类型的值,传递方式是传值传递,在函数内部修改值不会影响到函数外部。
  • 如果是复合类型,传递方式是传址传递,在函数内部修改参数将会影响到原始值。但如果是替换掉整个参数则不会影响到原始值,因为重新对a赋值会使得a指向另一个地址,保存在原地址上的值当然不受影响。
var a = [1,2,3,4]
function set(a){
    a = [5,6,7,8]
}
set(a)
console.log(a)
// [1,2,3,4]
var a = [1,2,3,4]
function set(a){
    a.push(5,6,7,8)
}
set(a)
console.log(a)
// [1,2,3,4,5,6,7,8]
obj = { a: 0 };
function test(obj) {
    obj.a = 1;
    obj = { a: 2 };
    obj.b = 3;
    console.log(obj);  // { a: 2, b: 3}
}
test(obj);
console.log(obj); // { a: 1 }
  1. typeof
console.log(typeof typeof typeof null)   // 'string'
console.log(typeof typeof null)   // 'string'
  1. 函数的参数作用域 「参数变量是默认声明的」,所以不能用 let 或 const 再次声明,会报错。 如果用 var 就相当于变量的重复声明,覆盖前面的值,输出 1。
function f(x){
  var x;
  console.log(x)
}
f(5) // 5
function f(x){
  var x = 4;  // var 变量的重复声明
  console.log(x)
}
f(5) // 4
function f(x){
  let x = 4;
  console.log(x)
}
f(5) // Error
  1. this指向、作用域
  • 函数、函数内的嵌套函数、对象的方法内的嵌套函数:指向window
  • 无论变量是在函数作用域还是全局作用域,只要没有声明就使用,一律为全局变量。
  • obj.fn = fn这里的fn中的this指向obj
  • obj1.obj2.fn = fnfn中的this会指向离自己最近的对象obj2
  • obj.fn这种形式的取出来再调用的,会导致隐式绑定丢失问题,具体的this指向要看运行环境
  • 作用域和引用类型的关系!
inner = 'window';
function say() {
    console.log(inner);
    console.log(this.inner);
}
let obj1 = (function() {
    var inner = '1-1';  // this指向 window
    return {
        inner: '1-2',  // this指向的是返回的这个对象
        say() {
            console.log(inner);
            console.log(this.inner);
        }
    }
})();
let obj2 = (function() {
    var inner = '2-1';
    return {
        inner: '2-2',
        say() {
            console.log(inner);
            console.log(this.inner);
        }
    }
})();
say();  // window, window,
obj1.say();  // 1-1, 1-2
obj2.say();  // 2-1, 2-2
obj1.say = say;   
obj1.say();  // window, 1-2
obj1.say = obj2.say; 
obj1.say();  // 2-1, 1-2

参考文章

  • 第一个obj1.say()obj1中的自执行函数调用者是windowsay函数inner在函数本身中没有,因此需要向上层作用域,对象是不会产生作用域的
  • 第二个obj1.say()函数属于引用类型,因此这里的赋值会直接覆盖obj1的say方法,通过obj1.say()调用的say函数,就是调用的全局作用域的say函数。
  • 第三个obj1.say():同上,在赋值后,我们通过 obj1调用的say函数依旧是obj2say函数
var name = '123'
var obj = {
    name:'456',
    getName: function() {  // getName() 隐式绑定 this
       // console.log(this.name);  这时会输出'123'
        function printName() {  // printName没有明确的调用对象
            console.log(this.name);
        }
        printName();
    }
}
 
obj.getName()  // '123',调用对象方法内的嵌套函数,指向window
var length = 10;
function fn() {
  return this.length + 1;
}
var obj = {
  length: 5,
  test1() {
    return fn();
  }
}
console.log(obj.test1())  // 11
obj.test2 = fn  // obj.test2 === 6
console.log(obj.test1() === obj.test2())  // false
  1. css样式 优先级相同的样式,后面声明的会覆盖前面声明的,和使用的位置顺序无关。
<style>
  .classA {color: red} 
  .classB {color: blue}
</style>
<p class="classB classA">文字</p>  // 蓝色

css的盒模型以及背景颜色的填充

<style>
 .box {
   width: 10px;
   height: 10px;
   border: 1px solid red;
   margin: 2px;
   padding: 2px;
   background: blue;
 }
 #borderBox {
   box-sizing: border-box;
 }
 #contentBox {
   box-sizing: content-box;
 }
</style>
</head>
<body>
<div>请问下面两个 div 元素,蓝***域的宽高各是多少像素?</div>
<div id="borderBox" class="box">IE盒模型: 8px</div>  
<div id="contentBox" class="box">标准盒模型: 14px</div>
</body>
  1. Object.prototype 和 Function.prototype
  • 构造函数和函数的原型都是Function.prototype,A.__proto__ = Function.prototype
  • 某实例.__proto__一直往前追溯会追溯到Object.prototype
  • Function.prototype.__proto__等于Object.prototype
Function.prototype.a = () => console.log(1)
Object.prototype.b = ()=> console.log(2);
function A(){}
const a = new A();
a.a();  // Error,a.a 为undefined, 并不是一个函数
a.b();  // 2
  1. Promise的链式调用
new Promise((resolve, reject) => {
  reject(1);
})
.catch((err) => {
  console.log(err);
})
.then((v) => {
 console.log(v);
},(k) => {
 console.log(k);
}
);
// 1 undefined
  1. 构造函数
  • 如果没有使用new调用,构造函数就变成了普通函数,不会生成实例对象,这时 this指向全局对象,this.x的属性x也变成了全局变量
  • 如果return语句后面跟着一个非空对象,new就会返回这个指定的对象。但是对于return this则刚好返回的就是新生成的实例对象。
  • 变量赋值了但没有声明,则会创建全局变量,此时没有变量提升
  • 如果重复声明,会覆盖前面的值。
var a = 1;
function print() {
  a = 2;  
}
print();
console.log(a);  // 2
  1. 箭头函数
function Person(name) {  
   this.name = name; 
}  
Person.prototype.print = function () {  
  return this.name; 
}; 
Person('abc'); 
const a = new Person('abc').print.call({}); 
console.log(a);  // undefined,call绑定的this对象没有name属性
const fn = () => {  
  this.x = 'z'; 
}; 
const b = {  
  x: 'y' 
}; 
fn.call(b); 
console.log(b);  // {x: 'y'},箭头函数不改变
  1. 变量提升
function Foo() {
  getName = function() { console.log(1); }
  return this;
}
Foo.getName = function() { console.log(2); }
Foo.prototype.getName = function() { console.log(3); }
var getName = function() { console.log(4); }
function getName() { console.log(5); }

Foo.getName();  // 2
getName();  // 4,函数声明提升和变量提升的顺序问题
// 由于未声明的变量默认全局属性,所以执行Foo()时会将内部的getName()提到外部。
// 后者覆盖前者更新getName。
// 构造函数Foo()普通调用时,内部的this指向window,所以调用this.getName()
Foo().getName();  // 1
getName();  // 1
new Foo.getName(); // 2,会先执行Foo.getName()返回2, new 2 输出2 
new Foo().getName();  // 3
new new Foo().getName()  // 3,相当于new(new Foo().getName())
  1. 原型对象
// 实现 sum方法
let arr = [1, 2, 3];
arr.sum();
// 写定义
Array.prototype.sum = function() {
  let a = this;  // a 就是数组
  return a.reduce((a, b) => a + b, 0);
}
  1. 作用域 + this指向 其实就是区分 作用域和执行上下文,作用域是 js 代码在解释阶段确定的、即静态作用域,执行上下文包括this指向,是 js 代码在运行阶段才能确定的! 不要混淆!
var length = 10;
function fn() {
  return this.length;
}
var obj = {
  length: 5,
  test1: function() {
    return fn();
  }
};
obj.test2 = fn;  // 隐式绑定,函数在对象内部或外部是没差的
// 说结果
console.log(obj.test1());  // 10,fn()是直接作为函数调用的
console.log(fn() === obj.test2());  // 10 ==? 5, false
  1. 作用域 + this指向
function bar() { console.log(myName); }
function foo() {
  var myName = '快手';
  bar();
}
var myName = '用户增长';
foo();  // '用户增长',静态作用域
var a = 'globalA';
var obj = {
  a: 'objA',
  test  // 键名和键值同名时,写一个即可
};
function test() { console.log(this.a); }
obj.test();  // 'objA'
const globalTest = obj.test;  
globalTest();  // 'globalA'
// 因为obj和obj.test 存储在两个内存地址,直接取出obj.test对应的内存地址再调用,取决于其所在环境
  1. 箭头函数【多看】
var name = 'window';
const person1 = {
  name: 'person1',
  sayName: () => {
    console.log(this.name);
  }
}
person1.sayName();  // 注意是window,因为箭头函数的 this 取决于外层作用域,这里是全局对象。
var name = 'window';
const person1 = {
  name: 'person1',
  sayName: function () {
    var name = 'p2';
    (() => {
      console.log(this.name);
    })()
  }
}
person1.sayName();  // 'person1',同理这里的箭头函数的外层作用域是 person1。
  1. 闭包 示例1:
for(var i = 0; i < 5; i++) {
  setTimeout(() => {
      console.log(i);
  }, 0);  // 5个5
}

改正方式一:

for(var i = 0; i < 5; i++) {
  (function(j) {
    setTimeout(() => {
      console.log(j);
  }, 0);  // 0,1,2,3,4
  })(i);
}

改正方式二: var --> let。

示例2:

var n = 10
function fn(){
    var n = 20;
    function f() {
       n++;
       console.log(n)
     }
    f()
    return f
}

var x = fn()
x()
x()
console.log(n)
// 21 22 23 10
  1. parseInt
['1', '2', '3'].map(parseInt)
 
==>将字符串'1','2','3'作为元素,0,1,2作为下标分别调用parseInt函数
 
parseInt('1', 0);  // 第2个参数0,忽略,按十进制计算,返回1
parseInt('2', 1);  // 第2个参数1,不在2~36,返回 NaN
parseInt('3', 2);  // 用二进制解析,但数字3不在二进制的0\1之间,结果 NaN
  1. 事件循环
let p = new Promise(function(resolve, reject) {
  console.log('Promise');
  resolve();
});
p.then(function() {
  console.log('resolved.');
});
console.log('Hi!');
// Promise  Hi!   resolved
  1. async / await: await的下一句是微任务,对于await这一句: 如果是值,那么直接返回;如果是异步,那么会等待。
async function async1() {           
  console.log("async1 start");          
  await  async2();            
  console.log("async1 end");      
}        
async  function async2() {          
  console.log( 'async2');      
}       
console.log("script start");       
setTimeout(function () {            
  console.log("settimeout");        
},0);        
async1();        
new Promise(function (resolve) {           
  console.log("promise1");          
  resolve();        
}).then(function () {            
  console.log("promise2");       
});        
console.log('script end');
// script start
// async1 start
// async2
// promise1
// script end
// async1 end
// promise2
// settimeout
  1. setTimeout 和 for 循环 纯同步代码是正常的,
for(var i = 0; i < 3; i++) {
  console.log(i);
}
// 0 1 2

当 for 循环内部的函数是异步时,结果如下

for(var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 1000);
}
// 3 3 3

这是因为,(1)var 只有函数和全局作用域,上面的 var i 实际上是声明了一个全局变量,每次改变都是改变同一个全局变量,后面的会覆盖前面的;(2)for 循环是同步的,setTimeout 是异步的,事件循环机制让同步代码先执行,执行完后再执行异步,而此时 i 都加完了,等于3。

正确做法一:let

for(let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 1000);
}
// 1s后,同时输出 0 1 2 

for 循环是多个块我们通过 let 来实现块级作用域,每次循环都是独立存在的,当前的 i 只在本轮循环有效,每一次循环的 i 都是一个新变量

(js引擎内部会记住上一轮循环的值,本轮循环初始i时是基于上一轮基础的,所以即使每一轮的变量i都是重新声明的,也能知道上一轮循环的值)

正确做法二:自执行函数(闭包1)

通过自执行函数提供闭包条件(私有作用域),并将传入的i值保存在闭包中

for(var i = 0; i < 3; i++) {
  (function(x) {   // 形参 x, 换成 i 也可以
    setTimeout(() => console.log(x), 1000);
  })(i);  // 保存变量i,自执行函数形成块级作用域
}
// 1s后,同时输出 0 1 2

正确做法三:闭包2

将外层的引用保存在内存中,当setTimeout中定义的操作执行时,则访问对应闭包保存的i值。

for(var i = 0; i < 3; i++) {
  setTimeout(function(i) {  // 不能用箭头函数
    return function() {
      console.log(i);
    }
  }(i), 1000);
}
// 1s后,输出 0 1 2 
  1. 把 setTimeout 封装成 Promise
function f(delay) {
  let p = new Promise((resolve) => {
    setTimeout(resolve, delay);
  });
  return p;
}
  1. this指向
let obj = {
  a()  {
    return () => {
      return () => {
        console.log(this);
      }
    }
  }
}
obj.a()()();  // obj
let obj = {
  a() {
    console.log(this)
  },
  name: 'Jack',
  showName: this.name
}
obj.a();  // obj
console.log(obj.showName);  // window
let a = obj.a;
a();  // window,未声明的变量会成为全局变量
  1. 静态作用域
var count = 10;
function a() {
  return count + 10;
}
function b() {
  var count = 20;
  return a();
}
console.log(b());   
// 20
  1. 事件循环:Promise.resolve() then 异步并行执行,按顺序来,如果都在promise实例的第1个 then里面,那么就按书写顺序,否则如果一个在第2个then,一个在第1个then,那么后面的会先执行。

Promise 状态只能改变一次!

new Promise(resolve => {
  console.log(3);
  resolve('resolve');
  console.log(4);
  reject(error);
}).then((res) => {   // 第2顺序
  console.log(res);
}).catch((err) => {
  console.log(err);
});
Promise.resolve().then(() => {  //第1顺序
  console.log(5);
})
// 3, 4, 'resolve', 5
new Promise(resolve => {
  console.log(3);
  resolve('resolve');
  console.log(4);
  reject(error);
}).catch((err) => {
  console.log(err);
}).then((res) => {   // 第2顺序
  console.log(res);
});
Promise.resolve().then(() => {  //第1顺序
  console.log(5);
})
// 3, 4, 5, 'resolve'
Promise.resolve().then(() => {
  console.log(0)
  return Promise.resolve(4);
}).then((res) => {
  console.log(res)
})
Promise.resolve().then(() => {
  console.log(1)
}).then(() => {
  console.log(2)
}).then(() => {
  console.log(3)
}).then(() => {
  console.log(5)
}).then(() => {
  console.log(6)
})
// 0, 1, 2, 3, 4, 5, 6
Promise.resolve().then(() => {
  console.log(0)
  return 4;
}).then((res) => {
  console.log(res)
})
Promise.resolve().then(() => {
  console.log(1)
}).then(() => {
  console.log(2)
}).then(() => {
  console.log(3)
}).then(() => {
  console.log(5)
}).then(() => {
  console.log(6)
})
// 0, 1, 4, 2, 3, 4, 5, 6
  1. 闭包
function f1() {
  var n = 999;
  nAdd = function(){  // 全局变量
    n += 1
  }
  function f2(){
    console.log(n);
  }
  return f2;
}
var res = f1();
res(); //999
nAdd();
res(); //1000
  1. 构造函数的 return
function Foo() {
  this.a = 1;
  // 如果return的是一个对象,那就直接返回该对象
  return {   
    a: 4,
    b: 5,
  };
}
Foo.prototype.a = 6;
Foo.prototype.b = 7;
Foo.prototype.c = 8;
var o = new Foo();
console.log(o.a); //4
console.log(o.b); //5
console.log(o.c);  //undefined
  1. this 指向
var name = "window";
var object = {
  name: 'MyObject',
  getName: function() {
    var that = this;   // 如果不保存this,那就输出window,考察的是this指向[对象方法中的函数]
    return function() {
      return that.name;
    }
  }
}
console.log(object.getName()());  // MyObject
var name = 'x';
var people = {
  name: 'y',
  setName: (name) => {   //@1
    this.name = name;
    return () => { //@2,如果1和2都改为函数声明,则返回的是'x','x'
      return this.name
    }
  }
}
var getName = people.setName(name);  
console.log(people.name);  // 'y'
console.log(getName());  // 'x'
首先箭头函数使得this从动态变成静态,
因此【不能用在对象的方法中,当调用people.setName()时,this指向的是全局对象】
导致setName的作用域就是全局作用域。
var length = 10;
funtion fn() {
    return this.length + 1;
}
var obj = {
    length: 5,
    test1: function() {
        return fn();
    }
}
obj.test2 = fn;
console.log(obj.test1());  // 11,对象方法中的嵌套函数
console.log(fn());  // 11,直接调用
console.log(obj.test2());  // 6,对象中的方法
  1. 原型链
Function的一个内置的构造器,所有的函数都是Function的实例!构造函数也是Function的实例。
Function.prototype.a = () => console.log(1);
Object.prototype.b = () => console.log(2);
function A() {}
var a = new A();
A.a();  //1,A中没有a方法,就去A.prototype中找也没有,A的原型的原型是Object。
            。但是A是Function的实例,所以就找到了Function.prototype。
A.b();  //2            
a.a();  // a.a is not a function,对象a中没有a这个方法,往上找到了Object.prototype,只有b属性、没有a属性
a.b();  //2
console.log(a instanceof A)  //true
console.log(a instanceof Function)  //false
console.log(a instanceof Object) //true
构造函数A能找到Function.prototypeObject.prototype的属性,但a只能找到Object.prototype的。
a:a.__proto__为A.prototype,A.prototype.__proto__Object.prototype,再往上为null。
A:A.__proto__Function.prototypeFunction.prototype__proto__Object.prototype
  1. 数据类型
if([]) console.log('1');
if([].length) console.log('2');
if({} === {}) console.log('3');
if({} == {}) console.log('4');
// 输出
1
  1. 作用域
var a = 100;
function fn() {
    var b = 30;
    function bar() {
        console.log(a + b);  // 100+30
        console.log(this.b); //400
    }
    return bar;
}
var x = fn(), b = 400;
x();
// 输出
130400
  1. Promise
const pro = new Promise((resolve, reject) => {
  const innerpro = new Promise((resolve, reject) => {
      setTimeout(() => {
          resolve(1);  // 一个promise只能resolve一次,所以根据事件循环不会再输出1
      }, 0);
      console.log(2);
      resolve(3);
  });
  innerpro.then(res => console.log(res));
  resolve(4);
  console.log('pro');
});
pro.then(res => console.log(res));
console.log('end');
// 2, pro, end, 3, 4
  1. 作用域链和原型链
var b = 'window';
function c() {
    console.log(b);
    console.log(this.b);
}
var obj = {
    b: '1-1',
    c: function() {
        console.log(b);
        console.log(this.b);
        return {
            b: '1-2',
            c: function() {
                console.log(b);
                console.log(this.b);
            }
        }
    }
}
c();  // window window
obj = obj.c();  // window  '1-1' 
obj.c = c;  
obj.c();  // window '1-2'
  1. 事件循环
setTimeout(() => {
  console.log(1);
})
const p1 = Promise.resolve(() => {
  console.log(2);
})
const p2 = new Promise((resolve, reject) => {
  console.log(3);
  resolve();
})

Promise.race([p1, p2]).then(() => {
  console.log(4);
})

Promise.all([p1, p2]).then(() => {
  console.log(5);
})

console.log(6)

// 3 6 4 5 undefined 1
  1. GUI线程和JS线程互斥
function demo() {
  const now = Date.now();
  document.body.style.backgroundColor = 'red';
  while (Date.now() - now <= 2000) {
    continue;
  }
  document.body.style.backgroundColor = 'blue';
}

2s 后直接变蓝。

  1. 对象的for..in for ..in 的 key 的排序规则:
  • key会自动转为字符串;
  • 如果key是整数或整数类型的字符串,那么会按照从小到大排序
  • 如果key中既有整数也有普通字符,那么整数放在最前面。

怎么按照原来顺序呢?将它变成一个字符串,例如在键名后面加点号.

let obj = {
  a:'a',
  b: 'b',
  1:'1'
}
for(let key in obj){
  console.log(key)
}
// 1 a b