认识JavaScript

浏览器发展史、5大主流浏览器和内核、v8引擎的前后今生

5大浏览器内核
不同浏览器不一样
IE: trident
Chrome:webkit blink
Safari:webkit
Firefox:gecko
opera:presto

国产浏览器:trident+webkit(trident相对安全,所以很多项目只支持IE)

浏览器的历史和js的诞生
1990
有个人 用超文本来分享资讯
world wide web = > 移植到c
允许别人浏览他人编写的网站
1993
美国伊利诺大学组织的核心成员 马克安德森
MOSIAC浏览器 显示图片
图形化浏览器

1994
马克安德成立公司 马赛克MOSIAC
->名字侵权->改名Netscape
网景公司->流行10多年 2003

1996
微软收购spy glass
-> IE 基于MOSIAC 的内核
-> IE 自己的内核 trident

IE3 Jscript

网景公司核心成员在Netscape基础上开发出 livescript

JAVA 火起来,livescript不火->推广改名JavaScript

2001
IE6 XP诞生
js引擎诞生

2003
Mozilla公司Firefox->Netscape

2008
google基于webkit blink gears ->chrome->V8引擎(js引擎)
直接翻译机器码
独立于浏览器允许

nodejs 基于 chrome V8

progressive web app -> 渐进式webapp-> vue

2009
甲骨文oracle收购了SUN公司
JS的所有权给甲骨文

ECMA 欧洲计算机制造联合会
很多组织都在瑞士日内瓦

评估开发认可电信计算机标准
ECMA=262 脚本语言规范 ECMAScript
ES5 ES6
规范化脚本语言

编程语言
高级语言

内核组成模块
(主线程)
js引擎模块:负责js程序的编译yu运行
html,css文档解析模块:负责页面文本的解析
DOM/css模块:负责dom/css在内存中的相关处理
布局和渲染模块
……
(分线程)
定时器模块
DOM事件响应模块
网络请求模块

ECMA欧洲计算机制造商协会

组织在1961年的日内瓦建立为了标准化欧洲的计算机系统。
日内瓦公约
作为世界著名的国际化都市,日内瓦在两次世界大战之间,国际联盟的总部就设立在此。日内瓦在国际上享有的高知名度主要得益于这些国际组织或办事处,包括红十字会总部、世界卫生组织、联合国日内瓦办事处等。在2016年的世界最佳居住城市评选中日内瓦高居全球第二位。

ECMA-262 脚本语言的规范和标准 ECMAScript ES5 ES6 -> 规范化脚本语言

编程语言

  • 机器语言(0/1)
  • 高级语言
    • 编译形(需要移植,快)
      源码->编译器->机器语言->可执行文件
    • 解释形(不需要移植,可以跨平台,慢)
      源码->编译器->解释一行执行一行
  • 脚本语言(脚本引擎->解释器解释和立即执行,前端后端都有,js,php)

JavaScript初识

  1. ECMAScript(语法、变量、关键字……)
  2. DOM 文档对象模型、W3C规范
  3. BOM 浏览器对象模型、没有规范

单线程模拟多线程->轮转时间片

  • js的引入(可以不写type=“text/javascript”)
  1. 内部引入
  2. 外部引入
  3. 混合引入(只有外部生效,不建议这样写)

js的引入可以不写type=“text/javascript”
开发中也会故意写错type=“text/tb”,然后给script一个id
同过dom操作拿到这个id里面的html模板,通过正则重新渲染页面

  • ts的引入

基础语法

var a = 1,
    b = 2,
    c = 3;
var d = (a + b) * c;
// 1. 声明变量c
// 2. 变量a的值和变量c的值相乘得到结果
// 3. 将该结果赋值给c

// 括号运算符>普通运算>赋值
// 运算符两边要有空格

// 任何数据类型的值+字符串=字符串

var a = 5,
    b = 2,
    c;
c = a / b; //2.5

0 / 0 // NaN -> not a number,NaN是数字类型,非数
"a" / "b" //NaN
1 / NaN //NaN , NaN和任何数据运算都是NaN, NaN!=NaN

1 / 0  //Infinity 数字类型 正无穷
-1 / 0 //-Infinity 数字类型 负无穷

变量交换值的问题

// 交换值的问题

var a = 1,
    b = 2;
// 1. 使用中间变量
var c = a;
a = b;
b = c;

// 2. 先求和,然后互相减去对方
a = a + b;
b = a - b;
a = a - b;

var a = 5,
    b;
b = --a + a--
console.log(b , a) //8 3

//比较操作符两边数据类型的隐式转换

switch-case

