重读JavaScript高级程序设计(2)
本文同步本人掘金平台的文章:https://juejin.cn/post/6844903569317953550继承
许多的OO语言都支持两种继承方法:接口继承和实现继承。接口继承只继承方法签名,而实现继承则继承实际的方法。由于函数没有签名,在ECMAScript中无法实现接口继承。ECMAScript只支持实现继承,而且实现主要是依靠原型链来实现的。[p162]原型链
原型链的基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。回顾下构造函数、原型和实例的关系: 每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。function SuperType(){ this.property = true; } SuperType.prototype.getSuperValue = function(){ return this.property; } function SubType(){ this.subProperty = false; } // 继承了SuperType,重点哦 SubType.prototype = new SuperType(); SubType.prototype.getSubValue = function(){ return this.subProperty; } var instance = new SubType(); console.log(instance.getSuperValue()); // true 复制代码
原型链继承带来两个问题:一是原型实际上变成了另一个类型的实例,于是,原先的实例属性也就变成了现在原型的属性,共享了属性。二是在创建子类型的实例时,不能在没有影响所有对象实例的情况下向超类型的构造函数传递参数。借用构造函数
借用构造函数解决原型链继承带来的不能向构造函数传递仓鼠的问题。这里使用到了apply()或者call()方法在新创建的对象上执行构造函数。function SuperType(){ this.colors = ["red","blue","green"]; } function SubType(){ // 继承SuperType SuperType.call(this); // SuperType.apply(this)同效 } var instance1 = new SubType(); instance1.color.push("black"); console.log(instance1.colors); // "red,blue,green,black" var instance2 = new SubType(); console.log(instance2.colors); // "red,blue,green" 复制代码
上面的例子中,我在父类型构造函数中没有传参数,看者感兴趣的话可以自己加下参数来实验一番咯。
借用构造函数解决了原型链继承的确定,但是又没有接纳原型链的优点:共享。下面的组合继承结合了原型链和借用构造函数,容纳了两者的优点。组合继承
组合继承的思路是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。function SuperType(name){ this.name = name; this.colors = ["red","blue","green"]; } SuperType.prototype.sayName = function(){ console.log(this.name); } function SubType(name,age){ // 继承属性 SuperType.call(this,name); this.age = age; } // 继承方法 SubType.prototype = new SuperType(); SubType.prototype.constructor =SubType; // 避免重写构造函数指向错误 SubType.prototype.sayAge = function(){ console.log(this.age); } var instance1 = new SubType("nicholas" , 29); instance1.colors.push("black"); console.log(instance1.colors); // "red,blue,green,black" instance1.sayName(); // "nicholas" instance1.sayAge(); // 29 var instance2 = new SubType("greg" , 27); console.log(instance2.colors); // "red,blue,green" instance2.sayName(); // "greg" instance2.sayAge(); // 27 复制代码
组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点,成为了JavaScript中最常用的继承模式。而且,instanceof和isPrototypeOf()也能够用于识别基于组合继承创建的对象。原型式继承
原型式继承是借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义的类型。function object(o){ // 传入一个对象 function F(){}; F.prototype = o; return new F(); } var person = { name : "nicholas", friends: ["shelby","court","van"] }; var anotherPerson = object(person); anotherPerson.name = "greg"; anotherPerson.friends.push("rob"); var yetAnotherPerson = object(person); yetAnotherPerson.name = "linda"; yetAnotherPerson.friends.push("barbie"); console.log(person.friends); // "shelby,court,van,rob,barbie" 复制代码寄生式继承
寄生式继承是与原型继承紧密相关的一种思路。寄生式继承的思路与寄生构造函数和工厂模式类似,即是创建了一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真的做了所有工作一样返回对象。function object(o){ // 传入一个对象 function F(){}; F.prototype = o; return new F(); } function createAnother(original){ var clone = object(original); clone.sayHi = function(){ console.log("hi"); }; return clone; } var person = { name : "nicholas", friends : ["shelby","court","van"] } var anotherPerson = createAnother(person); anotherPerson.sayHi(); // "hi" 复制代码
上面的例子中,新对象anotherPerson不仅具有person的所有属性和方法,而且还有了自己的sayHi()方法。寄生组合式继承
组合继承是JavaScript最常用的继承模式;不过,它也有自己的不足。组合继承最大的问题就是无论什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。寄生组合式继承能够解决这个问题。
所谓寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。其背后的基本思路是不必为了指定子类型的原型而调用超类型的构造函数,我们所需要的无非就是超类型的原型的一个副本而已。寄生组合式继承的基本模式如下:function inheritPrototype(subType,superType){ var prototype = Object(superType.prototype); // 创建对象 prototype.constructor = subType; // 增强对象,防止下面重写constructor属性 subType.prototype = prototype; // 指定对象 } 复制代码
一个完整的例子如下,相关插图见书[p173]:function inheritPrototype(subType,superType){ var prototype = Object(superType.prototype); prototype.constructor = subType; subType.prototype = prototype; } function SuperType(name){ this.name = name; this.colors = ["red","blue","green"]; } SuperType.prototype.sayName = function(){ alert(this.name); } function SubType(name, age){ SuperType.call(this, name); // 只在这调用了一次超类型的构造函数 this.age = age; } inheritPrototype(SubType , SuperType); SubType.prototype.sayAge = function(){ console.log(this.age); } var instance = new SubType("nicholas" , 29); 复制代码
上面的例子的高效处体现在它只调用了一次SuperType构造函数,并且避免了在SubType.prototype上创建不必要的,多余的属性。与此同时,原型链还能保持不变;因此还能正常使用instanceof和inPrototypeOf()。开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。闭包
闭包是指有权访问另一个函数作用域中的变量的函数。我的理解是,函数内的函数使用到外层函数的变量延长变量的生存时间,造成常驻内存。例子见下:function foo(){ var a = 2; return function(){ a += 1; console.log(a); } } var baz = foo(); baz(); // 3 baz(); // 4 baz(); // 5 baz(); // 6 复制代码
上面的例子中,外部的函数foo()执行完成之后,正常的情况下应该销毁a变量的,但是内部的返回的匿名函数使用到该变量,不能销毁。如果需要销毁的话,可以改写成下面:function foo(){ var a = 2; return function(){ a += 1; console.log(a); } } var baz = foo(); baz(); // 3 baz = null; // 将内部的匿名函数赋值为空 复制代码从闭包说起
谈到了闭包,这让我想起了不久前刷知乎看到一篇文章。自己整理如下:for(var i = 0 ; i < 5; i++){ setTimeout(function(){ console.log(i); },1000) } console.log(i); // 5,5,5,5,5,5 复制代码
上面的代码是输出了6个5,而这6个5是这样执行的,先输出全局中的console.log(i),然后是过了1秒种后,瞬间输出了5个5(为什么用瞬间这个词呢,怕看者理解为每过一秒输出一个5)。解读上面的代码的话,可以通过狭义范围(es5)的理解:同步 => 异步 => 回调 (回调也是属于异步的范畴,所以我这里指明了狭义啦)。先是执行同步的for,遇到异步的setTimeout(setTimeout和setInterval属于异步哈)后将其放入队列中等待,接着往下执行全局的console.log(i),将其执行完成后执行异步的队列。
追问1:闭包
改写上面的代码,期望输出的结果为:5 => 0,1,2,3,4。改造的方式一:for(var i = 0; i < 5; i++){ (function(j){ setTimeout(function(){ console.log(j); },1000); })(i); } console.log(i); // 5,0,1,2,3,4 复制代码
上面的代码巧妙的利用IIFE(Immediately Invoked Function Expression:声明即执行的函数表达式)来解决闭包造成的问题,闭包的解析看上面。
方法二:利用js中基本类型的参数传递是按值传递的特征,改造代码如下var output = function(i){ setTimeout(function(){ console.log(i); },1000); }; for(var i = 0; i < 5; i++){ output(i); // 这里传过去的i值被复制了 } console.log(i); // 5,0,1,2,3,4 复制代码
上面改造的两个方法都是执行代码后先输出5,然后过了一秒种依次输出0,1,2,3,4。
如果不要考虑全局中的console.log(i)输出的5,而是循环中输出的0,1,2,3,4。你还可以使用ES6的let块级作用域语法,实现超级简单:for(let i = 0; i < 5; i++){ setTimeout(function(){ console.log(i); },1000); } // 0,1,2,3,4 复制代码
上面是过了一秒钟后,依次输出0,1,2,3,4。这种做法类似于无形中添加了闭包。那么,如果使用ES6语法的话,会怎样实现5,0,1,2,3,4呢?
追问2:ES6
改造刚开始的代码使得输出的结果是每隔一秒输出0,1,2,3,4,大概第五秒输出5。
在不使用ES6的情况下:for(var i = 0; i < 5; i++){ (function(j){ setTimeout(function(){ console.log(j); },1000*j); })(i); } setTimeout(function(){ console.log(i); },1000*i); // 0,1,2,3,4,5 复制代码
上面的代码简单粗暴,但是不推荐。看题目是每隔一秒输出一个值,再回调实现最后的5输出,这个时候应该使用ES6语法来考虑,应该使用Promise方案:const tasks = []; for(var i = 0; i < 5; i++){// 这里的i声明不能改成let,改成let的话请看下一段代码 ((j)=>{ tasks.push(new Promise((resolve)=>{ // 执行tasks setTimeout(()=>{ console.log(j); resolve(); // 这里一定要resolve,否则代码不会按照预期执行 },1000*j); })) })(i); } Promise.all(tasks).then(()=>{ // 执行完tasks,回调 setTimeout(()=>{ console.log(i); },1000); }); // 符合要求的每隔一秒输出 // 0,1,2,3,4,5 复制代码
如果是使用let,我的改造如下:const tasks = []; for (let i = 0; i < 5; i++) { tasks.push(new Promise((resolve) => { setTimeout(() => { console.log(i); resolve(); }, 1000 * i); })); } Promise.all(tasks).then(() => { setTimeout(() => { console.log(tasks.length); }, 1000); }); // 0,1,2,3,4,5 复制代码
上面的代码比较庞杂,可以将其颗粒话,模块化。对上面两段代码的带var那段进行改造后如下:const tasks = []; // 这里存放异步操作的Promise const output = (i) => new Promise((resolve) => { setTimeout(()=>{ console.log(i); },1000*i); }); // 生成全部的异步操作 for(var i = 0; i < 5; i++){ tasks.push(output(i)); } // 异步操作完成之后,输出最后的i Promise.all(tasks).then(() => { setTimeout(() => { console.log(i); },1000); }); // 符合要求的每隔一秒输出 // 0,1,2,3,4,5 复制代码
追问3:ES7
既然ES6的Promise可以写,那么ES7是否可以写呢,从而让代码更加简洁易读?那就使用到到了异步操作的async await特性啦。// 模拟其他语言中的sleep,实际上可以是任何异步操作 const sleep = (time) => new Promise((resolve) => { setTimeout(resolve , time); }); (async () => { for(var i = 0; i < 5; i++){ await sleep(1000); console.log(i); } await sleep(1000); console.log(i); })(); // 符合要求的每隔一秒输出 // 0,1,2,3,4,5 复制代码浏览器窗口位置
IE、Safari、Opera和Chrome都提供了screenLeft和screenTop属性,分别表示浏览器窗口相对于屏幕左上角和上边的位置[p197]。Firefox则以screenX和screenY属性来表示。为了兼容各个浏览器,可以入下面这样写:var leftPos = (typeof window.screenLeft == "number")?window.screenLeft : window.screenX; var topPos = (typeof window.screenTop == "number")? window.screenTop : window.screenY; 复制代码浏览器窗口大小
由于浏览器厂商以及历史的问题,无法确认浏览器本身的大小,但是可以取得视口的大小[p198]。如下:var pageWidth = window.innerWidth, pageHeight = window.innerHeight; if(typeof pageWidth != "number"){ if(document.compatMode == "CSS1Compat"){ // 标准模式下的低版本ie pageWidth = document.documentElement.clientWidth; pageHeight = document.documentElement.clientHeight; }else{ // 混杂模式下的chrome pageWidth = document.body.clientWidth; pageHeight = document.body.clientHeight; } } 复制代码
上面的示例可以简写成下面这样:var pageWidth = window.innerWidth || document.documentElement.clientWidth || document.body.clientHeight; var pageHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight; 复制代码canvas中的变换
为绘制上下文应用变换,会导致使用不同的变换矩阵应用处理,从而产生不同的结果。[p453]
可通过下面的方法来修改变换矩阵:rotation(angle):围绕原点旋转图像angle弧度scale(scaleX,scaleY)translate(x,y): 将坐标原点移动到(x,y)。执行这个变换后,坐标(0,0)会变成之前由(x,y)表示的点。JSON
关于JSON,最重要的是要理解它是一种数据格式,不是一种编程语言。对象字面量和JSON格式比较
先来看下对象字面量demo写法:var person = { name : "nicholas", age : 29 }; # 上面的代码也可以写成下面的 var person = { "name" : "nicholas", "age" : 29 }; 复制代码
而上面的对象写成数据的话,就是下面这样了:{ "name": "nicholas ", "age": 29 } # 可到网站 https://www.bejson.com/ 验证 复制代码
⚠️ 与JavaScript对象字面量相比,JSON对象又两个地方不一样。首先,没有声明变量(JSON中没有变量的概念)。其次,没有分号(因为这不是JavaScript语句,所以不需要分号)。留意的是,对象的属性必须加双引号(不是单引号哦),这在JSON中是必须的。stringify()和parse()
可以这么理解:JSON.stringify()是从一个object中解析成JSON数据格式,而JSON.parse()是从一个字符串中解析成JSON数据格式。var person = { name: "nicholas", age: 29 }; var jsonText = JSON.stringify(person); console.log(jsonText); // {"name":"nicholas","age":29} 复制代码var strPerson = "{"name":"nicholas","age":29}"; var jsonText = JSON.parse(strPerson); console.log(jsonText); // { name: "nicholas", age: 29 } 复制代码XMLHttpRequest对象
XMLHttpRequest对象用于在后台与服务器交换数据。它是Ajax技术的核心[p571]。
XMLHttpRequest对象能够使你:在不重新加载页面的情况下更新网页在页面已加载后从服务器请求数据在页面已加载后从服务器接收数据在后台向服务器发送数据
XMLHttpRequest的使用:# 创建XHR对象 => open()准备发送 => send()传送数据 // 创建对象,对浏览器做兼容 function createXHR(){ if(typeof XMLHttpRequest != "undefined"){ // IE7+和其他浏览器支持 return new XMLHttpRequest(); }else if(typeof ActiveXObject != "undefined"){ if(typeof arguments.callee.activeXString != "string"){ var versions = ["MSXML2.XMLHttp.6.0","MSXML2.XMLHttp.3.0","MSXML2.XMLHttp"]; // 低版的ie可能遇到三种不同版本的XMR对象 var i , len; for(i = 0,len = versions.length; i < len ; i++){ try{ new ActiveXObject(version[i]); arguments.callee.activeXString = versions[i]; break; }catch (ex){ // 跳过 } } } return new ActiveXObject(arguments.callee.activeXString); }else{ throw new Error("No XHR object available."); } } var xhr = createXHR(); // 准备发送数据 xhr.open("get","path/to/example.txt",false);// 非异步,异步的话第三个参数改为true // 传送数据 xhr.send(null); // get方法不需要传数据 // 判断状态嘛,获取服务器返回的数据 if((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){ console.log(xhr.responseText); }else{ console.log("Request was nsuccessful : " + xhr.status); } 复制代码跨域解决方案
何为跨域呢?只要访问的资源的协议、域名、端口三个不全相同,就可以说是非同源策略而产生了跨域了,这是狭义的说法。广义的说法:通过XHR实现Ajax通信的一个主要限制,来源于跨域的安全策略;默认情况下,XHR对象只能访问包含它的页面位于同一个域中的资源[p582]。注:部分文字和代码引用自前端常见跨域解决方案(全)CORS
CORS(Cross-Origin Resource Sharing,跨资源共享)定义了在必须访问跨资源时,浏览器与服务器应该如何沟通。其背后的基本思想,就是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功,还是应该失败。 复杂的跨域请求应当考虑使用它。
普通跨域请求:只服务端设置Access-Control-Allow-Origin即可,前端无需设置,如果要带cookie请求:前后端都要设置。
1.前端设置
1.) 原生ajaxfunction createCORSRequest(method,url){ // 兼容处理,ie8/9需要用到window.XDomainRequest var xhr = new XMLHttpRequest(); // 前端设置是否带cookie xhr.withCredentials = true; if("withCredentials" in xhr){ // 其他的用到withCredentials xhr.open(method,url,true); }else if(typeof XDomainRequest != "undefined"){ xhr = new XDomainRequest(); xhr.open(method , url); }else{ xhr = null; } return xhr; } // get请求 var request = createCORSRequest("get","http://www.somewhere-else.com/page/"); if(request){ request.onload = function(){ // 对request.responseText 进行处理 }; request.send(); } // post请求,带cookie var requestXhr = createCORSRequest("post","http://www.somewhere-else.com/page/"); requestXhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded"); requestXhr.send("user=admin"); xhr.onreadystatechange = function() { if (xhr.readyState == 4 && xhr.status == 200) { alert(xhr.responseText); } }; 复制代码
2.)jquery ajax
上面写了一大堆原生的,看得头都有点大了,还是使用jquery ajax 比较舒服:$.ajax({ ... xhrFields: { withCredentials: true // 前端设置是否带cookie }, crossDomain: true, // 会让请求头中包含跨域的额外信息,但不会含cookie ... }); 复制代码
3.) vue框架
在vue-resource封装的ajax组建中加入以下代码:Vue.http.options.credentials = true; 复制代码
2.服务器设置
若后端设置成功,前端浏览器控制台上就不会出现跨域报错的信息,反之,说明没有成功。
1.) java后台/* * 导入包:import javax.servlet.http.HttpServletResponse; * 接口参数中定义:HttpServletResponse response */ response.setHeader("Access-Control-Allow-Origin", "http://www.domain1.com"); // 若有端口需写全(协议+域名+端口) response.setHeader("Access-Control-Allow-Credentials", "true"); 复制代码
2.) node后台var http = require("http"); var server = http.createServer(); var qs = require("querystring"); server.on("request", function(req, res) { var postData = ""; // 数据块接收中 req.addListener("data", function(chunk) { postData += chunk; }); // 数据接收完毕 req.addListener("end", function() { postData = qs.parse(postData); // 跨域后台设置 res.writeHead(200, { "Access-Control-Allow-Credentials": "true", // 后端允许发送Cookie "Access-Control-Allow-Origin": "http://www.domain1.com", // 允许访问的域(协议+域名+端口) "Set-Cookie": "l=a123456;Path=/;Domain=www.domain2.com;HttpOnly" // HttpOnly:脚本无法读取cookie }); res.write(JSON.stringify(postData)); res.end(); }); }); server.listen("8080"); console.log("Server is running at port 8080..."); 复制代码JSONP
JSONP是JSON with padding(填充式JSON或参数式JSON)的简写,是应用JSON的一种新方法,在后来的web服务中非常流行。简单的跨域请求用JSONP即可。
通常为了减轻web服务器的负载,我们把js,css,img等静态资源分离到另一台独立域名的服务器,在html页面中再通过相应的标签从不同域名下加载静态资源,而被浏览器允许,基于此原理,我们可以通过动态创建script,再请求一个带参网址实现跨域通信。
1.前端实现
1.)原生实现 复制代码
服务器返回如下(返回时即执行全局函数):onBack({"status": true,"user":"admin"}) 复制代码
2.)jquery ajax$.ajax({ url: "http://www.domain2.com:8080/login", type: "get", dataType: "jsonp", // 请求方式为jsonp jsonpCallback: "onBack", // 自定义回调函数名 data: {} }); 复制代码
3.)vue.jsthis.$http.jsonp("http://www.domain2.com:8080/login",{ params: {}, jsonp: "onBack " }).then((res)=>{ console.log(res); }); 复制代码
2.后端nodejs代码的示范:var qs = require("querystring"); var http = require("http"); var server = http.createServer(); server.on("request",function(req,res){ var params = qs.parse(req.url.split("?")[1]); var fn = params.callback; // jsonp返回设置 res.writeHead(200,{"Content-Type":"text/javascript"}); res.write(fn + "("+JSON.stringify(params)+")"); res.end(); }); server.listen("8080"); console.log("Server is running at port 8080 ..."); 复制代码
⚠️ jsonp缺点:只能实现get一种请求。WebSocket协议跨域
WebSocket protocol 是 HTML5一种新的协议。它实现了浏览器与服务器全双工通信,同时允许跨域通讯。
原生的WebSocket API使用起来不太方便,示例中使用了socket.io,它很好的封装了webSocket接口,提供了更简单、灵活的接口,也对不支持webSocket的浏览器提供了向下兼容。
1.前端代码user input: 复制代码
2.node socket后台var http = require("http"); var socket = require("socket.io"); // 启http服务 var server = http.createServer(function(req, res) { res.writeHead(200, { "Content-type": "text/html" }); res.end(); }); server.listen("8080"); console.log("Server is running at port 8080..."); // 监听socket连接 socket.listen(server).on("connection", function(client) { // 接收信息 client.on("message", function(msg) { client.send("hello:" + msg); console.log("data from client: ---> " + msg); }); // 断开处理 client.on("disconnect", function() { console.log("Client socket has closed."); }); }); 复制代码requestAnimationFrame()帧动画
requestAnimationFrame 创建平滑的动画[p682]。在此之前都是使用setTimeout或者setInterval实现,requestAnimationFrame与它们相比:不需要时间间隔,会贴切浏览器的刷新频率在切换到另外的页面时,会停止运行
使用的示范如下:1 复制代码// 兼容浏览器 (function(){ var lastTime = 0; var vendors = ["webkit","moz","ms","-o"]; for(var x = 0;x
大S多年前的限制级作品,令人毛骨悚然娱评大赏汪小菲和大S的瓜,仿佛是一场又臭又长的连续剧。关于爱情,关于金钱,男人女人,新欢旧爱,理不清,道不明。微博上热闹了两周,一直还是处于未完待续的状态。娱乐圈明星的家长里短,不
周迅的前男友,被赵丽颖的闺蜜骂拖油瓶上热搜,骂得好你看综艺再见爱人2了吗?这次带动热搜的是赵丽颖的闺蜜张婉婷。她种种疯批操作屡屡被骂上热搜。她骂的不是别人,而是周迅的前男友齐溪的前夫自己的老公宋宁峰(娱乐圈就是这样,说不定谁跟谁就
关晓彤生图流出,网友土味十足,鹿晗知道吗?金鸡颁奖的红毯上,不少女星都让人惊艳。可只有关晓彤,让人惊讶。关于关晓彤土的热搜都有好几个,也算是掌握了流量密码吧。白衬衫黑内搭短半截长拖地垮领带手拿包KTV服务生。这种黑色内搭,
前中后期都涵盖!想要挑选强势英雄,这份榜单不能少前中后期都涵盖!想要挑选强势英雄,这份榜单不能少王者荣耀现在已经出现了100多位英雄,并不是每一个英雄在游戏里面都特别的强,基本上每个英雄都会分为前中后三个时期,有些英雄在前期的伤
王者荣耀之皇铠的干货1,终究是版本为神,当年铠皇风采不在,现如今峡谷谁为王者,凯皇不称皇,还有谁敢称皇2,你们所谓的秒人是什么,妲己的231吗?安琪拉的213吗?还是谁?在当年的凯皇面前你说你秒人了,
当前版本SP英雄培养顺序及就业方向无援孤岛和其他阵容在二条。今年6个SP英雄已经全部亮相,今天盒子君给大家写一份关于SP的年末总结文章,对SP的培养优先度以及就业环境做出总结,如图表格里的猎宝数据为近期三种地形的最
苹果官网标错价格,精神小伙一夜怒薅12万,结局你猜到了吗?提起苹果,想必很多人是又爱又恨,一方面它长期引领了智能手机的潮流,侧面促进了国内手机厂商的发展,但同时,在它的影响下,国内手机的价格也是初心不在,一路水涨船高。苹果之所以能在世界范
有哪些事,是买了华为手机才知道的?入手华为之后,发现实际使用中有一些设计很不错,今天整理出来分享给大家,华为粉可进来交流分享哦!智能截屏完整截屏使用指关节用力双击屏幕即可截取全屏。部分截屏指关节绘制封闭图案可以单独
北极爆发大规模变暖!科学家复活13种病毒,地球潘多拉魔盒开了?北极出问题了,出现大规模变暖了!12月北极地区出现了异常的变暖模式,并且大部分地区都是超过了常年12月的平均气温情况,难道我们地球真的要发生重大转变了?的确,北极变暖不是什么好事情
并非所有的太空垃圾都已经消失殆尽,有僵尸卫星在你的头顶上盘旋一颗卫星在地球上空的艺术家印象,太阳能电池板显示的是一个僵尸的形象。看来有些卫星是打不死的小强。图片来源KiselevAndreyValerevich,aapppShutterst
科学向日葵天文课堂草帽土星透过小型天文望远镜,一颗带有光环酷似草帽的行星飘荡在星海之中,这就是土星。它的体积是地球的764倍,质量是地球的95倍。绕太阳公转一周大约需要29。58年,自转一周只需要10个小时