目录
一、JS简介
JavaScript 是一种解释型的脚本语言,被大量地应用于网页中,用以实现网页和浏览者的动态交互。目前几乎所有的浏览器都可以很好地支持 JavaScript。 JavaScript 可以及时响应浏览者的操作,控制页面的行为表现,提高用户体验
JavaScript的组成部分
标准化后的 JavaScript 包含了 3 个组成部分,如图所示。
- ECMAScript
脚本语言的核心内容,它规定了js的语法标准,现在每种浏览器都有对ECMAScript标准的实现。 - DOM(Document Object Model)
文档对象模型,它是HTML和XML文档的应用程序编程接口。浏览器中的DOM把整个网页规划成由节点层级构成的树状结构的文档。用DOM API可以轻松地删除、添加和替换文档树结构中的节点。如addEventListener(),document.getElementById,e.stopPropagation() - BOM(Browser Object Model)
浏览器对象模型,描述了对浏览器窗口进行访问和操作的方法和接口。如location对象,navigator对象,screen对象,document对象,核心window
二、作用域与作用域链
函数作用域和全局作用域
所谓函数作用域就是说:-》变量在声明它们的函数体内是有定义的
变量提升
var scope="global";
function t(){
console.log(scope);
var scope="local"
console.log(scope);
}
t();
变量提升后相当于
var scope="global";
function t(){
var scope;
console.log(scope);
scope="local"
console.log(scope);
}
t();
// undefind local
无块级作用域
function t(flag){
if(flag){
var s="ifscope";
for(var i=0;i<2;i++)
;
}
console.log(i);
console.log(s);
}
t(true);
// 2 ifscope
提示:1.es6之后Let拥有了块级作用域
提示:2.Js中没有用var声明的变量都是全局变量,而且是顶层对象的属性。(window | global)
作用域链
如果在当前函数作用域中没有查到值,就会向上级作用域去查,直到查到全局作用域,查找过程形成的链路就叫做作用域链。
name="baikui";
function t(){
var name="zhangsan";
function s(){
var name="lisi";
console.log(name);//s=>t=>global
}
function ss(){
console.log(name);//ss=>t=>global
}
s();
ss();
}
t();
// lisi zhangsan
易错案例
<html>
<head>
<script type="text/javascript">
function buttonInit() {
for (var i = 1; i < 4; i++) {
var b = document.getElementById("button" + i);
b.addEventListener("click", function () {
console.log("Button" + i);
}, false);
}
}
</script>
</head>
<body onload="buttonInit()">
<button id="button1">Button1</button>
<button id="button2">Button2</button>
<button id="button3">Button3</button>
</body>
</html>
三、this关键字详解
this的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定this到底指向谁,实际上this的最终指向的是那个调用它的对象
JS里面,函数的几种调用方式:
1.普通函数调用
2.作为方法来调用
3.作为构造函数来调用
4.利用apply/call/bind方法来调用
5.es6箭头函数
但是不管函数是按哪种方法来调用的,记住一点:谁调用这个函数或方法,this关键字就指向谁。
1、普通函数调用
var name="xl";
function person(){
console.log(this.name);
}
person(); //输出 xl
//实际上person是作为全局对象window的一个方法来进行调用的,即window.person();
// 同时window还拥有了另外一个属性name,值为xl
2、作为方法来调用
var name="XL";
var person={
name:"xl",
showName:function(){
console.log(this.name);
}
}
person.showName(); //输出 xl
//这里是person对象调用showName方法,很显然this关键字是指向person对象的,所以会输出name
var showNameA=person.showName;
showNameA(); //输出 XL
//这里将person.showName方法赋给showNameA变量,此时showNameA变量相当于window对象的一个属性,因此showNameA()执行的时候相当于window.showNameA(),
// 即window对象调用showNameA这个方法,所以this关键字指向window
3、作为构造函数来调用
function Person(name){
this.name=name;
}
var personB=new Person("xl");
console.log(personB.name);// 输出 xl
//这部分代码的解释见下
4、使用apply/call方法来调用
在JS里函数也是对象,因此函数也有方法。从Function.prototype上继承到Function.prototype.call/Function.prototype.apply方法
call/apply方法最大的作用就是能改变this关键字的指向.
Obj.method.apply(AnotherObj,arguments);
var name="XL";
var person={
name:"xl",
showName:function(){
console.log(this.name);
}
}
var anotherPerson={name:'xxl'}
person.showName.call(anotherPerson); //输出 "xxl"
person.showName.call(); //输出 "XL"
//这里call方法里面的第一个参数为空,默认指向window。
//虽然showName方法定义在person对象里面,但是使用call方法后,将showName方法里面的this指向了anotherPerson|window。
// 因此最后不会输出xl;
5、作为箭头函数来调用
function Timer() {
this.seconds = 0;
setInterval( () => this.seconds ++, 1000);//箭头函数内的this指向词法环境,指向实例对象
}
var timer = new Timer();
setTimeout( () => console.log(timer.seconds), 3100);
// 3
在构造函数内部的setInterval()内的回调函数(箭头函数),this始终指向实例化的对象,并获取实例化对象的seconds的属性,每1s这个属性的值都会增加1
四、原型和原型链
原型
new 的过程分为三步
var p = new Person('张三',20);
1. var p={}; 初始化一个对象p。
2. p._proto_=Person.prototype;,将对象p的 __proto__ 属性设置为 Person.prototype
3. Person.call(p,”张三”,20);调用构造函数Person来初始化p。
“prototype”和“proto”进行简单的介绍:
对于所有的对象,都有__proto__属性,这个属性对应对象的原型.
对于函数对象,除了__proto__属性之外,还有prototype属性,当一个函数被用作构造函数来创建实例时,该函数的prototype属性值将被作为原型赋值给所有对象实例(也就是设置实例的__proto__属性)
原型链
属性查找
function Person(name, age){
this.name = name;
this.age = age;
}
Person.prototype.MaxNumber = 9999;
var personObj = new Person("xiaoming", 28);
console.log(personObj.MaxNumber); // 9999
console.log(personObj.MinNumber);
personObj.constructor == personObj.proto.constructor ==function Person
对象创建方式影响原型链
Object.prototype.age=20
var personObj = {
name: "张三",
getInfo: function(){
console.log(this.name + " is " + this.age + " years old");
}
}
console.log(personObj.getInfo());
当使用这种方式创建一个对象的时候,原型链就变成下图了. July对象的原型是”Object.prototype”也就是说对象的构建方式会影响原型链的形式。
五、同步和异步
单线程
JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事,JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM;他的缺点是很多时候CPU是空闲状态,因为IO设备(输入输出设备)很慢(比如Ajax操作从网络读取数据),不得不等着结果出来,再往下执行。
于是,所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)
1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
2)主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
3)一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
4)主线程不断重复上面的第三步,因为每个任务都是由一个事件触发的,因此也叫作事件循环。
所谓"回调函数"(callback),就是那些会被主线程挂起来的代码。异步任务必须指定回调函数,当主线程开始执行异步任务,就是执行对应的回调函数。A callback is a function that is passed as an argument to another function and is executed after its parent function has completed.
function fun1() {
console.log(1);
}
function fun2() {
console.log(2);
}
function fun3() {
console.log(3);
}
fun1();
setTimeout(function(){
fun2();
},0);//即使是0也会落入任务队列,等到主线程执行结束,才会执行
fun3();
// 输出
1
3
2
异步编程
图片资源还未请求完毕
上图可以看到,我要购买一个东西,当我点进物品的详情页之后,图片资源还未请求完毕,而此时我就可以点击add to cart, 发起另一个请求,js任务列表中的添加购物车事件就会开始执行,它的执行也不会干扰到图片资源的请求任务,这也是异步执行机制的妙处
<html>
<body>
加入购物车:
<img src="./hb.png" onload="loadImage()"></img>
<div onclick="addToCart()">Add to cart</div>
</body>
<script>
function addToCart() { //回调函数
alert('已加入购物车')
}
function loadImage() {
alert('图片加载完毕')
}
</script>
<html>
异步事件:
1、定时器 setTimeout和setInterval
2、事件绑定
3、AJAX
4、promise等
六、promise
ECMAscript 6 原生提供了 Promise 对象。
Promise 对象代表了未来将要发生的事件,用来传递异步操作的消息。
1、有三种状态:
- pending: 初始状态。
- fulfilled: 意味着操作成功完成。
- rejected: 意味着操作失败。
2、包含then 方法
一个Promise必须提供一个then方法来获取其值或原因。
Promise的then方法接受两个参数:promise.then(onFulfilled, onRejected)
then 返回一个promise,可以进行.then链式操作,异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数
3、立即执行
promise 实例创建
要想创建一个 promise 对象、可以使用 new 来调用 Promise 的构造器来进行实例化。
var rf = require("fs");//node 核心模块
let myRequest = new Promise(function (resolve, reject) {
rf.readFile("./text.txt", 'utf-8', function (err, data) {
if (err) {
reject(err);
} else {
resolve(data)
}
});
})
myRequest.then((value) => {
console.log('成功结果' + value)
return value // then中返回的结果会传递到下个then参数中
}, (reason) => {
console.log('失败原因' + reason)
})
ES6 class类——语法糖
定义: class (类)作为对象的模板被引入,可以通过 class 关键字定义类。它的本质是函数(function),可以看作一个语法糖,让对象原型的写法更加简单明了、更接近与面向对象的编程思想(类似python、java等)。
function Mold(a,b){
this.a=a;
this.b=b;
}
Mold.prototype.count=function(){
return this.a+this.b;
};
let sum=new Mold(1,2);
console.log(sum.count()) //3
//上下一致
class Mold{
constructor(a,b){
this.a=a; //关键字指向着实例对象
this.b=b;
}
count(){
return this.a+this.b;
}
//static fn1(){
//console.log("静态方法")
//}
}
let sum=new Mold(1,2);
console.log(sum.count()) //3
在属性或方法前面使用 static定义类的静态属性和方法;
所有的静态属性和静态方法都不能通过实例化的对象调用;
需要通过类来调用,静态属性和静态方法是类的专属属性和方法,和实例化对象无关,比如数组和数学方法中的:Array.from();Math.random()。
手动封装Promise
const RESLOVED = "RESLOVED"
const REJECTED = "REJECTED"
const PENDING = "PENDING"
class Promise1 {
constructor(excutor) {
this.status = PENDING;
this.value = "";
this.reason = "";
this.resloveList = [];
this.rejectList = []
this.success = (value) => {
if (this.status == PENDING) {
this.value = value;
this.status = RESLOVED;
this.resloveList.forEach((fn) => {
fn()
})
}
};
this.filled = (value) => {
if (this.status == PENDING) {
this.reason = value
this.status = REJECTED;
this.rejectList.forEach((fn) => {
fn()
})
}
};
try {
excutor(this.success, this.filled)
} catch (err) {
this.filled(err)
}
}
then(onFulfilld, onRejected) {
if (this.status == RESLOVED) {
onFulfilld(this.value)
} else if (this.status == REJECTED) {
onRejected(this.reason)
} else { // pending
// 异步返回采用 发布订阅模式
this.resloveList.push(() => {
onFulfilld(this.value)
})
this.rejectList.push(() => {
onRejected(this.reason)
})
}
}
}
module.exports = Promise1 // commmonJs node语法
提示:Promise的其他方法 all、race、resolve 、reject等
总结
以上就是今天要讲的一些JS比较重要的概念和原理、有利于之后学习vue和react框架以及他们底层的框架源码设计模式;之后无论是在浏览器还是在 Node.js 中,都会成为一名更成熟的 JavaScript 开发者
之后大家有相关的学习可以一起再深入探讨