// switch case
const expr = 'Mangoes';
switch (expr) {
  case 'Oranges':
    console.log('Oranges are $0.59 a pound.');
    break;
  case 'Mangoes':
  case 'Papayas':
    console.log('Mangoes and papayas are $2.79 a pound.');
    // expected output: "Mangoes and papayas are $2.79 a pound."
    break;
  default:
    console.log(`Sorry, we are out of ${expr}.`);
}

// 一个 switch 语句首先会计算其 expression 。然后,它将从第一个 case 子句开始直到寻找到一个其表达式值与所输入的 expression 的值所相等的子句(使用 严格运算符 (en-US),===)并将控制权转给该子句,执行相关语句。(如果多个 case 与提供的值匹配,则选择匹配的第一个 case,即使这些 case 彼此间并不相等。)

// 如果没有 case 子句相匹配,程序则会寻找那个可选的 default 子句,如果找到了,将控制权交给它,执行相关语句。若没有 default 子句,程序将继续执行直到 switch 结束。按照惯例,default 子句是最后一个子句,不过也不需要这样做。

// 可选的 break 语句确保程序立即从相关的 case 子句中跳出 switch 并接着执行 switch 之后的语句。若 break 被省略,程序会继续执行 switch 语句中的下一条语句。

var foo = 5;
switch (foo) {
  case 2:
    console.log(2);
    break; // 遇到 break,所以不会继续进入 'default:'
  default:
    console.log('default')
    // 掉到下面
  case 1:
    console.log('1');
}
// 多 case - 单一操作
// 这种方法利用这样一个事实:如果 case 语句之下没有 break ,它将继续执行下一个 case 语句,而不管 case 是否符合条件。
// 这是一个单操作顺序的 switch 语句,其中四个不同值的执行结果完全一样。
var Animal = 'Giraffe';
switch (Animal) {
  case 'Cow':
  case 'Giraffe':
  case 'Dog':
  case 'Pig':
    console.log('This animal will go on Noah\'s Ark.');
    break;
  case 'Dinosaur':
  default:
    console.log('This animal will not.');
}
// VM141:7 This animal will go on Noah's Ark.

// switch 语句内的块级作用域
// 随着绝大多数现代浏览器已支持 ECMAScript 2015 (ES6),在某些场景下您可能需要使用 let 和 const 语句,以在块级作用域内声明变量。

// 以这段代码为例:

const action = 'say_hello';
switch (action) {
  case 'say_hello':
    let message = 'hello';
           console.log('0 ~5');
           break;
  case 'say_hi':
    let message = 'hi';
    case 6: console.log('6');
    break;
  default:
    console.log('Empty action received.');
    break;
}
// 这个示例会导致意想不到的错误 Uncaught SyntaxError: Identifier 'message' has already been declared.

// 这是因为第一个 let message = 'hello'; 与第二个 let message = 'hi'; 语句产生了冲突,虽然他们处于各自分隔的 case 语句中,即 case 'say_hello': 和 case 'say_hi':。导致这一问题的根本原因在于两个 let 语句处于同一个块级作用域,所以它们被认为是同一个变量名的重复声明。

// 通过把 case 语句包装到括号里面,我们就可以轻松解决这个问题。

const action = 'say_hello';
switch (action) {
  case 'say_hello': { // added brackets
    let message = 'hello';
    console.log(message);
    break;
  } // added brackets
  case 'say_hi': { // added brackets
    let message = 'hi';
    console.log(message);
    break;
  } // added brackets
  default: { // added brackets
    console.log('Empty action received.');
    break;
  } // added brackets
}
// 此时,这段代码就会在控制台输出 hello,不会再有任何报错。


// case指定多个条件时的写法
// 第一种
switch(a){
    case 1:
    case 0:
         console.log("aaa");
         break;
    default:
    console.log("else");
}
// 第二种
let score = 81;
switch(score){
    case score >= 90 && score <= 100:
        console.log('你的成绩等级为A');
        break;
    case score >= 80 && score < 90:
        console.log('你的成绩等级为B');
        break;
    default:
        console.log('你的成绩不合格');    
}
// 你的成绩不合格
// case 会将后面的语句看成一个表达式,将swith中的值和case中的值比较后返回
// case后使用了逻辑运算返回的是布尔类型,所以它只会使用于布尔类型

// 正确写法
let score = 81;
switch(true){
    case score >= 90 && score <= 100:
        console.log('你的成绩等级为A');
        break;
    case score >= 80 && score < 90:
        console.log('你的成绩等级为B');
        break;
    default:
        console.log('你的成绩不合格');    
}

逻辑运算符

