- 构造函数、原型链、静态方法
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
- 事件循环、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
- 函数参数的传递方式
- 函数参数如果是原始类型的值,传递方式是传值传递,在函数内部修改值不会影响到函数外部。
- 如果是复合类型,传递方式是传址传递,在函数内部修改参数将会影响到原始值。但如果是替换掉整个参数则不会影响到原始值,因为重新对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 }
- typeof
console.log(typeof typeof typeof null) // 'string'
console.log(typeof typeof null) // 'string'
- 函数的参数作用域 「参数变量是默认声明的」,所以不能用 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
- this指向、作用域
- 函数、函数内的嵌套函数、对象的方法内的嵌套函数:指向
window。 - 无论变量是在函数作用域还是全局作用域,只要没有声明就使用,一律为全局变量。
obj.fn = fn这里的fn中的this指向obj。obj1.obj2.fn = fn,fn中的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中的自执行函数调用者是window,say函数的inner在函数本身中没有,因此需要向上层作用域,对象是不会产生作用域的。 - 第二个
obj1.say():函数属于引用类型,因此这里的赋值会直接覆盖obj1的say方法,通过obj1.say()调用的say函数,就是调用的全局作用域的say函数。 - 第三个
obj1.say():同上,在赋值后,我们通过obj1调用的say函数依旧是obj2的say函数 。
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
- 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>
- 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
- Promise的链式调用
new Promise((resolve, reject) => {
reject(1);
})
.catch((err) => {
console.log(err);
})
.then((v) => {
console.log(v);
},(k) => {
console.log(k);
}
);
// 1 undefined
- 构造函数
- 如果没有使用
new调用,构造函数就变成了普通函数,不会生成实例对象,这时this指向全局对象,this.x的属性x也变成了全局变量。 - 如果
return语句后面跟着一个非空对象,new就会返回这个指定的对象。但是对于return this则刚好返回的就是新生成的实例对象。 - 变量赋值了但没有声明,则会创建全局变量,此时没有变量提升。
- 如果重复声明,会覆盖前面的值。
var a = 1;
function print() {
a = 2;
}
print();
console.log(a); // 2
- 箭头函数
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'},箭头函数不改变
- 变量提升
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())
- 原型对象
// 实现 sum方法
let arr = [1, 2, 3];
arr.sum();
// 写定义
Array.prototype.sum = function() {
let a = this; // a 就是数组
return a.reduce((a, b) => a + b, 0);
}
- 作用域 + 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
- 作用域 + 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对应的内存地址再调用,取决于其所在环境
- 箭头函数【多看】
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:
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
- 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
- 事件循环
let p = new Promise(function(resolve, reject) {
console.log('Promise');
resolve();
});
p.then(function() {
console.log('resolved.');
});
console.log('Hi!');
// Promise Hi! resolved
- 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
- 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
- 把 setTimeout 封装成 Promise
function f(delay) {
let p = new Promise((resolve) => {
setTimeout(resolve, delay);
});
return p;
}
- 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,未声明的变量会成为全局变量
- 静态作用域
var count = 10;
function a() {
return count + 10;
}
function b() {
var count = 20;
return a();
}
console.log(b());
// 20
- 事件循环: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
- 闭包
function f1() {
var n = 999;
nAdd = function(){ // 全局变量
n += 1
}
function f2(){
console.log(n);
}
return f2;
}
var res = f1();
res(); //999
nAdd();
res(); //1000
- 构造函数的 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
- 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,对象中的方法
- 原型链
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.prototype和Object.prototype的属性,但a只能找到Object.prototype的。
a:a.__proto__为A.prototype,A.prototype.__proto__为Object.prototype,再往上为null。
A:A.__proto__为Function.prototype,Function.prototype__proto__为Object.prototype。
- 数据类型
if([]) console.log('1');
if([].length) console.log('2');
if({} === {}) console.log('3');
if({} == {}) console.log('4');
// 输出
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();
// 输出
130, 400
- 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
- 作用域链和原型链
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'
- 事件循环
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
- 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 后直接变蓝。
- 对象的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