变量
原始值 -> 基本类型
- Number
- String
- Boolean
- undefined
- null
引用值
引用值就是拿的以下类型在堆内存的地址
object
array
function
date
RegExp
栈内存 Stack
- 原始值在占栈内存中存储
堆内存 Heap
- 引用值是存在堆内存当中,然后在栈内存中存储对应的指针地址
- 修改原来的值会有影响,重新赋值没影响
基本语法,运算符
任何数据类型的值加上字符串,都得字符串
字符串相对应的ASCII码(字符串相对应的十进制码),多个字符的,从左到右依次进行对比,直到比较出ASCII码大大小为止
//字符串比较
let bool = "1.5" > "11"; //true
NaN与任何东西都不相等,包括它自己
有值范围的判断,或者条件多个,用if。 有定值的,用switch
逻辑运算
undefined,null,NaN,"",0,-0,false
除上面以外全部都是真
循环
for(var i = 0 ; i<10; i++){
console.log(i)
}
//等同于
//for循环语句中,中间的语句是判断的作用,为真则执行
var i = 0;
for(;i<10;){
console.log(i);
i++;
}
//可转换成while
while(i<10){
console.log(i);
i++;
}
//终止循环,break等价于i=0
var i = 1;
for(;i;){
console.log(i);
i++;
if(i==11){
break; // i = 0;
}
}
//continue 跳过当前循环
//typeof 引用类型返回都是object
//number string boolean object undefined function
typeof(null) // object
typeof(undefined) //undefined
typeof(function(){}) //function
typeof(a) //undefined 未定义的变量,用typeof不会报错
//typeof的返回值是字符串类型
typeof(typeof(a)) //string
Number(true) // 1
Number('true') //NaN
Number(null) //0
Number(undefined) //NaN
Number('1a') //NaN
//会先通过Number(值)转换后,再判断
isNaN(undefined) //true
isNaN(null) //false
parseInt(true) //NaN
parseInt(null) //NaN
parseInt('ab12') //NaN
parseInt('12ab34 ') //12
parseInt('3.99') //3
parseInt('c',16) //12
parseFloat('3.147').toFixed(2) //3.15 (四舍五入)
'1'>2 //false
'a'<'b' // 转换成ascii再对比
NaN == NaN //false
null == 0 // false ,既不大于也不小于,也不等于
undefined == 0 // false ,既不大于也不小于,也不等于
undefined == null // true
斐波那契数列
function fibonacci (n) {
if ( n <= 1 ) {return 1};
return fibonacci(n - 1) + fibonacci(n - 2);
}
阶乘
function jc(num){
if(num<0) return 0;
if(num==1) return 1
return num * jc(num-1)
}
函数
// 函数声明
function test(){}
// 函数表达式,函数表达式
var test = function (){}
// 形参和实参长度
function test(a,b){
console.log(test.length) // 2
console.log(arguments.length) // 3
}
test(1,2,3)
// 可在函数内改变实参的值
function test(a,b){
a = 3
console.log(arguments[0]) // 3
}
test(1,2)
// 如果实参没有传值,在函数体内赋值是无效的
function test(a,b){
b = 3 //没有传实参
console.log(arguments[0]) // undefined,参数默认值是undefined
}
test(1)
function test(){
return //js引擎会隐式添加return
}
递归
p5 前半段
预编译
Javascript 引擎:
检查通篇的语法是否有错误
预编译的过程
解释一行,执行一行
test() //正常执行调用
function test(){
console.log(1);
}
console.log(a) // undefined;
var a = 1
函数声明,整体提升。
变量只有声明提升,赋值是不提升的 (声明提升,字面量不提升)。
var a = 1;
b = 2;
// 同等,没有区别
console.log(window.b);// a = window.a
// b = window.b
window = { // 全局域
a:1,
b:2
}
function test(){
var a = b = 1; //函数内部没有用var声明变量而直接赋值,会提升到全局变量window上
}
test();
console.log(b)// 1
console.log(a)// a is not define
console.log(window.a)// undefined 访问对象里不存在的属性,默认是undefined
function test(b){
this.d = 3;
var a = 1;
function c(){}
}
test(123);
console.log(window.d) //3
const AO = {
b:undefined,
a:undefined,
c:function c(){},
arguments : [123],
this:window
}
函数上下文:
AO :activation object 活跃对象,函数上下文
- 提取形参和变量声明提升(形参和变量相同时,也是undefined)
- 实参值赋值给形参
- 函数声明提升 (形参和变量 与 函数名相同时,覆盖)
- 执行 (变量赋值)
形参和实参赋值,var变量声明提升,函数声明提升,执行
没var的变量不会提升,会执行到的时候才会直接赋值挂到window,如果在执行到当前代码之前使用,会报 x is not defined
函数声明提升赋值,执行的时候就不会再赋值
var a = 1;
function a(){
console.log(2)
}
console.log(a) //1
//----
function test(){
console.log(b)
if(a){
var b= 2; s
}
console.log(c) // 情况1:没用var声明的变量,不会提升,此处会报not defined
c = 3
console.log(c); // 情况2:执行以后 c 才会赋值到window下,3
}
var a;
test();
a = 1;
console.log(a);
作用域,作用域链
[[scope]] 函数创建时,生成的一个js内部的隐式属性。
[[scope]] -> scope chain (作用域链) -> GO -> AO
函数存储作用域链的容器,作用域链
- AO / GO
- AO,函数的执行期上下文
- GO,全局的执行期上下文
- 函数执行完成以后,AO是要销毁的,它是一个即时的存储容器
- AO / GO
每一个函数在被定义时就包含全局的执行上下文 GO
在函数被执行前一刻生成AO (预编译)
a函数执行前一刻,生成[[scope]]
- 所有函数自身的AO都是排在最顶端的
b函数执行前一刻,生成[[scope]]
- 为什么外面无法访问函数内部的变量?因为作用域链没有内部函数的AO
function a(){
function b(){
function c(){
}
c()
}
b()
}
a()