// 不要将原始布尔值的true和false与Boolean对象的真或假混淆。
// 任何一个值,只要它不是 undefined、null、 0、NaN或空字符串(""),
// 那么无论是任何对象,即使是值为假的Boolean对象,在条件语句中都为真


// && 遇到假或走到最后返回
// || 遇到真或走到最后返回
var a = 1 && 2 && undefined && 10;
var b = 0 || null || 1 || 0;
console.log(a,b)//undefined 1

// 使用场景
var name = 'Jack';
console.log(name || '未找到数据');

document.getElementById('id').onclick = function(e){
    var event = e || window.event;
    // ie 中的event 保存在 window.event
}

三目运算符

var a = 5;
var str;
a > 0 ? str = '大于0'
    : '小于等于0';
console.log(str)
// 三目运算符自带return
str = a > 0 ? '大于0' // return '大于0'
    : '小于等于0'; // return '小于等于0'
console.log(str)

// 三目运算符的嵌套:给嵌套的三目运算加上小括号
// 尽量不要嵌套,不好看
  • 面试
var str = 89 > 9 ? (
    '89' > '9' ? '通过了' : "内层未通过"
) : '外层未通过'
// '内层未通过'

‘89’ > ‘9’ 不涉及隐式类型转换,从第一个字符串开始比较,‘8’<‘9’

浅拷贝和深拷贝

Object.prototype.num = 1;
var person1 = {
    name: '张三',
    age: '26',
    son: {
        first: 'Jone',
        second: 'Luck'
    },
    arr: []
}

// var person2 = {};
// 浅拷贝
// clone1(person1,person2);
// var person2 = clone2(person1)
var person2 = deepClone(person1)

person2.name = '李四';
console.log(person2);
// {
//     "name": "李四",
//     "age": "26",
//     "son": {
//         "first": "Jone",
//         "second": "Luck" //对象只是拷贝了地址,为浅拷贝
//     },
//     "arr": [],
//     "num": 1   //这个上面语句Object.prototype.num = 1;所造成,不应该被拷贝
// }

// 浅拷贝1
function clone1(origin, target) {
    for (var key in origin) {
        target[key] = origin[key]
    }
}
// 浅拷贝2 
function clone2(origin, target) {
    var tar = target || {}
    for (var key in origin) {
        if (origin.hasOwnProperty(key)) {
            // 不遍历原型链上自定义属性
            // Object.prototype.num = 1 排除 num
            tar[key] = origin[key]
        }
    }
    return tar;
}

// 深拷贝
function deepClone(origin, target) {
    var target = target || {};
    var toStr = Object.prototype.toString;
    var arrType = '[Object Array]';
    for (var key in origin) {
        if (origin.hasOwnProperty(key)) {
            // 判断是当前key是否对象(null也是对象),如果是对象,key当做参数继续递归调用
            if (typeof (origin[key]) === 'object' && origin[key] !== null) {
                // 如果是数组,给目标对象赋值空数组
                // 如果是对象,给目标对象赋值空对象
                if (toStr.call(origin[key]) === arrType) {
                    target[key] = [];
                } else {
                    target[key] = {};
                }
                // 对当前key递归调用
                deepClone(origin[key], target[key])
            } else {
                target[key] = origin[key]
            }
        }
    }
    return target;
}

// json进行深拷贝
var person1str = JSON.stringify(person1)
var person2 = JSON.parse(person1str)
console.log(person2)
// ? 上述只是对 对象属性的克隆,而没有对 对象方法的克隆

JSON深拷贝的问题

JOSN.stringify() 深拷贝有什么问题?

最简单的深拷贝呗方式就是使用JSON.stringify();

var obj={
   name:'大雄',
   age:21
};
var obj1=JSON.parse(JSON.stringify(obj));

这样深拷贝出来是完全没有问题的,假如我们有如下的对象,进行深拷贝呢

var obj1={
   name:'大雄',
    say:function(){
	console.log('我会说话哦!');
    },
    birthday:new Date('1990/01/01')
};
var obj2=JSON.parse(JSON.stringify(obj));
console.log(obj2);
// {name: "大雄", birthday: "1989-12-31T16:00:00.000Z"}

我们看到了,当我们的对象中有函数和日期类型是,日期类型被转化成了字符串
函数属性直接没有了!是不是问题很大!

经过我们测试我们发现:
:::tip

  1. undefined、任意的函数以及 symbol 值,在序列化过程中会被忽略
  2. Date 日期调用了 toJSON() 将其转换为了 string 字符串(Date.toISOString()),因此会被当做字符串处理。
  3. NaN 和 Infinity 格式的数值及 null 都会被当做 null。
  4. 其他类型的对象,包括 Map/Set/WeakMap/WeakSet,仅会序列化可枚举的属性。
  5. 对包含循环引用的对象(对象之间相互引用,形成无限循环)执行此方法,会抛出错误。
    :::
