文章目录
浏览器发展史、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初识
- ECMAScript(语法、变量、关键字……)
- DOM 文档对象模型、W3C规范
- BOM 浏览器对象模型、没有规范
单线程模拟多线程->轮转时间片
- js的引入(可以不写type=“text/javascript”)
- 内部引入
- 外部引入
- 混合引入(只有外部生效,不建议这样写)
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
- undefined、任意的函数以及 symbol 值,在序列化过程中会被忽略
- Date 日期调用了 toJSON() 将其转换为了 string 字符串(Date.toISOString()),因此会被当做字符串处理。
- NaN 和 Infinity 格式的数值及 null 都会被当做 null。
- 其他类型的对象,包括 Map/Set/WeakMap/WeakSet,仅会序列化可枚举的属性。
- 对包含循环引用的对象(对象之间相互引用,形成无限循环)执行此方法,会抛出错误。
:::
// 下面就是循环引用;
var obj1 = {
x: 1,
y: 2
};
obj1.z = obj1;
var obj2 = JSON.parse(JSON.stringify(obj1)); // 栈溢出,抛出错误;
所以当我们要克隆的对象里面还有引用类型时,我们只能采用递归的方法进行遍历了,这里就不展开了。
- JOSN.stringify() 有几个参数?
JSON.stringify(value[, replacer [, space]])
我们主要看一下 第二个参数和第三个参数时干啥用的
- replacer 可选
- 如果该参数是一个函数,则在序列化过程中,被序列化的值的每个属性都会经过该函数的转换和处理;
- 如果该参数是一个数组,则只有包含在这个数组中的属性名才会被序列化到最终的 JSON 字符串中;
- 如果该参数为 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 可选
- 指定缩进用的空白字符串,用于美化输出(pretty-print)
- 如果参数是个数字,它代表有多少的空格;
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()))
错误
- SyntaxError 语法错误
- 变量名不规范
var 1 = 1
- 关键字赋值
new = 5
- 基本语法错误
var a = 1:
- 变量名不规范
- ReferenceError 引用错误
- 变量或函数未被声明
console.log(a)
- 给无法赋值的对象赋值
var a = 1 = 2
- 变量或函数未被声明
- RangeError 范围错误
- 数组长度赋值为负数
var arr = [0];arr.length = -1
- 对象方法参数超出可行范围
var num = 1;num.toFixed(-1)
- 数组长度赋值为负数
- 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')
- EvalError evel 函数执行错误
- 最大作用就是json字符串的解析,但我们用JSON.parse-
- 但不建议使用
var jsonStr = '[{"name":"luck"}]' var obj = eval(jsonStr) console.log(obj)//[ { name: 'luck' } ]
- 自定义错误
var err = new Error('代码错误') console.log(err)
- 错误的一般处理
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()
- 将过失错误转成异常
- 严格模式下无法再意外创建全局变量
"use strict";
// 假如有一个全局变量叫做mistypedVariable
mistypedVaraible = 17; // 因为变量名拼写错误
// 这一行代码就会抛出 ReferenceError
- 严格模式会使引起静默失败(注:不报错也没有任何效果)的赋值操作抛出异常.
例如, 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错误
- 在严格模式下, 试图删除不可删除的属性时会抛出异常(之前这种操作不会产生任何效果):
"use strict";
delete Object.prototype; // 抛出TypeError错误
- 严格模式要求一个对象内的所有属性名在对象内必须唯一。
正常模式下重名属性是允许的,最后一个重名的属性决定其属性值。
在严格模式下,重名属性被认为是语法错误:
"use strict";
var o = { p: 1, p: 2 }; // !!! 语法错误
- 严格模式要求函数的参数名唯一.
在正常模式下, 最后一个重名参数名会掩盖之前的重名参数. 之前的参数仍然可以通过 arguments[i] 来访问, 还不是完全无法访问.
然而, 这种隐藏毫无意义而且可能是意料之外的 (比如它可能本来是打错了), 所以在严格模式下重名参数被认为是语法错误:
function sum(a, a, c) { // !!! 语法错误
"use strict";
return a + a + c; // 代码运行到这里会出错
}
- 严格模式禁止八进制数字语法.
ECMAScript并不包含八进制语法,
但所有的浏览器都支持这种以零(0)开头的八进制语法: 0644 === 420 还有 “\045” === “%”.
在ECMAScript 6中支持为一个数字加"0o"的前缀来表示八进制数.
var a = 0o10; // ES6: 八进制
- 第七,ECMAScript 6中的严格模式禁止设置原始值的属性.
不采用严格模式,设置属性将会简单忽略(no-op),
采用严格模式,将抛出TypeError错误
(function() {
"use strict";
false.true = ""; //TypeError
(14).sailing = "home"; //TypeError
"with".you = "far away"; //TypeError
})();
- 简化变量的使用
- 严格模式禁用 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
- 严格模式禁止删除声明变量。delete name 在严格模式下会引起语法错误:
"use strict";
var x;
delete x; // !!! 语法错误
eval("var y; delete y;"); // !!! 语法错误
- arguments变的简单
- 严格模式下,参数的值不会随 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);
- 不再支持 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引擎自动回收
- 找出不再使用的变量
- 释放其占有内存
- 固定的时间间隔运行
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会假定以下情况:
- 如果输入的 string以 "0x"或 “0x”(一个0,后面是小写或大写的X)开头,那么radix被假定为16,字符串的其余部分被当做十六进制数去解析。
- 如果输入的 string以 “0”(0)开头, radix被假定为8(八进制)或10(十进制)。具体选择哪一个radix取决于实现。
ECMAScript 5 澄清了应该使用 10 (十进制),但不是所有的浏览器都支持。
因此,在使用 parseInt 时,一定要指定一个 radix。 - 如果输入的 string 以任何其他值开头, radix 是 10 (十进制)。
- 如果第一个字符不能转换为数字,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
面试题
- 访问的时候改变属性
// ===============
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!!!!')
}
定时器引发的思考
- 定时器真的是定时执行的吗?
定时器并不能保证真正地定时执行 - 定时器回调函数是在分线程执行的吗
在主线程执行的,js是单线程 - 定时器是如何实现的
事件循环模型
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. 第三方包