闭包
当内部函数被返回到外部并保存时,一定会产生闭包,闭包会产生 原来的作用域链 不释放。
过渡的闭包可能会导致内存泄露,或加载过慢。
立即执行函数
instantly invoked function expression
//W3C规范
(function(){
}());
//或者
(function(){
})()
括号包起来的都叫表达式
一定是表达式才能被执行符号执行
// 可以
(function test1(){
console.log(1);
})()
// 函数表达式,可以
var test2 = function(){
console.log(1);
}();
// 语法错误
function test3(){
console.log(1);
}();
// 不报错
function test3(){
console.log(1);
}(4); //认为表达式
console.log((3,4)) //4
// 表达式忽略函数名
var a = 10;
if(function b(){}){ // (function b(){}) 用括号括起来为表达式,表达式忽略函数名,此事b为未定义,用typeof包裹不会报错,返回undefined
a+=typeof(b)
}
console.log(a) //10undefined
//累加器
function calc(){
var num = 0
function add(){
return num++
}
return add
}
//班级学生名字操作
function classroom(){
var students = []
function enter(name){
if(!students.includes(name)) {
students.push(name)
}
return students
}
function pop(name){
var idx = students.indexOf(name)
if(idx !==-1){
students.splice(i,1)
}
return students
}
function get(){
return students
}
return [enter,pop,get]
}
构造函数
- 大驼峰
- this在没有实例化的时候指向window,new实例化后指向对象
- New的时候,实际上系统把this原本指向window的,转向实例化的对象
// 构造函数new原理
function Car(color,brand){
//AO = {
// this:{
// color:color,
// brand:brand
// }
//}
this.color = color;
this.brand = brand;
// return this
}
var car1 = new Car('red','Benz');
// 等同于
function Car(color,brand){
var me = {};
me.color = color;
me.brand = brand;
return me;
}
var car = Car('red','Benz')
// 如果return原始类型,实际上还是会隐式的return this
// 如果return引用类型,则会返回
function Car(){
this.color = 'red'
return 12 //无效,还是会返回this
return {} //返回空对象
}
var car = new Car();
console.log(car.color) //red
包装类
new Number(1)
new String('abc')
原始类型,访问属性,默认先new包装类,无法保存,执行delete
undefined 和 null 不能经过包装类转换为对象,所以没有toString()方法
var a = 123
a.len = 3 // undefined
//内部执行:
//new Number(123).len = 3 ; 无法保存,执行 delete
var a = new Number(123);
a.len = 3;
console.log(a.len) //3
//字符串本身没有length属性,是中间经过了一层包装类
var str = 'abc'
str.length = 1 //无效,不会改变。new String(str).length = 3 ; 无法保存,执行 delete
console.log(new String(str).length);
var arr = [1,2,3,4,5]
arr.length = 3
arr // [1,2,3] 会截断
原型
- prototype是定义构造函数构造出的每个对象的公共祖先
function Handphone (color,brand){
this.color = color;
this.brand = brand;
this.screen = '18:9';
this.system = 'Android'
}
Handphone.prototype.rom = '64G';
Handphone.prototype.ram = '6G';
Handphone.prototype.screen = '16:9';
var hp1 = new Handphone('red','小米');
var hp2 = new Handphone('black','华为');
console.log(hp1.rom); //64G 可以读取到原型上的属性
console.log(hp2.ram); //6G
console.log(hp1.screen); //18:9 构造函数自己里面有的,就不往原型上去找
- constructor
function Telephone(){}
function Handphone(color){
this.color = color;
}
//constructor -> 默认指向构造函数本身
//可以更改
Handphone.prototype = {
constructor:Telephone
}
console.log(Handphone.prototype)
理解:
P10 中间
Q:获取字节长度
//中文是两个字节,英文是一个字节
function getBytes(str){
var bytes = str.length;
for(var i = 0; i<str.length;i++){
if(str.charCodeAt(i)>255){
bytes++;
}
}
return bytes;
}
window和return
插件写法:
;(function(){
function Test(){
}
window.Test = Test;
})();
var test = new Test();
原型链
原型链 最重要的属性是 __proto__
原型链:沿着__proto__
向上去找原型上的属性,就形成链条式的继承关系
原型链的顶端是Object.prototype
function Obj(){}
var o1 = new Obj();
o1.__proto__ === Obj.prototype //true
Obj.prototype.__proto__ === Object.prototype //true
Object.creat(对象) //创建对象,Object.creat(null):空对象,没有原型
function Obj(){}
Obj.prototype.num = 1;
var obj1 = Object.create(Obj.prototype);
var obj2 = new Obj();
//以上两种效果一致;
console.log(obj1);
console.log(obj2);
简易计算器插件:
;(function()){
var Compute = function(){
Compute.prototype = {
plus:function(a,b){
return a + b;
},
minus:function(a,b){
return a - b;
}
mul:function(a,b){
return a * b;
}
div:function(a,b){
return a / b;
}
window.Compute = Compute;
}
}
})();
call/apply
改变this指向
var a = [1,2,3]
Object.prototype.toString.call(a) // "[object Array]"
全局this -> window 预编译函数this -> window apply/call改变this指向 构造函数的this指向实例化对象
圣杯模式
Buffer?
function inherit(Target,Origin){
function Buffer(){}
Buffer.prototype = Origin.prototype;
Target.prototype = new Buffer();
}
链式调用
return this
hasOwnProperty
instanceof
function Car(){}
var car = new Car();
function Person()
console.log(car instanceof Car);
var a = []
console.log(a instanceof Array)
callee
var sum = (function(n){
if(n<=1){
return 1;
}
return n + arguments.callee(n-1);//找到本身的这个函数
})(100)
caller
test1();
function test1(){
test2()
}
function test2(){
console.log(test2.caller); //function test1(){}
}
数组
var arr = []; //数组字面量
waterfall?
(0,function)()
//当您想调用方法而不将对象作为this值传递时:
var obj = {
method: function() { return this; },
v:2
};
obj.method(); // {method: ƒ, v: 2} this指向obj本身
(0,obj.method()) // {method: ƒ, v: 2}
obj.method()===obj // true
(0,obj.method()===obj) // true
(0,obj.method)()===obj // false 此处先获取method函数,再执行,所以this指向windows
//使用eval:
(function() {
(0,eval)("var foo = 123"); // indirect call to eval, creates global variable
})();
console.log(foo); // 123
(function() {
eval("var bar = 123"); // direct call to eval, creates local variable
})();
console.log(bar); // is not define
//根据上下文,它可能是参数分隔符而不是逗号运算符:
console.log(
function(a, b) {
return function() { return a; };
}
(123, function (arg) { /* ... */ })(this)
); // 123
module.exports
Node
里面的模块系统遵循的是CommonJS
规范。
那问题又来了,什么是CommonJS
规范呢?
由于js
以前比较混乱,各写各的代码,没有一个模块的概念,而这个规范出来其实就是对模块的一个定义。
CommonJS
定义的模块分为: 模块标识(module
)、模块定义(exports
) 、模块引用(require
)
先解释 exports
和 module.exports
在一个node执行一个文件时,会给这个文件内生成一个 exports
和module
对象,
而module
又有一个exports
属性。他们之间的关系如下图,都指向一块{}内存区域。
exports = module.exports = {};
module.exports = {
b:2
}
exports.a = 1
exports.c = 2
////
const d = require('./exports.js')
console.log(d) //{b:2} module.exports优先
// ----------
//module.exports = {
// b:2
//}
exports.a = 1
exports.c = 2
const d = require('./exports.js')
console.log(d) //{a:1,c:2}
//utils.js
let a = 100;
console.log(module.exports); //能打印出结果为:{}
console.log(exports); //能打印出结果为:{}
exports.a = 200; //这里辛苦劳作帮 module.exports 的内容给改成 {a : 200}
exports = '指向其他内存区'; //这里把exports的指向指走
//test.js
var a = require('/utils');
console.log(a) // 打印为 {a : 200}
从上面可以看出,其实
require
导出的内容是module.exports
的指向的内存块内容,并不是exports
的。 简而言之,区分他们之间的区别就是exports
只是module.exports
的引用,辅助后者添加内容用的。
用白话讲就是,exports
只辅助module.exports
操作内存中的数据,辛辛苦苦各种操作数据完,累得要死,结果到最后真正被require
出去的内容还是module.exports
的,真是好苦逼啊。
其实大家用内存块的概念去理解,就会很清楚了。
然后呢,为了避免糊涂,尽量都用 module.exports
导出,然后用require
导入。
export / export default
ES中的模块导出导入
在es中的模块,就非常清晰了。不过也有一些细节的东西需要搞清楚。
比如 export
和 export default
,还有 导入的时候,import a from ..
,import {a} from ..
,总之也有点乱,那么下面我们就开始把它们捋清楚吧。
它们的区别
- export与export default均可用于导出常量、函数、文件、模块等
- 在一个文件或模块中,export、import可以有多个,export default仅有一个
- 通过export方式导出,在导入时要加{ },export default则不需要
- export能直接导出变量表达式,export default不行。
下面咱们看看代码去验证一下
export function a (){
console.log('fun')
}
const b = {a:222}
export default b;
////
import a from './export'
console.log(a) //默认导入export default的
//----
import {a} from './export'
console.log(a) //fun ,导入export的
可选链操作符 ?
a.b.c.d
中间值如果取不到会报错
a?.b?.c?.d
而在当前的 ES2020 中,我们可以使用可选链操作符优雅获取,不会报错
typeScript
学习泛型:Type-challenges
mdn
方法的polyfill 实现、数组扩展方法的重写、函数静态方法
数据类型的判断
typeof 可以正确识别:undefined、boolean、number、string、symbol、function 等类型的数据,但是对于其它的都会认为是 object, 比如 Null、Date 等,所以通过 typeof 来判断数据类型不准确
function typeof(obj){
return Object.prototype.toString.call(obj).slice(8,-1).toLowerCase();
}
typeOf([]) // 'array'
typeOf({}) // 'object'
typeOf(new Date) // 'date'
数组去重
Array.filter(function(currentValue, indedx, arr), thisValue)
ES5:
function unique(arr){
let res = arr.filter(function (item,index,array){
return array.indexOf(item) === index
})
return res
}
ES6:
var unique = arr => [...new Set(arr)]
数组扁平化
数组扁平化就是将 [1, [2, [3]]] 这种多层的数组拍平成一层 [1, 2, 3]。使用 Array.prototype.flat 可以直接将多层数组拍平成一层:
[1, [2, [3]]].flat(2) // [1, 2, 3]
实现 flat 这种效果。
ES5 实现:递归。
function flat2(arr){
let res = []
for(let i = 0,len=arr.length;i<len;i++){
if(Array.isArray(arr[i]){
res = res.concat(flat2(arr[i]))
}else{
res.push(arr[i])
}
}
return res;
}
ES6 实现:
function flat3(arr){
while(arr.some(item=>Array.isArray(item))){
arr = [].concat(...arr)
}
return arr;
}
深浅拷贝
浅拷贝:只考虑对象类型
function shallowCopy(obj) {
if (typeof obj !== 'object') return
let newObj = obj instanceof Array ? [] : {}
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = obj[key]
}
}
return newObj
}
简单版深拷贝:只考虑普通对象属性,不考虑内置对象和函数
function deepClone(obj) {
if (typeof obj !== 'object') return;
var newObj = obj instanceof Array ? [] : {};
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = typeof obj[key] === 'object' ? deepClone(obj[key]) : obj[key];
}
}
return newObj;
}
javascript的执行上下文和执行栈
执行上下文是当前javascript代码被解析和执行所在环境的抽象概念。
执行上下文三种类型
全局执行上下文:默认的,最基础执行上下文
不在函数内部的代码都位于全局执行上下文中
创建一个全局对象,就是window对象
将this指针指向这个全局对象
函数执行上下文:每次函数被调用时,都会创建一个新的执行上下文
- 每个函数都有自己的执行上下文
- 一个程序中可以存在任意数量的函数执行上下文
eval函数执行上下文:运行在
eval
函数中的代码,也能获得自己的执行上下文,很少用不建议用
执行栈
执行栈,也叫调用栈,具有LIFO(后进先出)结构,用于存储在代码执行期间创建的所有执行上下文。
执行上下文的声明周期
- 创建阶段
执行阶段
回收阶段
Object.defineProperty
object.defineproperty ()
方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
备注:应当直接在obiect构造器对象上调用此方法,而不是在任意一个object类型的实 例上调用
let obj = {};
let newObj = Object.defineProperty(obj,'a',{
value:1
});
console.log(newObj === obj); // true
Object.defineProterty(obj,prop,descriptor);
obj
要定义属性的对象
prop
要定义或修改的属性的名称
descriptor
要定义或修改的属性描述符
描述
该方法允许精确地添加或修改对象的属性。通过赋值操作添加的普通属性是可枚举的,在枚举对象属性时会被枚举到(for...in
或 Object.keys
方法),可以改变这些属性的值,也可以删除
这些属性。这个方法允许修改默认的额外选项(或配置)。默认情况下,使用 Object.defineProperty()
添加的属性值是不可修改(immutable)的。
对象里目前存在的属性描述符有两种主要形式:数据描述符和存取描述符。数据描述符是一个具有值的属性,该值可以是可写的,也可以是不可写的。存取描述符是由 getter 函数和 setter 函数所描述的属性。一个描述符只能是这两者其中之一;不能同时是两者。
这两种描述符都是对象。它们共享以下可选键值(默认值是指在使用 Object.defineProperty()
定义属性时的默认值):
configurable
当且仅当该属性的
configurable
键值为true
时,该属性的描述符才能够被改变,同时该属性也能从对应的对象上被删除。 默认为false
。enumerable
当且仅当该属性的
enumerable
键值为true
时,该属性才会出现在对象的枚举属性中。 默认为false
。
数据描述符还具有以下可选键值:
value
该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。 默认为
undefined
。writable
当且仅当该属性的
writable
键值为true
时,属性的值,也就是上面的value
,才能被赋值运算符
(en-US)改变。 默认为false
。
存取描述符还具有以下可选键值:
get
属性的 getter 函数,如果没有 getter,则为
undefined
。当访问该属性时,会调用此函数。执行时不传入任何参数,但是会传入this
对象(由于继承关系,这里的this
并不一定是定义该属性的对象)。该函数的返回值会被用作属性的值。 默认为undefined
。set
属性的 setter 函数,如果没有 setter,则为
undefined
。当属性值被修改时,会调用此函数。该方法接受一个参数(也就是被赋予的新值),会传入赋值时的this
对象。 默认为undefined
。