// 下面就是循环引用;
var obj1 = {
  x: 1, 
  y: 2
};
obj1.z = obj1;
var obj2 = JSON.parse(JSON.stringify(obj1)); // 栈溢出,抛出错误;

所以当我们要克隆的对象里面还有引用类型时,我们只能采用递归的方法进行遍历了,这里就不展开了。

  • JOSN.stringify() 有几个参数?

JSON.stringify(value[, replacer [, space]])

我们主要看一下 第二个参数和第三个参数时干啥用的

  • replacer 可选
    1. 如果该参数是一个函数,则在序列化过程中,被序列化的值的每个属性都会经过该函数的转换和处理;
    2. 如果该参数是一个数组,则只有包含在这个数组中的属性名才会被序列化到最终的 JSON 字符串中;
    3. 如果该参数为 null 或者未提供,则对象所有的属性都会被序列化;
    var obj1={
        x:1,
        y:2
    };
    var obj2=JOSN.stringify(obj1,function(key,value){
        if(typeof value == 'number'){
            return value*2
        }
        return value;
    });
    // "{x:2,y:4}"
    
  • space 可选
    1. 指定缩进用的空白字符串,用于美化输出(pretty-print)
    2. 如果参数是个数字,它代表有多少的空格;
    var obj1={x:1,y:2};
    var jsonStr = JSON.stringify(obj1,null,'\t');
    console.log(jsonStr)
    // "{
    // 	"x": 1,
    // 	"y": 2
    // }"
    // 我我们用制表符来进行缩进
    

ES6中深拷贝weakMap

:::tip weakMap 是ES6定义的
WeakMap对象也是键值对的集合。它的键必须是对象类型,值可以是任意类型。它的键被弱保持,也就是说,当其键所指对象没有其他地方引用的时候,它会被GC回收掉。WeakMap提供的接口与Map相同。

与Map对象不同的是,WeakMap的键是不可枚举的。不提供列出其键的方法。列表是否存在取决于垃圾回收器的状态,是不可预知的。
:::

    const oBtn = document.querySelector('btn')
    oBtn.addEventListener('click',handleClick,false)
    function handleClick(){}
    // oBtn.removeEventListener
    oBtn.remove();
    // 只移除了dom但handleClick时间还被引用

    // 使用WeakMap、弱引用,如果里面的key没有被引用了就会自动删除
    const wMap = new weakMap();
    wMap.set(oBtn,handleClick)
    oBtn.addEventListener('click',wMap.get(oBtn))

  • 判断有效对象
  if(origin==undefined || typeof origin !=='object'){
        // 不是有效对象  (undefined==null)
        return
    }
  • new 实例对象中的构造器来克隆此对象
    const obj = {}
    const newobj = new obj.constructor()
    console.log(obj,newobj)
  • 深拷贝递归实现
function deepClone(origin) {
    if(origin==undefined || typeof origin !=='object'){
        // 不是对象就直接返回
        return origin
    }
    if(origin instanceof Date){
        return new Date(origin)
    }
    if(origin instanceof RegExp){
        return new RegExp(origin)
    }
    const target = new origin.constructor;
    for (let key in origin) {
        if (origin.hasOwnProperty(key)) {
            target[key] = deepClone(origin[key]);
        }
    }
    return target;
}

var obj = {age:new Number(16)}
var obj2 = deepClone(obj)
obj2.age = 27
console.log(obj,obj2)   
  • 解决拷贝中的循环引用的问题
let test1 = {};
let test2 = {};
test1.test2 = test2;
test2.test1 = test1;
console.log(deepClone(test2))

用weakMap记录已经拷贝过的key完美

function deepClone(origin, hashMap = new WeakMap()) {
    if (origin == undefined || typeof origin !== 'object') {
        // 不是对象就直接返回
        return origin
    }
    if (origin instanceof Date) {
        return new Date(origin)
    }
    if (origin instanceof RegExp) {
        return new RegExp(origin)
    }
    // 如果orgin对应的target存在说明已经拷贝过了,直接返回这个target
    const hashKey = hashMap.get(origin);
    if(hashKey){
        return hashKey;
        // weakMap中没走到这一步的键值对都会被GC
    }
    const target = new origin.constructor;
    hashMap.set(origin,target) 
    // 一个orgin对应一个target。

    for (let key in origin) {
        if (origin.hasOwnProperty(key)) {
            target[key] = deepClone(origin[key],hashMap);
        }
    }
    return target;
}

let test1 = { age: new Number(16) };
let test2 = {};
test1.test2 = test2;
test2.test1 = test1;
console.log(deepClone(test2))

循环

  • 模仿块级作用域和for循环
    :::tip
    ES 中没有块级作用域的概念
    这意味着在块语句中定义的变量,实际上是在包含函数中而非语句中创建的
    :::
function outputNumbers(count){
    for(var i=0;i<count;i++){
        console.log(i)
    }
    alert(i)//计数
}
outputNumbers(10)//弹出9

使用es6中的let
:::tip
在for循环中使用let声明的变量只能在for当前的块级作用域使用
:::

function outputNumbers(count){
    for(let i=0;i<count;i++){
        console.log(i)
    }
    alert(i)//计数
}
outputNumbers(10)// i is not defined
  • for、while循环解析
//1
for(var i=0;i<10;i++){
    console.log(i)
    i++
}
//2
var i =0
for(;i<10;){
    console.log(i)
    i++
}
//1 和 2 是相等的

//3 i=0 的时候为假 结束循环
var i = 1
for(;i;){
    console.log(i)
    i++
    if(i>=10){
        i=0
    }
}
//4 break 跳出循环
var i = 1
for(;i;){
    console.log(i)
    i++
    if(i==11>){
        break//i=0
    }
}

// 以上两个1 2 for循环也可以转换为while循环
//while
while(i<10){
    console.log(i)
    i++
}

// 死循环
while(1){
    console.log(i)
    i++
}

//从0开始做加法,加到100
var sum = 0
for(var i=0;i<100;i++){
    sum+=i
    if(sum>=100){
        break
    }
    console.log(i,sum)//break跳出循环这里不执行
}
//100以内跳过可以被7整除或个位数是7的数
for(var i=0;i<=100;i++){
    if(i%7==0 || i%10==7){
        continue
    }
    console.log(i)
}

//打印0-100的数,只能有1句 不能出现+号和判断语句
var i=100
for(;i--;){
    console.log(i)
}

隐式转换

undefined 、null和0没关系
undefined==null但不是全等于

isNaN



斐波那契数列
黄金分割数列
兔子数列

typeof

typeof重写

function myTypeof(val) {
    var type = typeof (val);
    var toStr = Object.prototype.toString;
    var res = {
        '[object Array]': 'array',
        '[object Object]': 'object',
        '[object Number]': 'object number',
        '[object String]': 'object string',
        '[object Boolean]': 'object boolean',
    }
    if (val === null) {
        return 'null'
    } else if (type === 'object') {
        var ret = toStr.call(val);
        return res[ret];
    } else {
        return type;
    }
}

console.log(myTypeof(1))
console.log(myTypeof('1'))
console.log(myTypeof({}))
console.log(myTypeof([]))
console.log(myTypeof(new Number()))

错误

  1. SyntaxError 语法错误
    • 变量名不规范
      var 1 = 1
    • 关键字赋值
      new = 5
    • 基本语法错误
      var a = 1:
  2. ReferenceError 引用错误
    • 变量或函数未被声明
      console.log(a)
    • 给无法赋值的对象赋值
      var a = 1 = 2
  3. RangeError 范围错误
    • 数组长度赋值为负数
      var arr = [0];arr.length = -1
    • 对象方法参数超出可行范围
      var num = 1;num.toFixed(-1)
  4. TypeError 类型错误
    • 调用不存在的方法
    • 实例化原始值
      var a = new 'string'
    • URIError URI错误
    var url = 'https://www.baidu.com/?wd=小白'
    var newUrl = encodeURI(url)
    //https://www.baidu.com/?wd=%E5%B0%8F%E7%99%BD
    console.log(newUrl);
    console.log(decodeURI(newUrl))
    // 不正确的编码会报错
    var str = decodeURI('%fdsdf')
    
  5. EvalError evel 函数执行错误
    • 最大作用就是json字符串的解析,但我们用JSON.parse-
    • 但不建议使用
    var jsonStr = '[{"name":"luck"}]'
    var obj = eval(jsonStr)
    console.log(obj)//[ { name: 'luck' } ]
    
  6. 自定义错误
    var err = new Error('代码错误')
    console.log(err)
    
  7. 错误的一般处理
        try {
        console.log('正常执行1')
        // console.log(a)
        throw '变量未定义'
        console.log('正常执行2') //不执行
    } catch (e) {
        console.log(e,e.name,e.message)
        //ReferenceError a is not defined
        
        // 换种error方案
        var errTip = {
            name:'数据传输失败',
            code:'10010'
        }
    } finally{
        console.log('正常执行3')
    }
    

ES5严格模式

IE9及以下浏览器不支持严格模式
‘use strict’

// 不允许全局写 'use strict' ,应该写在函数内部
function test(){
    'use strict'
}
  • with会改变作用域,严格模式下不建议使用
var a = 1;
var obj = {
    a:2
}
function test(){
    'use strict'
    var a= 3;
    // with会改变作用域,消耗空间,严格模式下不建议使用
    // Uncaught SyntaxError: Strict mode code may not include a with statement
    with(obj){
        console.log(a)
    }
}
test()

  • 将过失错误转成异常
  1. 严格模式下无法再意外创建全局变量
"use strict";
// 假如有一个全局变量叫做mistypedVariable
mistypedVaraible = 17; // 因为变量名拼写错误
// 这一行代码就会抛出 ReferenceError
  1. 严格模式会使引起静默失败(注:不报错也没有任何效果)的赋值操作抛出异常.
    例如, NaN 是一个不可写的全局变量. 在正常模式下, 给 NaN 赋值不会产生任何作用; 开发者也不会受到任何错误反馈.
    但在严格模式下, 给 NaN 赋值会抛出一个异常
"use strict";

// 给不可写属性赋值
var obj1 = {};
Object.defineProperty(obj1, "x", { value: 42, writable: false });
obj1.x = 9; // 抛出TypeError错误

// 给只读属性赋值
var obj2 = { get x() { return 17; } };
obj2.x = 5; // 抛出TypeError错误

// 给不可扩展对象的新属性赋值
var fixed = {};
Object.preventExtensions(fixed);
fixed.newProp = "ohai"; // 抛出TypeError错误
  1. 在严格模式下, 试图删除不可删除的属性时会抛出异常(之前这种操作不会产生任何效果):
"use strict";
delete Object.prototype; // 抛出TypeError错误
  1. 严格模式要求一个对象内的所有属性名在对象内必须唯一。
    正常模式下重名属性是允许的,最后一个重名的属性决定其属性值。
    在严格模式下,重名属性被认为是语法错误:
"use strict";
var o = { p: 1, p: 2 }; // !!! 语法错误
  1. 严格模式要求函数的参数名唯一.
    在正常模式下, 最后一个重名参数名会掩盖之前的重名参数. 之前的参数仍然可以通过 arguments[i] 来访问, 还不是完全无法访问.
    然而, 这种隐藏毫无意义而且可能是意料之外的 (比如它可能本来是打错了), 所以在严格模式下重名参数被认为是语法错误:
function sum(a, a, c) { // !!! 语法错误
  "use strict";
  return a + a + c; // 代码运行到这里会出错
}
  1. 严格模式禁止八进制数字语法.
    ECMAScript并不包含八进制语法,
    但所有的浏览器都支持这种以零(0)开头的八进制语法: 0644 === 420 还有 “\045” === “%”.
    在ECMAScript 6中支持为一个数字加"0o"的前缀来表示八进制数.
var a = 0o10; // ES6: 八进制
  1. 第七,ECMAScript 6中的严格模式禁止设置原始值的属性.
    不采用严格模式,设置属性将会简单忽略(no-op),
    采用严格模式,将抛出TypeError错误
(function() {
  "use strict";

  false.true = "";              //TypeError
  (14).sailing = "home";        //TypeError
  "with".you = "far away";      //TypeError
})();
  • 简化变量的使用
  1. 严格模式禁用 with.
"use strict";
var x = 17;
with (obj) { // !!! 语法错误
  // 如果没有开启严格模式,with中的这个x会指向with上面的那个x,还是obj.x?
  // 如果不运行代码,我们无法知道,因此,这种代码让引擎无法进行优化,速度也就会变慢。
  x;
}

一种取代 with的简单方法是,将目标对象赋给一个短命名变量,然后访问这个变量上的相应属性.
2. 严格模式下 eval 仅仅为被运行的代码创建变量
所以 eval 不会使得名称映射到外部变量或者其他局部变量:

var x = 17;
var evalX = eval("'use strict'; var x = 42; x");
console.assert(x === 17); //true
console.assert(evalX === 42);//true
  1. 严格模式禁止删除声明变量。delete name 在严格模式下会引起语法错误:
"use strict";
var x;
delete x; // !!! 语法错误
eval("var y; delete y;"); // !!! 语法错误
  • arguments变的简单
  1. 严格模式下,参数的值不会随 arguments 对象的值的改变而变化
    严格模式下,函数的 arguments 对象会保存函数被调用时的原始参数。arguments[i] 的值不会随与之相应的参数的值的改变而变化,同名参数的值也不会随与之相应的 arguments[i] 的值的改变而变化。
function f(a) {
  "use strict";
  a = 42;
  return [a, arguments[0]];
}
var pair = f(17);
console.assert(pair[0] === 42);
console.assert(pair[1] === 17);
  1. 不再支持 arguments.callee、caller

命名空间

  • 很早之前使用的是对象的方式
var namespace = {
    header:{
        Jenny:{
            a:1
        }
    },
    sideBar:{
        Ben:{
            a:2
        }
    }
}
console.log(namespace.header.Jenny.a)
// 这种名字太长,常用with改变命名空间
with(namespace.header.Jenny){
    console.log(a)
}
  • ES5的写法-使用立即执行函数模块化开发
window.onload = function(){
    init()
}

function init(){
    initHeader;
    initSider;
}

var initHeader = (function(){
    var a = 1;
    console.log(a);
})()

var initSider = (function(){
    var a = 2;
    console.log(a);
})()

垃圾回收

  • js引擎自动回收
  1. 找出不再使用的变量
  2. 释放其占有内存
  3. 固定的时间间隔运行

parseInt(string, radix)

解析一个字符串并返回指定基数的十进制整数, radix 是2-36之间的整数,表示被解析字符串的基数。
MDN

  • string
    要被解析的值。如果参数不是一个字符串,则将其转换为字符串(使用 ToString 抽象操作)。
    字符串开头的空白符将会被忽略。

  • radix 可选
    从 2 到 36,表示字符串的基数。
    例如指定 16 表示被解析值是十六进制数。请注意,10不是默认值!

    parseInt('123', 5) 
    // 将'123'看作5进制数,返回十进制数38 => 1*5^2 + 2*5^1 + 3*5^0 = 38
    
    console.log(parseInt('0x629eb',16))
    // 403947
    6 * Math.pow(16,4) +
    2 * Math.pow(16,3) +
    9 * Math.pow(16,2) +
    14 * Math.pow(16,1) +
    11 * Math.pow(16,0) 
    // 403947
    
  • 返回值
    从给定的字符串中解析出的一个整数。
    或者 NaN,当radix 小于 2 或大于 36 ,或第一个非空格字符不能转换为数字。

    //以下例子均返回 NaN:
    parseInt("Hello", 8); // 根本就不是数值
    parseInt("546", 2);   // 除了“0、1”外,其它数字都不是有效二进制数字
    
  • 如果 parseInt 遇到的字符不是指定 radix 参数中的数字,它将忽略该字符以及所有后续字符,并返回到该点为止已解析的整数值。
    parseInt 将数字截断为整数值。 允许前导和尾随空格。

    parseInt("146", 2);   //1. 除了“0、1”外,其它数字都不是有效二进制数字
    //等于parseInt("1", 2);
    parseInt(" 106  ", 2);//2
    parseInt(" 101", 2);//5
    
  • parseInt 可以理解两个符号。+ 表示正数,- 表示负数(从ECMAScript 1开始)
    它是在去掉空格后作为解析的初始步骤进行的。
    如果没有找到符号,算法将进入下一步;否则,它将删除符号,并对字符串的其余部分进行数字解析

    parseInt(" +106", 2);//2
    parseInt(" -101", 2);//-5
    
  • 如果 radix 是 undefined、0或未指定的,JavaScript会假定以下情况:

    1. 如果输入的 string以 "0x"或 “0x”(一个0,后面是小写或大写的X)开头,那么radix被假定为16,字符串的其余部分被当做十六进制数去解析。
    2. 如果输入的 string以 “0”(0)开头, radix被假定为8(八进制)或10(十进制)。具体选择哪一个radix取决于实现。
      ECMAScript 5 澄清了应该使用 10 (十进制),但不是所有的浏览器都支持。
      因此,在使用 parseInt 时,一定要指定一个 radix。
    3. 如果输入的 string 以任何其他值开头, radix 是 10 (十进制)。
    4. 如果第一个字符不能转换为数字,parseInt会返回 NaN。
  • 为了算术的目的,NaN 值不能作为任何 radix 的数字。
    你可以调用isNaN函数来确定parseInt的结果是否为 NaN。
    如果将NaN传递给算术运算,则运算结果也将是 NaN。

  • 要将一个数字转换为特定的 radix 中的字符串字段,请使用 thatNumber.toString(radix)函数。

    var num = 10;
    num.toString(16) //'a'
    
  • 一个更严格的解析函数
    有时采用一个更严格的方法来解析整型值很有用。此时可以使用正则表达式:

    filterInt = function (value) {
    if(/^(\-|\+)?([0-9]+|Infinity)$/.test(value))
        return Number(value);
    return NaN;
    }
    console.log(filterInt('421'));               // 421
    console.log(filterInt('-421'));              // -421
    console.log(filterInt('+421'));              // 421
    console.log(filterInt('Infinity'));          // Infinity
    console.log(filterInt('421e+0'));            // NaN
    console.log(filterInt('421hop'));            // NaN
    console.log(filterInt('hop1.61803398875'));  // NaN
    console.log(filterInt('1.61803398875'));     // NaN
    

副作用、纯函数

副作用的函数不仅仅是返回了一个值,还做了其他事情,
比如

  • 操作系统文件
  • 操作数据库
  • 发送http请求
  • console.log
  • 修改全局变量

在js中我们不是要完全消除副作用而是避免不应该出现的副作用

如果没有副作用我们的函数只能进行计算

function test(a,b){
    return a+b
}
const result = test(1,2)
  • 纯函数

输入输出数据都是显示的
函数与外界交换数据的唯一渠道—参数和返回值
函数从外部接收到的所有数据都是由参数传入到函数内部的
函数从内部输出到外部的数据都是通过返回值传递到函数外部的

  • 非纯函数
    函数除了参数和返回值的方法和外界进行数据交换-比如函数内部修改了全局变量

map就是纯函数,没有副作用,调用map返回新的数组,不修改原数组
pop就是非函数式,有副作用,调用pop会修改原数组

const myPop = (arr) =>{
    const [...cloneArr] = arr;
    return cloneArr.pop()
}
const arr1 = [1,2,3];
const item = myPop(arr1);
console.log(arr1,item)
  • 引用透明性
    对一个纯函数而言,给定相同的参数,返回值都相同。
    所以我们可以用计算值替代纯函数的调用。

可以用计算的结果代替表达式的能力,我们称之为引用透明性

function addOne(a){
    return a+1;
}
addOne(1)+addOne(1);
// 2+2 以上可以直接用结果表示
  • 局部副作用的函数
function test(round){
    let result = 0;
    for(let i =0;i<round;i++){
        result +=i;
    }
    return result;
    // result不断变化 ,他是有局部副作用的函数
}

console.log(test(3)) //3
console.log(test(3)) //3

面试题

  1. 访问的时候改变属性
// ===============
var a = {
    _a:0,
    toString(){
        return ++ this._a
    }
};
//toString 双等号会调用toString
if(a==1 && a==2 && a==3){
    console.log('You win!!')
}
// ====================
// get set
var _a =0
Object.defineProperty(window,'a',{
    get(){
        return ++_a
    }
})
if(a===1 && a===2 && a===3){
    console.log('You win!!!!')
}
// ===================
var obj = {
    _a:0,
    get a(){ //Object.defineProperty语法糖
        return ++ this._a
    }
}
if(obj.a===1 && obj.a===2 && obj.a===3){
    console.log('You win!!!!')
}

定时器引发的思考

  1. 定时器真的是定时执行的吗?
    定时器并不能保证真正地定时执行
  2. 定时器回调函数是在分线程执行的吗
    在主线程执行的,js是单线程
  3. 定时器是如何实现的
    事件循环模型
var start = Date.now()
console.log('启动定时器前··')
setTimeout(function(){
    console.log('定时器执行了',Date.now()-start)
},200)
console.log('定时器启动后')

JS精度

在浏览器中0.1+0.2 = 0.30000000000000004

IEEE754规范
js中64位双精度浮点数存储数字

10转换为二进制,除2取余(除2反序取余)
10/2   0
5/2    1
2/2    0
1/2    1
商为0结束
10的二进制1010

对于十进制1200->1.2 x 10^3
对于二进制1010->1.01 x 2^3

计算机64位存储
最高一位符号位:0正1负
往后11位是指数位:2^3->指数位存3(如果是小数指数位应该是负数,为了确保正数统一加上2^11=1023,所以指数为都加1023)(1026)
最后一位是有效位

0.1转换为二进制,取小数位乘2取整(乘2正序取整)
0.1 * 2 = 0.2
0.2 * 2 = 0.4
0.4 * 2 = 0.8
0.8 * 2 = 1.6
0.6 * 2 = 1.2
0.2 * 2 = 0.4
0.4 * 2 = 0.8
0.8 * 2 = 1.6
0.6 * 2 = 1.2
……
0.00011 00011

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Lmp8x1pV-1652268412470)(https://gitee.com/jingyu7/pic/raw/master/202201281506433.png)]

  • 如何解决0.1+0.2>0.3
// 19.99+20.00 = 39.989999999999995
// 1. toFixed
parseFloat((19.99+20.00).toFixed(2))
// 2. 整数没有精度问题, 可以借用整数
(19.99*100 + 20.00*100)/100 = 19.99
// 3. 第三方包

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值